Files
kaptan/firmware/libs/kaptan-config-derive/tests/section.rs
T

293 lines
7.5 KiB
Rust

use kaptan_config::{
Config, ConfigEnum, ConfigValue, ConfigViolation, FieldId, FieldWriteError, ValidationRule,
};
#[derive(Config)]
struct LocalSection {
#[config(id = 1, key = "local.first", unit = "count", range(min = 1, max = 9))]
first: u32,
#[config(id = 2, key = "local.second", unit = "count", range(min = 1, max = 9))]
second: u32,
}
#[derive(Config)]
#[config(post = Self::validate_order)]
struct OrderedSection {
#[config(
id = 3,
key = "ordered.minimum",
unit = "count",
range(min = 0, max = 9)
)]
minimum: u32,
#[config(
id = 4,
key = "ordered.maximum",
unit = "count",
range(min = 0, max = 9)
)]
maximum: u32,
}
impl OrderedSection {
fn validate_order(&self) -> Result<(), ConfigViolation> {
if self.minimum > self.maximum {
return Err(ConfigViolation::related(
FieldId::new(3),
FieldId::new(4),
ValidationRule::Inconsistent,
));
}
Ok(())
}
}
#[derive(Config)]
struct Aggregate {
local: LocalSection,
ordered: OrderedSection,
}
#[derive(Config)]
struct BoardOwnedSection {
#[config(
id = 10,
key = "board.endpoint",
unit = "id",
range(min = 1, max = 255)
)]
endpoint: u32,
}
#[derive(Config)]
struct BoardOwnedAggregate {
endpoint: BoardOwnedSection,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ConfigEnum)]
#[repr(u8)]
enum TestMode {
Quiet = 1,
Loud = 3,
}
#[derive(Config)]
struct CompactSection {
#[config(id = 20, key = "compact.endpoint", unit = "id", range(min = 1))]
endpoint: u8,
#[config(enum, id = 21, key = "compact.mode", unit = "mode")]
mode: TestMode,
#[config(id = 22, key = "compact.port", unit = "id", range(min = 1))]
port: u16,
}
#[test]
fn generated_section_owns_every_declared_scalar_path() {
let mut section = LocalSection {
first: 2,
second: 4,
};
assert_eq!(LocalSection::FIELD_COUNT, 2);
assert_eq!(
section.field_value(FieldId::new(2)),
Some(ConfigValue::U32(4))
);
section
.set_field(FieldId::new(2), ConfigValue::U32(8))
.unwrap();
assert_eq!(
section.field_value(FieldId::new(2)),
Some(ConfigValue::U32(8))
);
assert_eq!(
section.set_field(FieldId::new(2), ConfigValue::U32(0)),
Err(FieldWriteError::Violation(ConfigViolation::field(
FieldId::new(2),
ValidationRule::BelowMinimum,
)))
);
assert_eq!(
section.field_value(FieldId::new(2)),
Some(ConfigValue::U32(8))
);
}
#[test]
fn narrow_scalars_and_compact_enums_keep_native_types_and_generic_metadata() {
let mut section = CompactSection {
endpoint: 7,
mode: TestMode::Quiet,
port: 4861,
};
assert_eq!(CompactSection::SNAPSHOT_VALUE_BYTES, 1 + 1 + 2);
assert_eq!(
section.field_value(FieldId::new(20)),
Some(ConfigValue::U8(7))
);
assert_eq!(
CompactSection::DESCRIPTORS[0].minimum,
Some(ConfigValue::U8(1))
);
assert_eq!(CompactSection::DESCRIPTORS[0].maximum, None);
assert_eq!(
CompactSection::DESCRIPTORS[1].enum_options,
TestMode::CONFIG_ENUM_OPTIONS
);
assert_eq!(TestMode::Quiet.wire_value(), 1);
assert_eq!(TestMode::try_from_wire(3), Some(TestMode::Loud));
assert_eq!(TestMode::try_from_wire(2), None);
section
.set_field(FieldId::new(21), ConfigValue::U8(3))
.unwrap();
assert_eq!(section.mode, TestMode::Loud);
assert!(matches!(
section.set_field(FieldId::new(21), ConfigValue::U8(2)),
Err(FieldWriteError::Violation(issue))
if issue.rule() == ValidationRule::InvalidEnumValue
));
assert!(matches!(
section.set_field(FieldId::new(20), ConfigValue::U8(0)),
Err(FieldWriteError::Violation(issue))
if issue.rule() == ValidationRule::BelowMinimum
));
assert_eq!(
section.field_value(FieldId::new(22)),
Some(ConfigValue::U16(4861))
);
assert_eq!(
CompactSection::DESCRIPTORS[2].kind,
kaptan_config::ValueKind::U16
);
}
#[test]
fn aggregate_routes_writes_and_defers_declared_post_validation() {
let mut aggregate = Aggregate {
local: LocalSection {
first: 2,
second: 4,
},
ordered: OrderedSection {
minimum: 1,
maximum: 2,
},
};
assert_eq!(Aggregate::FIELD_COUNT, 4);
assert_eq!(
aggregate.field_value(FieldId::new(2)),
Some(ConfigValue::U32(4))
);
aggregate
.set_field(FieldId::new(3), ConfigValue::U32(3))
.unwrap();
assert!(matches!(
aggregate.validate(),
Err(issue)
if issue.primary() == Some(FieldId::new(3))
&& issue.related_field() == Some(FieldId::new(4))
&& issue.rule() == ValidationRule::Inconsistent
));
aggregate
.set_field(FieldId::new(4), ConfigValue::U32(3))
.unwrap();
aggregate.validate().unwrap();
}
#[test]
fn aggregate_derives_its_flattened_descriptor_order() {
assert_eq!(Aggregate::SNAPSHOT_VALUE_BYTES, 4 * 4);
let descriptors: Vec<_> = (0..Aggregate::FIELD_COUNT)
.map(|index| Aggregate::descriptor(index).unwrap())
.collect();
assert_eq!(
descriptors
.iter()
.map(|descriptor| descriptor.id.get())
.collect::<Vec<_>>(),
[1, 2, 3, 4]
);
assert!(Aggregate::descriptor(Aggregate::FIELD_COUNT).is_none());
}
#[test]
fn factory_values_are_owned_outside_the_generated_schema() {
let mut aggregate = BoardOwnedAggregate {
endpoint: BoardOwnedSection { endpoint: 42 },
};
assert_eq!(BoardOwnedAggregate::FIELD_COUNT, 1);
assert_eq!(
aggregate.field_value(FieldId::new(10)),
Some(ConfigValue::U32(42))
);
aggregate
.set_field(FieldId::new(10), ConfigValue::U32(43))
.unwrap();
aggregate.validate().unwrap();
}
#[derive(Config)]
struct DuplicateChildren {
first: LocalSection,
second: LocalSection,
}
#[derive(Config)]
struct TransparentRoot {
local: LocalSection,
}
#[derive(Config)]
struct FirstKeyOwner {
#[config(id = 30, key = "shared.key", unit = "count")]
value: u32,
}
#[derive(Config)]
struct SecondKeyOwner {
#[config(id = 31, key = "shared.key", unit = "count")]
value: u32,
}
#[derive(Config)]
struct DuplicateKeysAcrossChildren {
first: FirstKeyOwner,
second: SecondKeyOwner,
}
#[test]
fn schema_fingerprint_is_generated_and_one_child_wrappers_are_transparent() {
assert_eq!(TransparentRoot::SCHEMA_ID, LocalSection::SCHEMA_ID);
assert_ne!(Aggregate::SCHEMA_ID, LocalSection::SCHEMA_ID);
}
#[test]
fn recursive_tree_rejects_duplicate_field_ids() {
let duplicate = DuplicateChildren {
first: LocalSection {
first: 2,
second: 4,
},
second: LocalSection {
first: 2,
second: 4,
},
};
assert!(matches!(
duplicate.validate(),
Err(issue) if issue.rule() == ValidationRule::Inconsistent
));
}
#[test]
fn recursive_tree_rejects_duplicate_display_keys() {
let duplicate = DuplicateKeysAcrossChildren {
first: FirstKeyOwner { value: 1 },
second: SecondKeyOwner { value: 2 },
};
assert!(matches!(
duplicate.validate(),
Err(issue) if issue.rule() == ValidationRule::Inconsistent
));
}