Skip to main content

oca_ast/
validator.rs

1use crate::{
2    ast::{Command, CommandType, NestedAttrType, NestedValue, OCAAst, ObjectKind, OverlayContent},
3    errors::Error,
4    utils::is_valid_language_code,
5};
6use indexmap::{IndexMap, IndexSet, indexmap};
7use log::debug;
8use overlay_file::{ElementType, KeyType};
9
10type CaptureAttributes = IndexMap<String, NestedAttrType>;
11
12/// Validates given commands against existing valid OCA AST
13///
14/// # Arguments
15/// * `ast` - valid OCA AST
16/// * `command` - Command to validate against AST
17///
18/// # Returns
19/// * `Result<bool, Error>` - Result of validation
20pub trait Validator {
21    fn validate(&self, ast: &OCAAst, command: Command) -> Result<bool, Error>;
22}
23
24pub struct OCAValidator {}
25
26impl Validator for OCAValidator {
27    fn validate(&self, ast: &OCAAst, command: Command) -> Result<bool, Error> {
28        let mut errors = Vec::new();
29        let mut valid = true;
30        match ast.version.as_str() {
31            "2.0.0" | "1.0.0" => {
32                let version_validator = validate(ast, command);
33                if version_validator.is_err() {
34                    valid = false;
35                    errors.push(version_validator.err().unwrap());
36                }
37            }
38            "" => {
39                valid = false;
40                errors.push(Error::MissingVersion());
41            }
42            _ => {
43                valid = false;
44                errors.push(Error::InvalidVersion(ast.version.to_string()));
45            }
46        }
47        if valid {
48            Ok(true)
49        } else {
50            Err(Error::Validation(errors))
51        }
52    }
53}
54
55fn validate(ast: &OCAAst, command: Command) -> Result<bool, Error> {
56    // General rules for ocafile
57    // Cannot remove if does not exist on stack
58    // Cannot modify if does not exist on stack
59    // Cannot add if already exists on stack
60    // Attributes must have valid type
61    let mut valid = true;
62    let mut errors = Vec::new();
63    match (&command.kind, &command.object_kind) {
64        (CommandType::Add, ObjectKind::CaptureBase(_)) => {
65            match rule_add_attr_if_not_exist(ast, command) {
66                Ok(result) => {
67                    if !result {
68                        valid = result;
69                    }
70                }
71                Err(error) => {
72                    valid = false;
73                    errors.push(error);
74                }
75            }
76        }
77        (CommandType::Remove, ObjectKind::CaptureBase(_)) => {
78            match rule_remove_attr_if_exist(ast, command) {
79                Ok(result) => {
80                    if !result {
81                        valid = result;
82                    }
83                }
84                Err(error) => {
85                    valid = false;
86                    errors.push(error);
87                }
88            }
89        }
90        (CommandType::Add, ObjectKind::Overlay(_)) => {
91            match validate_against_overlay_def(ast, &command) {
92                Ok(result) => {
93                    if !result {
94                        valid = result;
95                    }
96                }
97                Err(error) => {
98                    valid = false;
99                    errors.push(error);
100                }
101            }
102        }
103
104        _ => {
105            // TODO: Add support for FROM, MODIFY with combination of different object kinds
106        }
107    }
108    // CommandType::Modify => {
109    //     match rule_modify_if_exist(ast, command) {
110    //         Ok(result) => {
111    //             if !result {
112    //                 valid = result;
113    //             }
114    //         }
115    //         Err(error) => {
116    //             valid = false;
117    //             errors.push(error);
118    //         }
119    //     }
120    // }
121
122    if valid {
123        Ok(true)
124    } else {
125        Err(Error::Validation(errors))
126    }
127}
128
129/// Check rules of overlay definition against the provided command
130fn validate_against_overlay_def(ast: &OCAAst, command: &Command) -> Result<bool, Error> {
131    let mut errors = Vec::new();
132
133    if let ObjectKind::Overlay(overlay_content) = &command.object_kind {
134        let cb_attrs = extract_attributes(ast);
135        validate_overlay(overlay_content, &cb_attrs, &mut errors);
136    }
137
138    if errors.is_empty() {
139        Ok(true)
140    } else {
141        Err(Error::Validation(errors))
142    }
143}
144
145fn validate_overlay(
146    overlay_content: &OverlayContent,
147    capture_base_attrs: &CaptureAttributes,
148    errors: &mut Vec<Error>,
149) {
150    let new_properties = IndexMap::new();
151    let properties = overlay_content
152        .properties
153        .as_ref()
154        .unwrap_or(&new_properties);
155
156    let mut found_elements = IndexSet::new();
157
158    // Validate property names against the overlay definition
159    for (prop_name, prop_value) in properties.iter() {
160        if let Some(element) = overlay_content
161            .overlay_def
162            .elements
163            .iter()
164            .find(|e| e.name == *prop_name)
165            .or_else(|| {
166                overlay_content
167                    .overlay_def
168                    .elements
169                    .iter()
170                    .find(|e| e.name.is_empty())
171            })
172        {
173            found_elements.insert(prop_name.clone());
174            match is_valid_property_type(prop_value, &element.values) {
175                Ok(true) => {
176                    // If element type is AttrNames, validate attribute names against the capture base attributes
177                    if element.keys == KeyType::AttrNames {
178                        validate_attr_names(prop_value, capture_base_attrs, prop_name, errors);
179                    }
180                }
181                Ok(false) => {
182                    errors.push(Error::InvalidPropertyValue(format!(
183                        "Property '{}' in {} has an invalid value type",
184                        prop_name,
185                        overlay_content.overlay_def.get_full_name(),
186                    )));
187                }
188                Err(err_msg) => {
189                    errors.push(Error::InvalidPropertyValue(format!(
190                        "Property '{}': {} in {}",
191                        prop_name,
192                        err_msg,
193                        overlay_content.overlay_def.get_full_name(),
194                    )));
195                }
196            }
197        } else {
198            errors.push(Error::InvalidProperty(format!(
199                "Property '{}' is not allowed by the overlay definition {}",
200                prop_name,
201                overlay_content.overlay_def.get_full_name(),
202            )));
203        }
204    }
205    // Check for missing required properties
206    for element in &overlay_content.overlay_def.elements {
207        if !element.name.is_empty() && !found_elements.contains(&element.name) {
208            errors.push(Error::MissingRequiredAttribute(
209                element.name.clone(),
210                overlay_content.overlay_def.get_full_name(),
211            ));
212        }
213    }
214
215    // Ensure unique keys are present in the overlay properties
216    for key in &overlay_content.overlay_def.unique_keys {
217        if !properties.contains_key(key) {
218            errors.push(Error::MissingRequiredAttribute(
219                key.clone(),
220                overlay_content.overlay_def.get_full_name(),
221            ));
222        }
223    }
224}
225
226fn validate_attr_names(
227    prop_value: &NestedValue,
228    capture_base_attributes: &CaptureAttributes,
229    prop_name: &str,
230    errors: &mut Vec<Error>,
231) {
232    if let NestedValue::Object(attr_names) = prop_value {
233        for attr_name in attr_names.keys() {
234            if !capture_base_attributes.contains_key(attr_name) {
235                errors.push(Error::InvalidProperty(format!(
236                    "Attribute '{}' in '{}' is not present in the capture base",
237                    attr_name, prop_name
238                )));
239            }
240        }
241    } else {
242        errors.push(Error::InvalidPropertyValue(format!(
243            "Property '{}' should be an object for AttrNames type",
244            prop_name
245        )));
246    }
247}
248
249fn is_valid_property_type(
250    value: &NestedValue,
251    expected_type: &ElementType,
252) -> Result<bool, String> {
253    match (value, expected_type) {
254        (NestedValue::Array(_), ElementType::Array(_)) => Ok(true),
255        (NestedValue::Reference(_), ElementType::Ref) => Ok(true),
256        (NestedValue::Value(_), ElementType::Any) => Ok(true),
257        (NestedValue::Value(_), ElementType::Text) => Ok(true),
258        (NestedValue::Value(_), ElementType::Binary) => Ok(true),
259        (NestedValue::Value(s), ElementType::Lang) => {
260            if is_valid_language_code(s) {
261                Ok(true)
262            } else {
263                Err(format!("Invalid language code: '{}'", s))
264            }
265        }
266        // Handle Object with Complex types - validate each value in the object against the complex types
267        (NestedValue::Object(object), ElementType::Complex(types)) => {
268            for (key, v) in object {
269                let mut any_valid = false;
270                let mut last_error = String::new();
271
272                for t in types {
273                    debug!(
274                        "Checking value {:?} for key '{}' against type {:?}",
275                        v, key, t
276                    );
277                    match is_valid_property_type(v, t) {
278                        Ok(true) => {
279                            any_valid = true;
280                            break;
281                        }
282                        Err(e) => {
283                            last_error = e;
284                            continue;
285                        }
286                        _ => continue,
287                    }
288                }
289
290                if !any_valid {
291                    return Err(format!(
292                        "No valid type found for value {:?} (key: '{}') in complex element: {:?}. Last error: {}",
293                        v, key, types, last_error
294                    ));
295                }
296            }
297            Ok(true)
298        }
299        // Handle non-Object values with Complex types
300        (_, ElementType::Complex(types)) => {
301            let mut any_valid = false;
302            let mut last_error = String::new();
303
304            for t in types {
305                debug!("Checking value {:?} against type {:?}", value, t);
306                match is_valid_property_type(value, t) {
307                    Ok(true) => {
308                        any_valid = true;
309                        break;
310                    }
311                    Err(e) => {
312                        last_error = e;
313                        continue;
314                    }
315                    _ => continue,
316                }
317            }
318
319            if any_valid {
320                Ok(true)
321            } else {
322                Err(format!(
323                    "No valid type found for value {:?} in complex element: {:?}. Last error: {}",
324                    value, types, last_error
325                ))
326            }
327        }
328        (NestedValue::Object(object), ElementType::Object(inner)) => {
329            if let Some(inner_def) = inner {
330                for (_, v) in object {
331                    is_valid_property_type(v, &inner_def.values)?;
332                }
333                Ok(true)
334            } else {
335                Err(format!(
336                    "No valid inner definition of the object {:?} found for complex type",
337                    value
338                ))
339            }
340        }
341        (NestedValue::Object(object), _) => {
342            // This case handles objects where the expected type is not Complex or Object
343            // It validates each value in the object against the expected type
344            for (_, v) in object {
345                is_valid_property_type(v, expected_type)?;
346            }
347            Ok(true)
348        }
349        _ => Err(format!(
350            "Mismatched value type: expected {:?}, got {:?}",
351            expected_type, value
352        )),
353    }
354}
355/// Check rule for remove command
356/// Rule would be valid if attributes which commands tries to remove exist in the stack
357///
358/// # Arguments
359/// * `ast` - valid OCA AST
360/// * `command` - Command to validate against AST
361///
362/// # Returns
363/// * `Result<bool, Error>` - Result of validation
364fn rule_remove_attr_if_exist(ast: &OCAAst, command_to_validate: Command) -> Result<bool, Error> {
365    let mut errors = Vec::new();
366
367    let attributes = extract_attributes(ast);
368
369    let content = command_to_validate.object_kind.capture_content();
370
371    match (
372        content,
373        content.as_ref().and_then(|c| c.attributes.as_ref()),
374    ) {
375        (Some(_content), Some(attrs_to_remove)) => {
376            let valid = attrs_to_remove
377                .keys()
378                .all(|key| attributes.contains_key(key));
379            if !valid {
380                errors.push(Error::InvalidOperation(
381                    "Cannot remove attribute if does not exists".to_string(),
382                ));
383            }
384        }
385        (None, None) => (),
386        (None, Some(_)) => (),
387        (Some(_), None) => (),
388    }
389
390    if errors.is_empty() {
391        Ok(true)
392    } else {
393        Err(Error::Validation(errors))
394    }
395}
396
397/// Check rule for add command
398/// Rule would be valid if attributes which commands tries to add do not exist in the stack
399///
400/// # Arguments
401/// * `ast` - valid OCA AST
402/// * `command` - Command to validate against AST
403///
404/// # Returns
405/// * `Result<bool, Error>` - Result of validation
406fn rule_add_attr_if_not_exist(ast: &OCAAst, command_to_validate: Command) -> Result<bool, Error> {
407    let mut errors = Vec::new();
408    // Create a list of all attributes ADDed and REMOVEd via commands and check if what left covers needs of new command
409    let default_attrs: IndexMap<String, NestedAttrType> = indexmap! {};
410
411    let attributes = extract_attributes(ast);
412
413    let content = command_to_validate.object_kind.capture_content();
414
415    match content {
416        Some(content) => {
417            let attrs_to_add = content.attributes.clone().unwrap_or(default_attrs);
418            debug!("attrs_to_add: {:?}", attrs_to_add);
419
420            let existing_keys: Vec<_> = attrs_to_add
421                .keys()
422                .filter(|key| attributes.contains_key(*key))
423                .collect();
424
425            if !existing_keys.is_empty() {
426                errors.push(Error::InvalidOperation(format!(
427                    "Cannot add attribute if already exists: {:?}",
428                    existing_keys
429                )));
430                Err(Error::Validation(errors))
431            } else {
432                Ok(true)
433            }
434        }
435        None => {
436            errors.push(Error::InvalidOperation(
437                "No attribtues specify to be added".to_string(),
438            ));
439            Err(Error::Validation(errors))
440        }
441    }
442}
443
444fn extract_attributes(ast: &OCAAst) -> CaptureAttributes {
445    let default_attrs: IndexMap<String, NestedAttrType> = indexmap! {};
446    let mut attributes: CaptureAttributes = indexmap! {};
447    for instruction in &ast.commands {
448        match (instruction.kind.clone(), instruction.object_kind.clone()) {
449            (CommandType::Remove, ObjectKind::CaptureBase(capture_content)) => {
450                let attrs = capture_content
451                    .attributes
452                    .as_ref()
453                    .unwrap_or(&default_attrs);
454                attributes.retain(|key, _value| !attrs.contains_key(key));
455            }
456            (CommandType::Add, ObjectKind::CaptureBase(capture_content)) => {
457                let attrs = capture_content
458                    .attributes
459                    .as_ref()
460                    .unwrap_or(&default_attrs);
461                attributes.extend(attrs.iter().map(|(k, v)| (k.clone(), v.clone())));
462            }
463            _ => {}
464        }
465    }
466    attributes
467}
468
469#[cfg(test)]
470mod tests {
471    use indexmap::indexmap;
472    use overlay_file::KeyType;
473
474    use super::*;
475    use crate::ast::{
476        AttributeType, CaptureContent, Command, CommandType, OCAAst, ObjectKind, RefValue,
477    };
478
479    #[test]
480    fn test_rule_remove_if_exist() {
481        let command = Command {
482            kind: CommandType::Add,
483            object_kind: ObjectKind::CaptureBase(CaptureContent {
484                attributes: Some(indexmap! {
485                    "name".to_string() => NestedAttrType::Value(AttributeType::Text),
486                    "documentType".to_string() => NestedAttrType::Value(AttributeType::Text),
487                    "photo".to_string() => NestedAttrType::Value(AttributeType::Binary),
488                }),
489            }),
490        };
491
492        let command2 = Command {
493            kind: CommandType::Add,
494            object_kind: ObjectKind::CaptureBase(CaptureContent {
495                attributes: Some(indexmap! {
496                    "issuer".to_string() => NestedAttrType::Value(AttributeType::Text),
497                    "last_name".to_string() => NestedAttrType::Value(AttributeType::Binary),
498                }),
499            }),
500        };
501
502        let remove_command = Command {
503            kind: CommandType::Remove,
504            object_kind: ObjectKind::CaptureBase(CaptureContent {
505                attributes: Some(indexmap! {
506                    "name".to_string() => NestedAttrType::Null,
507                    "documentType".to_string() => NestedAttrType::Null,
508                }),
509            }),
510        };
511
512        let add_command = Command {
513            kind: CommandType::Add,
514            object_kind: ObjectKind::CaptureBase(CaptureContent {
515                attributes: Some(indexmap! {
516                    "name".to_string() => NestedAttrType::Value(AttributeType::Text),
517                }),
518            }),
519        };
520
521        let valid_command = Command {
522            kind: CommandType::Remove,
523            object_kind: ObjectKind::CaptureBase(CaptureContent {
524                attributes: Some(indexmap! {
525                    "name".to_string() => NestedAttrType::Null,
526                    "issuer".to_string() => NestedAttrType::Null,
527                }),
528            }),
529        };
530
531        let invalid_command = Command {
532            kind: CommandType::Remove,
533            object_kind: ObjectKind::CaptureBase(CaptureContent {
534                attributes: Some(indexmap! {
535                    "documentType".to_string() => NestedAttrType::Null,
536                }),
537            }),
538        };
539
540        let mut ocaast = OCAAst::new();
541        ocaast.commands.push(command);
542        ocaast.commands.push(command2);
543        ocaast.commands.push(remove_command);
544        ocaast.commands.push(add_command);
545        let mut result = rule_remove_attr_if_exist(&ocaast, valid_command.clone());
546        assert!(result.is_ok());
547        ocaast.commands.push(invalid_command.clone());
548        result = rule_remove_attr_if_exist(&ocaast, invalid_command);
549        assert!(result.is_err());
550    }
551
552    #[test]
553    fn test_rule_add_if_not_exist() {
554        let command = Command {
555            kind: CommandType::Add,
556            object_kind: ObjectKind::CaptureBase(CaptureContent {
557                attributes: Some(indexmap! {
558                    "name".to_string() => NestedAttrType::Value(AttributeType::Text),
559                    "documentType".to_string() => NestedAttrType::Value(AttributeType::Text),
560                    "photo".to_string() => NestedAttrType::Value(AttributeType::Binary),
561                }),
562            }),
563        };
564
565        let command2 = Command {
566            kind: CommandType::Add,
567            object_kind: ObjectKind::CaptureBase(CaptureContent {
568                attributes: Some(indexmap! {
569                    "issuer".to_string() => NestedAttrType::Value(AttributeType::Text),
570                    "last_name".to_string() => NestedAttrType::Value(AttributeType::Binary),
571                }),
572            }),
573        };
574
575        let valid_command = Command {
576            kind: CommandType::Add,
577            object_kind: ObjectKind::CaptureBase(CaptureContent {
578                attributes: Some(indexmap! {
579                    "first_name".to_string() => NestedAttrType::Value(AttributeType::Text),
580                    "address".to_string() => NestedAttrType::Value(AttributeType::Text),
581                }),
582            }),
583        };
584
585        let invalid_command = Command {
586            kind: CommandType::Add,
587            object_kind: ObjectKind::CaptureBase(CaptureContent {
588                attributes: Some(indexmap! {
589                    "name".to_string() => NestedAttrType::Value(AttributeType::Text),
590                    "phone".to_string() => NestedAttrType::Value(AttributeType::Text),
591                }),
592            }),
593        };
594
595        let mut ocaast = OCAAst::new();
596        ocaast.commands.push(command);
597        ocaast.commands.push(command2);
598        let mut result = rule_add_attr_if_not_exist(&ocaast, valid_command.clone());
599        assert!(result.is_ok());
600        ocaast.commands.push(invalid_command.clone());
601        result = rule_add_attr_if_not_exist(&ocaast, invalid_command.clone());
602        assert!(result.is_err());
603    }
604
605    #[test]
606    fn test_validate_overlay_against_definition() {
607        use overlay_file::{ElementType, OverlayDef, OverlayElementDef};
608
609        let label_overlay_def = OverlayDef {
610            name: "Label".to_string(),
611            elements: vec![
612                OverlayElementDef {
613                    name: "attr_labels".to_string(),
614                    values: ElementType::Text,
615                    keys: KeyType::AttrNames,
616                },
617                OverlayElementDef {
618                    name: "language".to_string(),
619                    values: ElementType::Lang,
620                    keys: KeyType::None,
621                },
622            ],
623            namespace: Some("hcf".to_string()),
624            version: "2.0.0".to_string(),
625            unique_keys: Vec::new(),
626        };
627
628        let meta_overlay_def = OverlayDef {
629            name: "meta".to_string(),
630            elements: vec![
631                OverlayElementDef {
632                    name: "language".to_string(),
633                    values: ElementType::Lang,
634                    keys: KeyType::None,
635                },
636                OverlayElementDef {
637                    name: "description".to_string(),
638                    values: ElementType::Text,
639                    keys: KeyType::None,
640                },
641                OverlayElementDef {
642                    name: "name".to_string(),
643                    values: ElementType::Text,
644                    keys: KeyType::None,
645                },
646                // Empty name means that any name is allowed with specific types
647                OverlayElementDef {
648                    name: "".to_string(),
649                    values: ElementType::Text,
650                    keys: KeyType::None,
651                },
652            ],
653            namespace: Some("hcf".to_string()),
654            version: "2.0.0".to_string(),
655            unique_keys: Vec::new(),
656        };
657
658        let entry_overlay_def = OverlayDef {
659            name: "Entry".to_string(),
660            elements: vec![
661                OverlayElementDef {
662                    name: "attribute_entries".to_string(),
663                    values: ElementType::Complex(vec![
664                        ElementType::Ref,
665                        ElementType::Object(Some(Box::new(OverlayElementDef {
666                            name: "".to_string(),
667                            values: ElementType::Any,
668                            keys: KeyType::Text,
669                        }))),
670                    ]),
671                    keys: KeyType::AttrNames,
672                },
673                OverlayElementDef {
674                    name: "language".to_string(),
675                    values: ElementType::Lang,
676                    keys: KeyType::None,
677                },
678            ],
679            namespace: Some("hcf".to_string()),
680            version: "2.0.0".to_string(),
681            unique_keys: Vec::new(),
682        };
683
684        let entry_code_overlay_def = OverlayDef {
685            name: "Entry_Code".to_string(),
686            elements: vec![OverlayElementDef {
687                name: "attribute_entry_codes".to_string(),
688                values: ElementType::Complex(vec![ElementType::Ref, ElementType::Array(None)]),
689                keys: KeyType::AttrNames,
690            }],
691            namespace: Some("hcf".to_string()),
692            version: "2.0.0".to_string(),
693            unique_keys: Vec::new(),
694        };
695
696        let capture_base = Command {
697            kind: CommandType::Add,
698            object_kind: ObjectKind::CaptureBase(CaptureContent {
699                attributes: Some(indexmap! {
700                    "first_name".to_string() => NestedAttrType::Value(AttributeType::Text),
701                    "last_name".to_string() => NestedAttrType::Value(AttributeType::Text),
702                    "address".to_string() => NestedAttrType::Value(AttributeType::Text),
703                    "sex".to_string() => NestedAttrType::Value(AttributeType::Text),
704                }),
705            }),
706        };
707
708        let mut ocaast = OCAAst::new();
709        ocaast.commands.push(capture_base.clone());
710
711        // Test case 1: Valid overlay
712        let valid_overlay = Command {
713            kind: CommandType::Add,
714            object_kind: ObjectKind::Overlay(OverlayContent {
715                properties: Some(indexmap! {
716                    "attr_labels".to_string() => NestedValue::Object(indexmap! {
717                        "first_name".to_string() => NestedValue::Value("First name".to_string()),
718                        "last_name".to_string() => NestedValue::Value("Last name".to_string()),
719                    }),
720                    "language".to_string() => NestedValue::Value("en-UK".to_string()),
721                }),
722                overlay_def: label_overlay_def.clone(),
723            }),
724        };
725
726        match validate_against_overlay_def(&ocaast, &valid_overlay) {
727            Ok(_) => {} // success, continue
728            Err(Error::Validation(errors)) => {
729                panic!(
730                    "Valid overlay should pass validation, got errors: {:?}",
731                    errors
732                );
733            }
734            Err(e) => {
735                panic!("Unexpected error during validation: {:?}", e);
736            }
737        }
738
739        ocaast.commands.push(valid_overlay.clone());
740
741        // Test case 2: Invalid overlay (missing required field and wrong types )
742        let invalid_overlay_missing_field = Command {
743            kind: CommandType::Add,
744            object_kind: ObjectKind::Overlay(OverlayContent {
745                overlay_def: label_overlay_def.clone(),
746                properties: Some(indexmap! {
747                    "attr_labels".to_string() => NestedValue::Object(indexmap! {
748                        "address".to_string() => NestedValue::Reference(RefValue::Name("passport".to_string())),
749                    }),
750                    "lang".to_string() => NestedValue::Value("pl".to_string()),
751                }),
752            }),
753        };
754
755        let result = validate_against_overlay_def(&ocaast, &invalid_overlay_missing_field);
756        match result {
757            Ok(is_valid) => assert!(
758                !is_valid,
759                "Overlay with missing required field should fail validation"
760            ),
761            Err(Error::Validation(errors)) => {
762                assert_eq!(errors.len(), 3);
763                assert_eq!(
764                    errors[2].to_string(),
765                    "Missing required attribute language in Overlay: hcf:Label/2.0.0"
766                );
767                assert_eq!(
768                    errors[0].to_string(),
769                    "Invalid Property Value: Property 'attr_labels': Mismatched value type: expected Text, got Reference(Name(\"passport\")) in hcf:Label/2.0.0"
770                );
771                assert_eq!(
772                    errors[1].to_string(),
773                    "Invalid Property: Property 'lang' is not allowed by the overlay definition hcf:Label/2.0.0"
774                );
775            }
776            Err(e) => panic!("Unexpected error: {:?}", e),
777        }
778
779        // Test case 3: validate custom fileds and check their values
780        let meta_overlay = Command {
781            kind: CommandType::Add,
782            object_kind: ObjectKind::Overlay(OverlayContent {
783                overlay_def: meta_overlay_def.clone(),
784                properties: Some(indexmap! {
785                    "language".to_string() => NestedValue::Value("en-UK".to_string()),
786                    "description".to_string() => NestedValue::Value("Some description".to_string()),
787                    "name".to_string() => NestedValue::Value("Some name".to_string()),
788                    "custom1".to_string() => NestedValue::Value("Custom value 1".to_string()),
789                    "custom2".to_string() => NestedValue::Array(vec![NestedValue::Value("Custom value 2".to_string()), NestedValue::Value("Custom value 3".to_string())]),
790                }),
791            }),
792        };
793
794        match validate_against_overlay_def(&ocaast, &meta_overlay) {
795            Ok(_) => {} // success, continue
796            Err(Error::Validation(errors)) => {
797                assert_eq!(
798                    errors[0].to_string(),
799                    "Invalid Property Value: Property 'custom2': Mismatched value type: expected Text, got Array([Value(\"Custom value 2\"), Value(\"Custom value 3\")]) in hcf:meta/2.0.0"
800                );
801            }
802            Err(e) => panic!("Unexpected error: {:?}", e),
803        }
804
805        // Test case 4: validate complex types
806        let entry_code_overlay = Command {
807            kind: CommandType::Add,
808            object_kind: ObjectKind::Overlay(OverlayContent {
809                overlay_def: entry_code_overlay_def.clone(),
810                properties: Some(indexmap! {
811                    "attribute_entry_codes".to_string() => NestedValue::Object(indexmap! {
812                        "sex".to_string() => NestedValue::Array(vec![NestedValue::Value("Male".to_string()), NestedValue::Value("Female".to_string())]),
813                        "address".to_string() => NestedValue::Reference(RefValue::Name("adres".to_string())),
814                    }),
815                }),
816            }),
817        };
818
819        match validate_against_overlay_def(&ocaast, &entry_code_overlay) {
820            Ok(result) => assert!(result, "Entry code overlay should pass validation"),
821            Err(e) => panic!("Unexpected error: {:?}", e),
822        }
823
824        // Test case 5: validate complex types part 2 - refs
825        let entry_code_overlay = Command {
826            kind: CommandType::Add,
827            object_kind: ObjectKind::Overlay(OverlayContent {
828                overlay_def: entry_code_overlay_def.clone(),
829                properties: Some(indexmap! {
830                    "attribute_entry_codes".to_string() => NestedValue::Object(indexmap! {
831                        "sex".to_string() => NestedValue::Reference(RefValue::Name("nazwa".to_string())),
832                    }),
833                }),
834            }),
835        };
836
837        let result = validate_against_overlay_def(&ocaast, &entry_code_overlay);
838        match result {
839            Ok(is_valid) => assert!(is_valid, "Entry code overlay should pass validation"),
840            Err(e) => panic!("Unexpected error: {:?}", e),
841        }
842        // Test case 5: validate complex types: Object
843        let entry_overlay = Command {
844            kind: CommandType::Add,
845            object_kind: ObjectKind::Overlay(OverlayContent {
846                overlay_def: entry_overlay_def.clone(),
847                properties: Some(indexmap! {
848                    "attribute_entries".to_string() => NestedValue::Object(indexmap! {
849                        "sex".to_string() => NestedValue::Object(indexmap! {
850                            "M".to_string() => NestedValue::Value("male".to_string()),
851                            "F".to_string() => NestedValue::Value("female".to_string()),
852                        }),
853                    }),
854                    "language".to_string() => NestedValue::Value("en-UK".to_string()),
855                }),
856            }),
857        };
858
859        let result = validate_against_overlay_def(&ocaast, &entry_overlay);
860        match result {
861            Ok(is_valid) => assert!(is_valid, "Entry overlay should pass validation"),
862            Err(Error::Validation(errors)) => {
863                assert_eq!(errors.len(), 1);
864                assert_eq!(
865                    errors[0].to_string(),
866                    "Invalid Property Value: Property 'attribute_entries"
867                );
868            }
869            Err(e) => panic!("Unexpected error: {:?}", e),
870        }
871    }
872}