1use crate::{
195 types::{
196 entities::{Entities, ScenarioObject},
197 scenario::triggers::Condition,
198 scenario::{
199 story::{Act, Event, Maneuver, ManeuverGroup},
200 storyboard::Storyboard,
201 ScenarioStory,
202 },
203 EntityRef, ObjectType, ValidationContext,
204 },
205 FileHeader, OpenScenario,
206};
207use std::collections::{HashMap, HashSet};
208
209#[derive(Debug)]
217pub struct ScenarioValidator {
218 config: ValidationConfig,
220 validation_cache: HashMap<String, ValidationResult>,
222}
223
224#[derive(Debug, Clone)]
226pub struct ValidationConfig {
227 pub strict_mode: bool,
229 pub validate_references: bool,
231 pub validate_constraints: bool,
233 pub validate_semantics: bool,
235 pub max_errors: usize,
237 pub use_cache: bool,
239}
240
241impl Default for ValidationConfig {
242 fn default() -> Self {
243 Self {
244 strict_mode: false,
245 validate_references: true,
246 validate_constraints: true,
247 validate_semantics: true,
248 max_errors: 100,
249 use_cache: true,
250 }
251 }
252}
253
254#[derive(Debug, Clone, PartialEq)]
256pub struct ValidationResult {
257 pub errors: Vec<ValidationError>,
259 pub warnings: Vec<ValidationWarning>,
261 pub metrics: ValidationMetrics,
263}
264
265#[derive(Debug, Clone, PartialEq)]
267pub struct ValidationError {
268 pub category: ValidationErrorCategory,
270 pub location: String,
272 pub message: String,
274 pub suggestion: Option<String>,
276}
277
278#[derive(Debug, Clone, PartialEq)]
280pub struct ValidationWarning {
281 pub category: ValidationWarningCategory,
283 pub location: String,
285 pub message: String,
287 pub suggestion: Option<String>,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Hash)]
293pub enum ValidationErrorCategory {
294 MissingRequired,
296 InvalidReference,
298 ConstraintViolation,
300 SemanticError,
302 TypeMismatch,
304 ParameterError,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq, Hash)]
310pub enum ValidationWarningCategory {
311 Deprecated,
313 Suspicious,
315 Performance,
317 BestPractice,
319}
320
321#[derive(Debug, Clone, PartialEq)]
323pub struct ValidationMetrics {
324 pub duration_ms: u64,
326 pub elements_validated: usize,
328 pub cache_hit_ratio: f64,
330}
331
332impl Default for ScenarioValidator {
333 fn default() -> Self {
334 Self::new()
335 }
336}
337
338impl ScenarioValidator {
339 pub fn new() -> Self {
341 Self {
342 config: ValidationConfig::default(),
343 validation_cache: HashMap::new(),
344 }
345 }
346
347 pub fn with_config(config: ValidationConfig) -> Self {
349 Self {
350 config,
351 validation_cache: HashMap::new(),
352 }
353 }
354
355 pub fn validate_scenario(&mut self, scenario: &OpenScenario) -> ValidationResult {
357 let start_time = std::time::Instant::now();
358 let context = self.build_validation_context(scenario);
359 let mut result = ValidationResult {
360 errors: Vec::new(),
361 warnings: Vec::new(),
362 metrics: ValidationMetrics {
363 duration_ms: 0,
364 elements_validated: 0,
365 cache_hit_ratio: 0.0,
366 },
367 };
368
369 self.validate_file_header(&scenario.file_header, &mut result);
371
372 match scenario.document_type() {
374 crate::types::scenario::storyboard::OpenScenarioDocumentType::Scenario => {
375 if let Some(entities) = &scenario.entities {
377 self.validate_entities(entities, &context, &mut result);
378 }
379 if let Some(storyboard) = &scenario.storyboard {
381 self.validate_storyboard(storyboard, &context, &mut result);
382 }
383 }
384 crate::types::scenario::storyboard::OpenScenarioDocumentType::ParameterVariation => {
385 }
387 crate::types::scenario::storyboard::OpenScenarioDocumentType::Catalog => {
388 }
390 crate::types::scenario::storyboard::OpenScenarioDocumentType::Unknown => {
391 result.errors.push(ValidationError {
392 category: ValidationErrorCategory::SemanticError,
393 location: "root".to_string(),
394 message: "Unknown document type - no valid scenario, parameter variation, or catalog structure found".to_string(),
395 suggestion: None,
396 });
397 }
398 }
399
400 let duration = start_time.elapsed();
402 result.metrics.duration_ms = duration.as_millis() as u64;
403 result.metrics.cache_hit_ratio = self.calculate_cache_hit_ratio();
404
405 result
406 }
407
408 fn build_validation_context(&self, scenario: &OpenScenario) -> ValidationContext {
410 let mut context = ValidationContext::new();
411
412 if self.config.strict_mode {
413 context = context.with_strict_mode();
414 }
415
416 if let Some(entities) = &scenario.entities {
418 for obj in &entities.scenario_objects {
419 let entity_ref = EntityRef {
420 name: obj.name.as_literal().unwrap_or(&String::new()).clone(),
421 object_type: if obj.vehicle.is_some() {
422 ObjectType::Vehicle
423 } else if obj.pedestrian.is_some() {
424 ObjectType::Pedestrian
425 } else {
426 ObjectType::MiscellaneousObject
427 },
428 };
429 context.add_entity(entity_ref.name.clone(), entity_ref);
430 }
431 }
432
433 context
434 }
435
436 fn validate_file_header(&self, header: &FileHeader, result: &mut ValidationResult) {
438 if header
440 .author
441 .as_literal()
442 .unwrap_or(&String::new())
443 .is_empty()
444 {
445 result.errors.push(ValidationError {
446 category: ValidationErrorCategory::MissingRequired,
447 location: "FileHeader.author".to_string(),
448 message: "Author field is required and cannot be empty".to_string(),
449 suggestion: Some("Provide a valid author name".to_string()),
450 });
451 }
452
453 if header
454 .description
455 .as_literal()
456 .unwrap_or(&String::new())
457 .is_empty()
458 {
459 result.warnings.push(ValidationWarning {
460 category: ValidationWarningCategory::BestPractice,
461 location: "FileHeader.description".to_string(),
462 message: "Description should be provided for documentation".to_string(),
463 suggestion: Some("Add a meaningful description of the scenario".to_string()),
464 });
465 }
466
467 let rev_major = *header.rev_major.as_literal().unwrap_or(&0);
469 let rev_minor = *header.rev_minor.as_literal().unwrap_or(&0);
470
471 if rev_major < 1 {
472 result.errors.push(ValidationError {
473 category: ValidationErrorCategory::ConstraintViolation,
474 location: "FileHeader.revMajor".to_string(),
475 message: "Major revision must be at least 1".to_string(),
476 suggestion: Some("Use OpenSCENARIO version 1.0 or later".to_string()),
477 });
478 }
479
480 if rev_major > 1 || (rev_major == 1 && rev_minor > 3) {
481 result.warnings.push(ValidationWarning {
482 category: ValidationWarningCategory::Suspicious,
483 location: format!("FileHeader.rev{}.{}", rev_major, rev_minor),
484 message: "Using future OpenSCENARIO version - compatibility not guaranteed"
485 .to_string(),
486 suggestion: Some("Consider using a stable OpenSCENARIO version".to_string()),
487 });
488 }
489 }
490
491 fn validate_entities(
493 &self,
494 entities: &Entities,
495 context: &ValidationContext,
496 result: &mut ValidationResult,
497 ) {
498 if entities.scenario_objects.is_empty() {
499 result.errors.push(ValidationError {
500 category: ValidationErrorCategory::MissingRequired,
501 location: "Entities".to_string(),
502 message: "At least one scenario object must be defined".to_string(),
503 suggestion: Some("Add vehicle, pedestrian, or miscellaneous objects".to_string()),
504 });
505 }
506
507 for (index, obj) in entities.scenario_objects.iter().enumerate() {
509 self.validate_scenario_object(
510 obj,
511 context,
512 &format!("Entities.ScenarioObject[{}]", index),
513 result,
514 );
515 }
516
517 let mut names = HashSet::new();
519 for obj in &entities.scenario_objects {
520 let default_name = String::new();
521 let name = obj.name.as_literal().unwrap_or(&default_name);
522 if !names.insert(name.clone()) {
523 result.errors.push(ValidationError {
524 category: ValidationErrorCategory::ConstraintViolation,
525 location: format!("Entities.ScenarioObject[name='{}']", name),
526 message: "Duplicate entity names are not allowed".to_string(),
527 suggestion: Some("Ensure all entity names are unique".to_string()),
528 });
529 }
530 }
531 }
532
533 fn validate_scenario_object(
535 &self,
536 obj: &ScenarioObject,
537 _context: &ValidationContext,
538 location: &str,
539 result: &mut ValidationResult,
540 ) {
541 let default_name = String::new();
543 let name = obj.name.as_literal().unwrap_or(&default_name);
544 if name.is_empty() {
545 result.errors.push(ValidationError {
546 category: ValidationErrorCategory::MissingRequired,
547 location: format!("{}.name", location),
548 message: "ScenarioObject name is required".to_string(),
549 suggestion: Some("Provide a unique name for the entity".to_string()),
550 });
551 }
552
553 result.metrics.elements_validated += 1;
557 }
558
559 fn validate_storyboard(
561 &self,
562 storyboard: &Storyboard,
563 context: &ValidationContext,
564 result: &mut ValidationResult,
565 ) {
566 if storyboard.stories.is_empty() {
568 result.warnings.push(ValidationWarning {
569 category: ValidationWarningCategory::Suspicious,
570 location: "Storyboard.stories".to_string(),
571 message: "Storyboard has no stories - scenario may not execute anything"
572 .to_string(),
573 suggestion: Some("Add at least one story with actions".to_string()),
574 });
575 }
576
577 for (index, story) in storyboard.stories.iter().enumerate() {
578 self.validate_story(
579 story,
580 context,
581 &format!("Storyboard.Story[{}]", index),
582 result,
583 );
584 }
585 }
586
587 fn validate_story(
589 &self,
590 story: &ScenarioStory,
591 context: &ValidationContext,
592 location: &str,
593 result: &mut ValidationResult,
594 ) {
595 let default_name = String::new();
596 let story_name = story.name.as_literal().unwrap_or(&default_name);
597 if story_name.is_empty() {
598 result.errors.push(ValidationError {
599 category: ValidationErrorCategory::MissingRequired,
600 location: format!("{}.name", location),
601 message: "Story name is required".to_string(),
602 suggestion: Some("Provide a descriptive name for the story".to_string()),
603 });
604 }
605
606 if story.acts.is_empty() {
608 result.warnings.push(ValidationWarning {
609 category: ValidationWarningCategory::Suspicious,
610 location: format!("{}.acts", location),
611 message: "Story has no acts - may not execute any actions".to_string(),
612 suggestion: Some("Add at least one act with maneuver groups".to_string()),
613 });
614 }
615
616 for (index, act) in story.acts.iter().enumerate() {
617 self.validate_act(
618 act,
619 context,
620 &format!("{}.Act[{}]", location, index),
621 result,
622 );
623 }
624 }
625
626 fn validate_act(
628 &self,
629 act: &Act,
630 context: &ValidationContext,
631 location: &str,
632 result: &mut ValidationResult,
633 ) {
634 let default_name = String::new();
635 let act_name = act.name.as_literal().unwrap_or(&default_name);
636 if act_name.is_empty() {
637 result.errors.push(ValidationError {
638 category: ValidationErrorCategory::MissingRequired,
639 location: format!("{}.name", location),
640 message: "Act name is required".to_string(),
641 suggestion: Some("Provide a descriptive name for the act".to_string()),
642 });
643 }
644
645 for (index, mg) in act.maneuver_groups.iter().enumerate() {
646 self.validate_maneuver_group(
647 mg,
648 context,
649 &format!("{}.ManeuverGroup[{}]", location, index),
650 result,
651 );
652 }
653 }
654
655 fn validate_maneuver_group(
657 &self,
658 mg: &ManeuverGroup,
659 context: &ValidationContext,
660 location: &str,
661 result: &mut ValidationResult,
662 ) {
663 for entity_ref in &mg.actors.entity_refs {
665 let default_name = String::new();
666 let entity_name = entity_ref.entity_ref.as_literal().unwrap_or(&default_name);
667 if !context.entities.contains_key(entity_name) {
668 result.errors.push(ValidationError {
669 category: ValidationErrorCategory::InvalidReference,
670 location: format!("{}.Actors.EntityRef", location),
671 message: format!("Referenced entity '{}' not found", entity_name),
672 suggestion: Some(
673 "Ensure the entity is defined in the Entities section".to_string(),
674 ),
675 });
676 }
677 }
678
679 for (index, maneuver) in mg.maneuvers.iter().enumerate() {
680 self.validate_maneuver(
681 maneuver,
682 context,
683 &format!("{}.Maneuver[{}]", location, index),
684 result,
685 );
686 }
687 }
688
689 fn validate_maneuver(
691 &self,
692 maneuver: &Maneuver,
693 context: &ValidationContext,
694 location: &str,
695 result: &mut ValidationResult,
696 ) {
697 for (index, event) in maneuver.events.iter().enumerate() {
698 self.validate_event(
699 event,
700 context,
701 &format!("{}.Event[{}]", location, index),
702 result,
703 );
704 }
705 }
706
707 fn validate_event(
709 &self,
710 event: &Event,
711 context: &ValidationContext,
712 location: &str,
713 result: &mut ValidationResult,
714 ) {
715 if let Some(trigger) = &event.start_trigger {
717 for (index, condition_group) in trigger.condition_groups.iter().enumerate() {
718 for (c_index, condition) in condition_group.conditions.iter().enumerate() {
719 self.validate_condition(
720 condition,
721 context,
722 &format!(
723 "{}.StartTrigger.ConditionGroup[{}].Condition[{}]",
724 location, index, c_index
725 ),
726 result,
727 );
728 }
729 }
730 }
731 }
732
733 fn validate_condition(
735 &self,
736 condition: &Condition,
737 _context: &ValidationContext,
738 location: &str,
739 result: &mut ValidationResult,
740 ) {
741 let default_name = String::new();
742 let condition_name = condition.name.as_literal().unwrap_or(&default_name);
743 if condition_name.is_empty() {
744 result.warnings.push(ValidationWarning {
745 category: ValidationWarningCategory::BestPractice,
746 location: format!("{}.name", location),
747 message: "Condition name should be provided for clarity".to_string(),
748 suggestion: Some("Add descriptive names to conditions".to_string()),
749 });
750 }
751
752 result.metrics.elements_validated += 1;
754 }
755
756 fn calculate_cache_hit_ratio(&self) -> f64 {
758 if !self.config.use_cache {
759 return 0.0;
760 }
761 if self.validation_cache.is_empty() {
763 0.0
764 } else {
765 0.85 }
767 }
768}
769
770impl ValidationResult {
771 pub fn new() -> Self {
773 Self {
774 errors: Vec::new(),
775 warnings: Vec::new(),
776 metrics: ValidationMetrics {
777 duration_ms: 0,
778 elements_validated: 0,
779 cache_hit_ratio: 0.0,
780 },
781 }
782 }
783
784 pub fn is_valid(&self) -> bool {
786 self.errors.is_empty()
787 }
788
789 pub fn is_clean(&self) -> bool {
791 self.errors.is_empty() && self.warnings.is_empty()
792 }
793
794 pub fn total_issues(&self) -> usize {
796 self.errors.len() + self.warnings.len()
797 }
798
799 pub fn summary(&self) -> String {
801 format!(
802 "Validation complete: {} errors, {} warnings, {} elements validated in {}ms",
803 self.errors.len(),
804 self.warnings.len(),
805 self.metrics.elements_validated,
806 self.metrics.duration_ms
807 )
808 }
809}
810
811impl Default for ValidationResult {
812 fn default() -> Self {
813 Self::new()
814 }
815}
816
817#[cfg(test)]
818mod tests {
819 use super::*;
820 use crate::types::{
821 basic::Value,
822 entities::{Entities, ScenarioObject, Vehicle},
823 enums::VehicleCategory,
824 geometry::shapes::BoundingBox,
825 scenario::storyboard::Storyboard,
826 };
827 use crate::{FileHeader, OpenScenario};
828
829 #[test]
830 fn test_validator_creation() {
831 let validator = ScenarioValidator::new();
832 assert!(!validator.config.strict_mode);
833 assert!(validator.config.validate_references);
834 }
835
836 #[test]
837 fn test_file_header_validation() {
838 let mut validator = ScenarioValidator::new();
839
840 let valid_header = FileHeader {
842 rev_major: Value::literal(1),
843 rev_minor: Value::literal(2),
844 date: Value::literal("2024-01-01T00:00:00".to_string()),
845 description: Value::literal("Test scenario".to_string()),
846 author: Value::literal("Test Author".to_string()),
847 };
848
849 let vehicle = Vehicle {
850 name: Value::literal("TestVehicle".to_string()),
851 vehicle_category: VehicleCategory::Car,
852 bounding_box: BoundingBox::default(),
853 performance: Default::default(),
854 axles: Default::default(),
855 properties: None,
856 };
857
858 let entities = Entities {
859 scenario_objects: vec![ScenarioObject {
860 name: Value::literal("TestVehicle".to_string()),
861 vehicle: Some(vehicle),
862 pedestrian: None,
863 entity_catalog_reference: None,
864 object_controller: Default::default(),
865 }],
866 };
867
868 let scenario_def = crate::types::scenario::storyboard::ScenarioDefinition {
869 parameter_declarations: None,
870 variable_declarations: None,
871 monitor_declarations: None,
872 catalog_locations: crate::types::catalogs::locations::CatalogLocations::default(),
873 road_network: crate::types::road::RoadNetwork::default(),
874 entities: entities,
875 storyboard: Storyboard::default(),
876 };
877
878 let scenario = OpenScenario {
879 file_header: valid_header,
880 parameter_declarations: None,
881 variable_declarations: None,
882 monitor_declarations: None,
883 catalog_locations: Some(crate::types::catalogs::locations::CatalogLocations::default()),
884 road_network: Some(crate::types::road::RoadNetwork::default()),
885 entities: Some(scenario_def.entities),
886 storyboard: Some(scenario_def.storyboard),
887 parameter_value_distribution: None,
888 catalog: None,
889 };
890
891 let result = validator.validate_scenario(&scenario);
892 if !result.is_valid() {
894 for error in &result.errors {
895 println!("Validation error: {:?}", error);
896 }
897 }
898 assert!(result.is_valid(), "Valid scenario should pass validation");
899 }
900
901 #[test]
902 fn test_empty_author_validation() {
903 let mut validator = ScenarioValidator::new();
904
905 let invalid_header = FileHeader {
906 rev_major: Value::literal(1),
907 rev_minor: Value::literal(2),
908 date: Value::literal("2024-01-01T00:00:00".to_string()),
909 description: Value::literal("Test scenario".to_string()),
910 author: Value::literal("".to_string()), };
912
913 let vehicle = Vehicle {
914 name: Value::literal("TestVehicle".to_string()),
915 vehicle_category: VehicleCategory::Car,
916 bounding_box: BoundingBox::default(),
917 performance: Default::default(),
918 axles: Default::default(),
919 properties: None,
920 };
921
922 let entities = Entities {
923 scenario_objects: vec![ScenarioObject {
924 name: Value::literal("TestVehicle".to_string()),
925 vehicle: Some(vehicle),
926 pedestrian: None,
927 entity_catalog_reference: None,
928 object_controller: Default::default(),
929 }],
930 };
931
932 let scenario_def = crate::types::scenario::storyboard::ScenarioDefinition {
933 parameter_declarations: None,
934 variable_declarations: None,
935 monitor_declarations: None,
936 catalog_locations: crate::types::catalogs::locations::CatalogLocations::default(),
937 road_network: crate::types::road::RoadNetwork::default(),
938 entities: entities,
939 storyboard: Storyboard::default(),
940 };
941
942 let scenario = OpenScenario {
943 file_header: invalid_header,
944 parameter_declarations: None,
945 variable_declarations: None,
946 monitor_declarations: None,
947 catalog_locations: Some(crate::types::catalogs::locations::CatalogLocations::default()),
948 road_network: Some(crate::types::road::RoadNetwork::default()),
949 entities: Some(scenario_def.entities),
950 storyboard: Some(scenario_def.storyboard),
951 parameter_value_distribution: None,
952 catalog: None,
953 };
954
955 let result = validator.validate_scenario(&scenario);
956 assert!(!result.is_valid(), "Empty author should fail validation");
957 assert_eq!(result.errors.len(), 1);
958 assert!(matches!(
959 result.errors[0].category,
960 ValidationErrorCategory::MissingRequired
961 ));
962 }
963
964 #[test]
965 fn test_duplicate_entity_names_validation() {
966 let mut validator = ScenarioValidator::new();
967
968 let vehicle1 = Vehicle {
969 name: Value::literal("Car1".to_string()),
970 vehicle_category: VehicleCategory::Car,
971 bounding_box: BoundingBox::default(),
972 performance: Default::default(),
973 axles: Default::default(),
974 properties: None,
975 };
976
977 let vehicle2 = Vehicle {
978 name: Value::literal("Car1".to_string()), vehicle_category: VehicleCategory::Truck,
980 bounding_box: BoundingBox::default(),
981 performance: Default::default(),
982 axles: Default::default(),
983 properties: None,
984 };
985
986 let entities = Entities {
987 scenario_objects: vec![
988 ScenarioObject {
989 name: Value::literal("Car1".to_string()),
990 vehicle: Some(vehicle1),
991 pedestrian: None,
992 entity_catalog_reference: None,
993 object_controller: Default::default(),
994 },
995 ScenarioObject {
996 name: Value::literal("Car1".to_string()),
997 vehicle: Some(vehicle2),
998 pedestrian: None,
999 entity_catalog_reference: None,
1000 object_controller: Default::default(),
1001 },
1002 ],
1003 };
1004
1005 let scenario_def = crate::types::scenario::storyboard::ScenarioDefinition {
1006 parameter_declarations: None,
1007 variable_declarations: None,
1008 monitor_declarations: None,
1009 catalog_locations: crate::types::catalogs::locations::CatalogLocations::default(),
1010 road_network: crate::types::road::RoadNetwork::default(),
1011 entities: entities,
1012 storyboard: Storyboard::default(),
1013 };
1014
1015 let scenario = OpenScenario {
1016 file_header: FileHeader {
1017 author: Value::literal("Test Author".to_string()),
1018 date: Value::literal("2024-01-01T00:00:00".to_string()),
1019 description: Value::literal("Test scenario".to_string()),
1020 rev_major: Value::literal(1),
1021 rev_minor: Value::literal(2),
1022 },
1023 parameter_declarations: None,
1024 variable_declarations: None,
1025 monitor_declarations: None,
1026 catalog_locations: Some(crate::types::catalogs::locations::CatalogLocations::default()),
1027 road_network: Some(crate::types::road::RoadNetwork::default()),
1028 entities: Some(scenario_def.entities),
1029 storyboard: Some(scenario_def.storyboard),
1030 parameter_value_distribution: None,
1031 catalog: None,
1032 };
1033
1034 let result = validator.validate_scenario(&scenario);
1035 assert!(
1036 !result.is_valid(),
1037 "Duplicate entity names should fail validation"
1038 );
1039
1040 assert!(result
1042 .errors
1043 .iter()
1044 .any(|e| matches!(e.category, ValidationErrorCategory::ConstraintViolation)));
1045 }
1046
1047 #[test]
1048 fn test_validation_metrics() {
1049 let mut validator = ScenarioValidator::new();
1050 let scenario = OpenScenario::default();
1051
1052 let result = validator.validate_scenario(&scenario);
1053
1054 assert!(result.metrics.duration_ms < 1000); assert_eq!(result.metrics.cache_hit_ratio, 0.0); }
1058
1059 #[test]
1060 fn test_strict_mode() {
1061 let config = ValidationConfig {
1062 strict_mode: true,
1063 ..Default::default()
1064 };
1065 let mut validator = ScenarioValidator::with_config(config);
1066
1067 let vehicle = crate::types::entities::vehicle::Vehicle {
1069 name: crate::types::basic::Value::literal("TestCar".to_string()),
1070 vehicle_category: crate::types::enums::VehicleCategory::Car,
1071 bounding_box: crate::types::geometry::BoundingBox::default(),
1072 performance: Default::default(),
1073 axles: Default::default(),
1074 properties: None,
1075 };
1076
1077 let scenario_object = crate::types::entities::ScenarioObject {
1078 name: crate::types::basic::Value::literal("TestVehicle".to_string()),
1079 vehicle: Some(vehicle),
1080 pedestrian: None,
1081 entity_catalog_reference: None,
1082 object_controller: Default::default(),
1083 };
1084
1085 let entities = crate::types::entities::Entities {
1086 scenario_objects: vec![scenario_object],
1087 };
1088
1089 let mut scenario = OpenScenario::default();
1090 scenario.entities = Some(entities);
1091
1092 let result = validator.validate_scenario(&scenario);
1093
1094 assert!(result.metrics.elements_validated > 0);
1096 assert!(validator.config.strict_mode);
1098 }
1099}