use crate::error::{ValidationError, ValidationResult};
use crate::schema::types::{AttributeDefinition, AttributeType};
use serde_json::Value;
use std::any::Any;
use std::fmt::Debug;
pub trait ValueObject: Debug + Send + Sync {
fn attribute_type(&self) -> AttributeType;
fn attribute_name(&self) -> &str;
fn to_json(&self) -> ValidationResult<Value>;
fn validate_against_schema(&self, definition: &AttributeDefinition) -> ValidationResult<()>;
fn as_json_value(&self) -> Value;
fn supports_definition(&self, definition: &AttributeDefinition) -> bool;
fn clone_boxed(&self) -> Box<dyn ValueObject>;
fn as_any(&self) -> &dyn Any;
}
pub trait SchemaConstructible: ValueObject + Sized {
fn from_schema_and_value(
definition: &AttributeDefinition,
value: &Value,
) -> ValidationResult<Self>;
fn can_construct_from(definition: &AttributeDefinition) -> bool;
fn constructor_priority() -> u8 {
50 }
}
pub trait ExtensionAttribute: ValueObject {
fn schema_uri(&self) -> &str;
fn extension_namespace(&self) -> &str;
fn validate_extension_rules(&self) -> ValidationResult<()>;
}
pub trait CompositeValidator {
fn validate_composite(&self, objects: &[Box<dyn ValueObject>]) -> ValidationResult<()>;
fn dependent_attributes(&self) -> Vec<String>;
fn applies_to(&self, attribute_names: &[String]) -> bool;
}
#[derive(Default)]
pub struct ValueObjectRegistry {
constructors: Vec<Box<dyn ValueObjectConstructor>>,
composite_validators: Vec<Box<dyn CompositeValidator>>,
}
pub trait ValueObjectConstructor: Send + Sync {
fn try_construct(
&self,
definition: &AttributeDefinition,
value: &Value,
) -> Option<ValidationResult<Box<dyn ValueObject>>>;
fn priority(&self) -> u8;
fn description(&self) -> &str;
}
impl ValueObjectRegistry {
pub fn new() -> Self {
let mut registry = Self::default();
registry.register_default_constructors();
registry
}
pub fn register_constructor(&mut self, constructor: Box<dyn ValueObjectConstructor>) {
self.constructors.push(constructor);
self.constructors
.sort_by(|a, b| b.priority().cmp(&a.priority()));
}
pub fn register_composite_validator(&mut self, validator: Box<dyn CompositeValidator>) {
self.composite_validators.push(validator);
}
pub fn create_value_object(
&self,
definition: &AttributeDefinition,
value: &Value,
) -> ValidationResult<Box<dyn ValueObject>> {
for constructor in &self.constructors {
if let Some(result) = constructor.try_construct(definition, value) {
return result;
}
}
Err(ValidationError::UnsupportedAttributeType {
attribute: definition.name.clone(),
type_name: format!("{:?}", definition.data_type),
})
}
pub fn validate_composite_rules(
&self,
objects: &[Box<dyn ValueObject>],
) -> ValidationResult<()> {
let attribute_names: Vec<String> = objects
.iter()
.map(|obj| obj.attribute_name().to_string())
.collect();
for validator in &self.composite_validators {
if validator.applies_to(&attribute_names) {
validator.validate_composite(objects)?;
}
}
Ok(())
}
pub fn has_constructors(&self) -> bool {
!self.constructors.is_empty()
}
fn register_default_constructors(&mut self) {
}
}
#[macro_export]
macro_rules! impl_value_object {
(
$type:ty,
attribute_type: $attr_type:expr,
attribute_name: $attr_name:expr
) => {
impl $crate::resource::value_objects::value_object_trait::ValueObject for $type {
fn attribute_type(&self) -> $crate::schema::types::AttributeType {
$attr_type
}
fn attribute_name(&self) -> &str {
$attr_name
}
fn to_json(&self) -> $crate::error::ValidationResult<serde_json::Value> {
Ok(serde_json::to_value(self)?)
}
fn validate_against_schema(
&self,
definition: &$crate::schema::types::AttributeDefinition,
) -> $crate::error::ValidationResult<()> {
if definition.data_type != self.attribute_type() {
return Err($crate::error::ValidationError::InvalidAttributeType(
definition.name.clone(),
format!("{:?}", definition.data_type),
format!("{:?}", self.attribute_type()),
));
}
Ok(())
}
fn as_json_value(&self) -> serde_json::Value {
self.to_json().unwrap_or(serde_json::Value::Null)
}
fn supports_definition(
&self,
definition: &$crate::schema::types::AttributeDefinition,
) -> bool {
definition.data_type == self.attribute_type()
&& definition.name == self.attribute_name()
}
fn clone_boxed(
&self,
) -> Box<dyn $crate::resource::value_objects::value_object_trait::ValueObject> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
};
}
pub struct GenericValueObjectConstructor<T> {
_phantom: std::marker::PhantomData<T>,
}
impl<T> GenericValueObjectConstructor<T> where T: SchemaConstructible + 'static {}
impl<T> ValueObjectConstructor for GenericValueObjectConstructor<T>
where
T: SchemaConstructible + 'static,
{
fn try_construct(
&self,
definition: &AttributeDefinition,
value: &Value,
) -> Option<ValidationResult<Box<dyn ValueObject>>> {
if T::can_construct_from(definition) {
Some(
T::from_schema_and_value(definition, value)
.map(|obj| Box::new(obj) as Box<dyn ValueObject>),
)
} else {
None
}
}
fn priority(&self) -> u8 {
T::constructor_priority()
}
fn description(&self) -> &str {
std::any::type_name::<T>()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schema::types::{AttributeType, Mutability, Uniqueness};
#[derive(Debug, Clone)]
struct MockValueObject {
name: String,
value: String,
}
impl ValueObject for MockValueObject {
fn attribute_type(&self) -> AttributeType {
AttributeType::String
}
fn attribute_name(&self) -> &str {
&self.name
}
fn to_json(&self) -> ValidationResult<Value> {
Ok(Value::String(self.value.clone()))
}
fn validate_against_schema(
&self,
_definition: &AttributeDefinition,
) -> ValidationResult<()> {
Ok(())
}
fn as_json_value(&self) -> Value {
Value::String(self.value.clone())
}
fn supports_definition(&self, definition: &AttributeDefinition) -> bool {
definition.data_type == AttributeType::String
}
fn clone_boxed(&self) -> Box<dyn ValueObject> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[test]
fn test_value_object_trait() {
let obj = MockValueObject {
name: "test".to_string(),
value: "value".to_string(),
};
assert_eq!(obj.attribute_type(), AttributeType::String);
assert_eq!(obj.attribute_name(), "test");
assert_eq!(obj.as_json_value(), Value::String("value".to_string()));
}
#[test]
fn test_value_object_registry() {
let registry = ValueObjectRegistry::new();
assert_eq!(registry.constructors.len(), 0); assert_eq!(registry.composite_validators.len(), 0);
}
#[test]
fn test_validate_against_schema() {
let obj = MockValueObject {
name: "test".to_string(),
value: "value".to_string(),
};
let definition = AttributeDefinition {
name: "test".to_string(),
data_type: AttributeType::String,
multi_valued: false,
required: false,
case_exact: false,
mutability: Mutability::ReadWrite,
uniqueness: Uniqueness::None,
canonical_values: vec![],
sub_attributes: vec![],
returned: None,
};
assert!(obj.validate_against_schema(&definition).is_ok());
}
}