200 lines
6.8 KiB
Rust
200 lines
6.8 KiB
Rust
//! Generated configuration trees.
|
|
|
|
use crate::{ConfigValue, ConfigViolation, FieldDescriptor, FieldId, FieldWriteError};
|
|
|
|
/// One recursively composable configuration tree.
|
|
///
|
|
/// `#[derive(kaptan_config::Config)]` implements this contract for both scalar
|
|
/// records and records composed from other configuration trees. Validation,
|
|
/// metadata, field routing, and schema identity therefore come from one source.
|
|
pub trait Config {
|
|
/// Number of scalar fields in the flattened tree.
|
|
const FIELD_COUNT: usize;
|
|
|
|
/// Fingerprint generated from the complete declared schema.
|
|
const SCHEMA_ID: [u8; 16];
|
|
|
|
/// Returns a descriptor from the flattened declaration order.
|
|
fn descriptor(index: usize) -> Option<&'static FieldDescriptor>;
|
|
|
|
/// Returns one scalar by stable field identity.
|
|
fn field_value(&self, id: FieldId) -> Option<ConfigValue>;
|
|
|
|
/// Applies one scalar only when its owning record accepts the local value.
|
|
/// Whole-tree relational validation is intentionally deferred until the
|
|
/// candidate is validated or committed.
|
|
fn set_field(&mut self, id: FieldId, value: ConfigValue) -> Result<(), FieldWriteError>;
|
|
|
|
/// Validates every generated rule and declared post-validation hook.
|
|
fn validate(&self) -> Result<(), ConfigViolation>;
|
|
}
|
|
|
|
/// Detects duplicate flattened field identities in a generated tree.
|
|
#[doc(hidden)]
|
|
pub fn __unique_field_ids<C: Config>() -> bool {
|
|
let mut index = 0;
|
|
while index < C::FIELD_COUNT {
|
|
let Some(descriptor) = C::descriptor(index) else {
|
|
return false;
|
|
};
|
|
let mut other = index + 1;
|
|
while other < C::FIELD_COUNT {
|
|
let Some(other_descriptor) = C::descriptor(other) else {
|
|
return false;
|
|
};
|
|
if descriptor.id == other_descriptor.id {
|
|
return false;
|
|
}
|
|
other += 1;
|
|
}
|
|
index += 1;
|
|
}
|
|
C::descriptor(C::FIELD_COUNT).is_none()
|
|
}
|
|
|
|
/// Checks display-key uniqueness across one recursively composed schema.
|
|
#[doc(hidden)]
|
|
pub fn __unique_display_keys<C: Config>() -> bool {
|
|
let mut index = 0;
|
|
while index < C::FIELD_COUNT {
|
|
let Some(descriptor) = C::descriptor(index) else {
|
|
return false;
|
|
};
|
|
let mut other = index + 1;
|
|
while other < C::FIELD_COUNT {
|
|
let Some(other_descriptor) = C::descriptor(other) else {
|
|
return false;
|
|
};
|
|
if descriptor.display_key == other_descriptor.display_key {
|
|
return false;
|
|
}
|
|
other += 1;
|
|
}
|
|
index += 1;
|
|
}
|
|
true
|
|
}
|
|
|
|
/// Validates the bounded, self-describing enum portion of generated metadata.
|
|
#[doc(hidden)]
|
|
pub fn __valid_enum_options(descriptors: &[FieldDescriptor]) -> bool {
|
|
for descriptor in descriptors {
|
|
if descriptor.enum_options.len() > crate::MAX_ENUM_OPTIONS_PER_FIELD {
|
|
return false;
|
|
}
|
|
for (index, option) in descriptor.enum_options.iter().enumerate() {
|
|
if option.1.is_empty()
|
|
|| option.1.len() > crate::MAX_ENUM_OPTION_LABEL_BYTES
|
|
|| descriptor.kind != crate::ValueKind::U32
|
|
{
|
|
return false;
|
|
}
|
|
if descriptor.enum_options[..index]
|
|
.iter()
|
|
.any(|previous| previous.0 == option.0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
const FINGERPRINT_LEFT_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
|
|
const FINGERPRINT_RIGHT_OFFSET: u64 = 0x6c62_272e_07bb_0142;
|
|
const FINGERPRINT_LEFT_PRIME: u64 = 0x0000_0100_0000_01b3;
|
|
const FINGERPRINT_RIGHT_PRIME: u64 = 0x9e37_79b1_85eb_ca87;
|
|
|
|
const fn hash_byte(mut state: [u64; 2], byte: u8) -> [u64; 2] {
|
|
state[0] = (state[0] ^ byte as u64).wrapping_mul(FINGERPRINT_LEFT_PRIME);
|
|
state[1] = (state[1] ^ byte as u64).wrapping_mul(FINGERPRINT_RIGHT_PRIME);
|
|
state
|
|
}
|
|
|
|
const fn hash_u64(mut state: [u64; 2], value: u64) -> [u64; 2] {
|
|
let bytes = value.to_le_bytes();
|
|
let mut index = 0;
|
|
while index < bytes.len() {
|
|
state = hash_byte(state, bytes[index]);
|
|
index += 1;
|
|
}
|
|
state
|
|
}
|
|
|
|
const fn hash_str(mut state: [u64; 2], value: &str) -> [u64; 2] {
|
|
state = hash_u64(state, value.len() as u64);
|
|
let bytes = value.as_bytes();
|
|
let mut index = 0;
|
|
while index < bytes.len() {
|
|
state = hash_byte(state, bytes[index]);
|
|
index += 1;
|
|
}
|
|
state
|
|
}
|
|
|
|
const fn hash_value(mut state: [u64; 2], value: Option<ConfigValue>) -> [u64; 2] {
|
|
let Some(value) = value else {
|
|
return hash_byte(state, 0);
|
|
};
|
|
state = hash_byte(state, value.kind() as u8);
|
|
hash_u64(state, value.raw_bits())
|
|
}
|
|
|
|
const fn finish_fingerprint(state: [u64; 2]) -> [u8; 16] {
|
|
let left = state[0].to_le_bytes();
|
|
let right = state[1].to_le_bytes();
|
|
[
|
|
left[0], left[1], left[2], left[3], left[4], left[5], left[6], left[7], right[0], right[1],
|
|
right[2], right[3], right[4], right[5], right[6], right[7],
|
|
]
|
|
}
|
|
|
|
/// Produces the stable schema fingerprint for one scalar record.
|
|
#[doc(hidden)]
|
|
pub const fn __schema_id(descriptors: &[FieldDescriptor]) -> [u8; 16] {
|
|
let mut state = [FINGERPRINT_LEFT_OFFSET, FINGERPRINT_RIGHT_OFFSET];
|
|
state = hash_u64(state, descriptors.len() as u64);
|
|
let mut index = 0;
|
|
while index < descriptors.len() {
|
|
let descriptor = descriptors[index];
|
|
state = hash_u64(state, descriptor.id.get() as u64);
|
|
state = hash_str(state, descriptor.display_key);
|
|
state = hash_byte(state, descriptor.kind as u8);
|
|
state = hash_value(state, descriptor.minimum);
|
|
state = hash_value(state, descriptor.maximum);
|
|
state = hash_u64(state, descriptor.enum_options.len() as u64);
|
|
let mut option = 0;
|
|
while option < descriptor.enum_options.len() {
|
|
state = hash_u64(state, descriptor.enum_options[option].0 as u64);
|
|
state = hash_str(state, descriptor.enum_options[option].1);
|
|
option += 1;
|
|
}
|
|
state = hash_str(state, descriptor.unit);
|
|
index += 1;
|
|
}
|
|
finish_fingerprint(state)
|
|
}
|
|
|
|
/// Produces a parent fingerprint from ordered child fingerprints.
|
|
///
|
|
/// A one-child wrapper is deliberately transparent, so adding or removing a
|
|
/// redundant structural wrapper does not invalidate persisted values.
|
|
#[doc(hidden)]
|
|
pub const fn __combine_schema_ids(children: &[[u8; 16]]) -> [u8; 16] {
|
|
if children.len() == 1 {
|
|
return children[0];
|
|
}
|
|
let mut state = [FINGERPRINT_LEFT_OFFSET, FINGERPRINT_RIGHT_OFFSET];
|
|
state = hash_u64(state, children.len() as u64);
|
|
let mut child = 0;
|
|
while child < children.len() {
|
|
let mut byte = 0;
|
|
while byte < children[child].len() {
|
|
state = hash_byte(state, children[child][byte]);
|
|
byte += 1;
|
|
}
|
|
child += 1;
|
|
}
|
|
finish_fingerprint(state)
|
|
}
|