use std::collections::HashMap;
use crate::error::ValidationError;
use crate::model::{DataType, Edit, Id, Op, PropertyValue, Value};
#[derive(Debug, Clone, Default)]
pub struct SchemaContext {
properties: HashMap<Id, DataType>,
}
impl SchemaContext {
pub fn new() -> Self {
Self::default()
}
pub fn add_property(&mut self, id: Id, data_type: DataType) {
self.properties.insert(id, data_type);
}
pub fn get_property_type(&self, id: &Id) -> Option<DataType> {
self.properties.get(id).copied()
}
}
pub fn validate_edit(edit: &Edit, schema: &SchemaContext) -> Result<(), ValidationError> {
for op in &edit.ops {
match op {
Op::CreateEntity(ce) => {
validate_property_values(&ce.values, schema)?;
}
Op::UpdateEntity(ue) => {
validate_property_values(&ue.set_properties, schema)?;
}
_ => {}
}
}
Ok(())
}
fn validate_property_values(
values: &[PropertyValue],
schema: &SchemaContext,
) -> Result<(), ValidationError> {
for pv in values {
if let Some(expected_type) = schema.get_property_type(&pv.property) {
let actual_type = pv.value.data_type();
if expected_type != actual_type {
return Err(ValidationError::TypeMismatch {
property: pv.property,
expected: expected_type,
});
}
}
}
Ok(())
}
pub fn validate_value(value: &Value) -> Option<&'static str> {
value.validate()
}
pub fn validate_position(pos: &str) -> Result<(), &'static str> {
crate::model::validate_position(pos)
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use super::*;
use crate::model::CreateEntity;
#[test]
fn test_validate_type_mismatch() {
let mut schema = SchemaContext::new();
schema.add_property([1u8; 16], DataType::Integer);
let edit = Edit {
id: [0u8; 16],
name: Cow::Borrowed(""),
authors: vec![],
created_at: 0,
ops: vec![Op::CreateEntity(CreateEntity {
id: [2u8; 16],
values: vec![PropertyValue {
property: [1u8; 16],
value: Value::Text {
value: Cow::Owned("not an int".to_string()),
language: None,
},
}],
context: None,
})],
};
let result = validate_edit(&edit, &schema);
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
}
#[test]
fn test_validate_type_match() {
let mut schema = SchemaContext::new();
schema.add_property([1u8; 16], DataType::Integer);
let edit = Edit {
id: [0u8; 16],
name: Cow::Borrowed(""),
authors: vec![],
created_at: 0,
ops: vec![Op::CreateEntity(CreateEntity {
id: [2u8; 16],
values: vec![PropertyValue {
property: [1u8; 16],
value: Value::Integer { value: 42, unit: None },
}],
context: None,
})],
};
let result = validate_edit(&edit, &schema);
assert!(result.is_ok());
}
#[test]
fn test_validate_unknown_property() {
let schema = SchemaContext::new();
let edit = Edit {
id: [0u8; 16],
name: Cow::Borrowed(""),
authors: vec![],
created_at: 0,
ops: vec![Op::CreateEntity(CreateEntity {
id: [2u8; 16],
values: vec![PropertyValue {
property: [99u8; 16], value: Value::Text {
value: Cow::Owned("test".to_string()),
language: None,
},
}],
context: None,
})],
};
let result = validate_edit(&edit, &schema);
assert!(result.is_ok());
}
}