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
12pub 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 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 }
107 }
108 if valid {
123 Ok(true)
124 } else {
125 Err(Error::Validation(errors))
126 }
127}
128
129fn 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 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.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 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 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 (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 (_, 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 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}
355fn 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
397fn rule_add_attr_if_not_exist(ast: &OCAAst, command_to_validate: Command) -> Result<bool, Error> {
407 let mut errors = Vec::new();
408 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 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 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(_) => {} 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 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 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(_) => {} 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 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 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 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}