diff --git a/src/global_properties.rs b/src/global_properties.rs index af36149d..a41453ba 100644 --- a/src/global_properties.rs +++ b/src/global_properties.rs @@ -36,6 +36,7 @@ where Arc::new( |context: &mut Context, name, value| -> Result<(), IxaError> { let val: T::Value = serde_json::from_value(value)?; + T::validate(&val)?; if context.get_global_property_value(T::new()).is_some() { return Err(IxaError::IxaError(format!("Duplicate property {name}"))); } @@ -59,9 +60,10 @@ fn get_global_property(name: &String) -> Option> { /// Defines a global property with the following parameters: /// * `$global_property`: Name for the identifier type of the global property /// * `$value`: The type of the property's value +/// * `$validate`: A function (or closure) that checks the validity of the property (optional) #[macro_export] macro_rules! define_global_property { - ($global_property:ident, $value:ty) => { + ($global_property:ident, $value:ty, $validate: expr) => { #[derive(Copy, Clone)] pub struct $global_property; @@ -71,6 +73,10 @@ macro_rules! define_global_property { fn new() -> Self { $global_property } + + fn validate(val: &$value) -> Result<(), IxaError> { + $validate(val) + } } paste::paste! { @@ -84,6 +90,10 @@ macro_rules! define_global_property { } } }; + + ($global_property: ident, $value: ty) => { + define_global_property!($global_property, $value, |_| { Ok(()) }); + }; } /// Global properties are not mutable and represent variables that are required @@ -92,6 +102,8 @@ pub trait GlobalProperty: Any { type Value: Any; fn new() -> Self; + #[allow(clippy::missing_errors_doc)] + fn validate(value: &Self::Value) -> Result<(), IxaError>; } pub use define_global_property; @@ -329,7 +341,7 @@ mod test { .join("tests/data/global_properties_missing.json"); match context.load_global_properties(&path) { Err(IxaError::IxaError(msg)) => { - assert_eq!(msg, "No global property: ixa.Property3"); + assert_eq!(msg, "No global property: ixa.PropertyUnknown"); } _ => panic!("Unexpected error type"), } @@ -360,4 +372,36 @@ mod test { _ => panic!("Unexpected error type"), } } + + #[derive(Deserialize)] + pub struct Property3Type { + field_int: u32, + } + define_global_property!(Property3, Property3Type, |v: &Property3Type| { + match v.field_int { + 0 => Ok(()), + _ => Err(IxaError::IxaError(format!( + "Illegal value for `field_int`: {}", + v.field_int + ))), + } + }); + #[test] + fn validate_property_success() { + let mut context = Context::new(); + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/data/global_properties_valid.json"); + context.load_global_properties(&path).unwrap(); + } + + #[test] + fn validate_property_failure() { + let mut context = Context::new(); + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/data/global_properties_invalid.json"); + assert!(matches!( + context.load_global_properties(&path), + Err(IxaError::IxaError(_)) + )); + } } diff --git a/tests/data/global_properties_invalid.json b/tests/data/global_properties_invalid.json new file mode 100644 index 00000000..280c7dc0 --- /dev/null +++ b/tests/data/global_properties_invalid.json @@ -0,0 +1,5 @@ +{ + "ixa.Property3": { + "field_int": 42 + } +} diff --git a/tests/data/global_properties_missing.json b/tests/data/global_properties_missing.json index 2b4344de..ed85d233 100644 --- a/tests/data/global_properties_missing.json +++ b/tests/data/global_properties_missing.json @@ -1,5 +1,5 @@ { - "ixa.Property3": { + "ixa.PropertyUnknown": { "field_int": 1, "field_str": "test" } diff --git a/tests/data/global_properties_valid.json b/tests/data/global_properties_valid.json new file mode 100644 index 00000000..66aff725 --- /dev/null +++ b/tests/data/global_properties_valid.json @@ -0,0 +1,5 @@ +{ + "ixa.Property3": { + "field_int": 0 + } +}