use crate::builder::{BuilderError, BuilderResult};
use crate::types::scenario::storyboard::OpenScenario;
use crate::types::ValidationContext;
use std::collections::HashMap;
#[derive(Default)]
pub struct BuilderValidationContext {
base_context: ValidationContext,
builder_rules: Vec<Box<dyn BuilderValidationRule>>,
entity_refs: HashMap<String, String>,
parameters: HashMap<String, String>,
}
impl std::fmt::Debug for BuilderValidationContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BuilderValidationContext")
.field("base_context", &self.base_context)
.field(
"builder_rules",
&format!("{} rules", self.builder_rules.len()),
)
.field("entity_refs", &self.entity_refs)
.field("parameters", &self.parameters)
.finish()
}
}
impl BuilderValidationContext {
pub fn new() -> Self {
Self {
base_context: ValidationContext::new(),
builder_rules: Vec::new(),
entity_refs: HashMap::new(),
parameters: HashMap::new(),
}
}
pub fn add_rule(mut self, rule: Box<dyn BuilderValidationRule>) -> Self {
self.builder_rules.push(rule);
self
}
pub fn register_entity(&mut self, name: &str, entity_type: &str) {
self.entity_refs
.insert(name.to_string(), entity_type.to_string());
}
pub fn register_parameter(&mut self, name: &str, value: &str) {
self.parameters.insert(name.to_string(), value.to_string());
}
pub fn validate_entity_ref(&self, entity_ref: &str) -> BuilderResult<()> {
if !self.entity_refs.contains_key(entity_ref) {
return Err(BuilderError::invalid_entity_ref(
entity_ref,
&self.entity_refs.keys().cloned().collect::<Vec<_>>(),
));
}
Ok(())
}
pub fn validate_parameter_ref(&self, param_ref: &str) -> BuilderResult<()> {
if !self.parameters.contains_key(param_ref) {
return Err(BuilderError::validation_error(&format!(
"Parameter '{}' not found. Available parameters: {}",
param_ref,
self.parameters
.keys()
.cloned()
.collect::<Vec<_>>()
.join(", ")
)));
}
Ok(())
}
pub fn validate_scenario(&self, scenario: &OpenScenario) -> BuilderResult<()> {
for rule in &self.builder_rules {
rule.validate(scenario, self)?;
}
Ok(())
}
pub fn entities(&self) -> &HashMap<String, String> {
&self.entity_refs
}
pub fn parameters(&self) -> &HashMap<String, String> {
&self.parameters
}
}
pub trait BuilderValidationRule {
fn validate(
&self,
scenario: &OpenScenario,
context: &BuilderValidationContext,
) -> BuilderResult<()>;
fn name(&self) -> &str;
fn description(&self) -> &str;
}
#[derive(Debug)]
pub struct EntityReferenceValidationRule;
impl BuilderValidationRule for EntityReferenceValidationRule {
fn validate(
&self,
scenario: &OpenScenario,
context: &BuilderValidationContext,
) -> BuilderResult<()> {
if let Some(storyboard) = &scenario.storyboard {
for story in &storyboard.stories {
for act in &story.acts {
for maneuver_group in &act.maneuver_groups {
for entity_ref in &maneuver_group.actors.entity_refs {
let entity_name = entity_ref.entity_ref.to_string();
context.validate_entity_ref(&entity_name)?;
}
}
}
}
}
Ok(())
}
fn name(&self) -> &str {
"EntityReferenceValidation"
}
fn description(&self) -> &str {
"Validates that all entity references in actions and conditions exist"
}
}
#[derive(Debug)]
pub struct ParameterReferenceValidationRule;
impl BuilderValidationRule for ParameterReferenceValidationRule {
fn validate(
&self,
scenario: &OpenScenario,
context: &BuilderValidationContext,
) -> BuilderResult<()> {
if let Some(param_decls) = &scenario.parameter_declarations {
for param in ¶m_decls.parameter_declarations {
let param_name = param.name.to_string();
if !context.parameters.contains_key(¶m_name) {
}
}
}
Ok(())
}
fn name(&self) -> &str {
"ParameterReferenceValidation"
}
fn description(&self) -> &str {
"Validates that all parameter references are properly declared"
}
}
#[derive(Debug)]
pub struct CatalogReferenceValidationRule;
impl BuilderValidationRule for CatalogReferenceValidationRule {
fn validate(
&self,
scenario: &OpenScenario,
_context: &BuilderValidationContext,
) -> BuilderResult<()> {
if let Some(entities) = &scenario.entities {
let has_catalog_refs = entities
.scenario_objects
.iter()
.any(|obj| obj.entity_catalog_reference.is_some());
if has_catalog_refs && scenario.catalog_locations.is_none() {
return Err(BuilderError::validation_error(
"Catalog references found but no catalog locations specified",
));
}
}
Ok(())
}
fn name(&self) -> &str {
"CatalogReferenceValidation"
}
fn description(&self) -> &str {
"Validates that catalog locations are specified when catalog references are used"
}
}
#[derive(Debug)]
pub struct StoryboardStructureValidationRule;
impl BuilderValidationRule for StoryboardStructureValidationRule {
fn validate(
&self,
scenario: &OpenScenario,
_context: &BuilderValidationContext,
) -> BuilderResult<()> {
if let Some(storyboard) = &scenario.storyboard {
for story in &storyboard.stories {
if story.acts.is_empty() {
return Err(BuilderError::validation_error(&format!(
"Story '{}' has no acts",
story.name.to_string()
)));
}
for act in &story.acts {
if act.maneuver_groups.is_empty() {
return Err(BuilderError::validation_error(&format!(
"Act '{}' has no maneuver groups",
act.name.to_string()
)));
}
for maneuver_group in &act.maneuver_groups {
if maneuver_group.maneuvers.is_empty() {
return Err(BuilderError::validation_error(&format!(
"Maneuver group '{}' has no maneuvers",
maneuver_group.name.to_string()
)));
}
}
}
}
}
Ok(())
}
fn name(&self) -> &str {
"StoryboardStructureValidation"
}
fn description(&self) -> &str {
"Validates the hierarchical structure of the storyboard"
}
}
#[derive(Default)]
pub struct ValidationContextBuilder {
rules: Vec<Box<dyn BuilderValidationRule>>,
entities: HashMap<String, String>,
parameters: HashMap<String, String>,
}
impl std::fmt::Debug for ValidationContextBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ValidationContextBuilder")
.field("rules", &format!("{} rules", self.rules.len()))
.field("entities", &self.entities)
.field("parameters", &self.parameters)
.finish()
}
}
impl ValidationContextBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_standard_rules(mut self) -> Self {
self.rules.push(Box::new(EntityReferenceValidationRule));
self.rules.push(Box::new(ParameterReferenceValidationRule));
self.rules.push(Box::new(CatalogReferenceValidationRule));
self.rules.push(Box::new(StoryboardStructureValidationRule));
self
}
pub fn with_rule(mut self, rule: Box<dyn BuilderValidationRule>) -> Self {
self.rules.push(rule);
self
}
pub fn with_entity(mut self, name: &str, entity_type: &str) -> Self {
self.entities
.insert(name.to_string(), entity_type.to_string());
self
}
pub fn with_parameter(mut self, name: &str, value: &str) -> Self {
self.parameters.insert(name.to_string(), value.to_string());
self
}
pub fn build(self) -> BuilderValidationContext {
let mut context = BuilderValidationContext::new();
context = context
.add_rule(Box::new(EntityReferenceValidationRule))
.add_rule(Box::new(ParameterReferenceValidationRule))
.add_rule(Box::new(CatalogReferenceValidationRule))
.add_rule(Box::new(StoryboardStructureValidationRule));
for (name, entity_type) in self.entities {
context.register_entity(&name, &entity_type);
}
for (name, value) in self.parameters {
context.register_parameter(&name, &value);
}
context
}
}
pub trait BuilderValidatable {
fn validate_with_context(&self, context: &BuilderValidationContext) -> BuilderResult<()>;
}
impl BuilderValidatable for OpenScenario {
fn validate_with_context(&self, context: &BuilderValidationContext) -> BuilderResult<()> {
context.validate_scenario(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_validation_context() {
let mut context = BuilderValidationContext::new();
context.register_entity("ego", "vehicle");
context.register_entity("target", "vehicle");
context.register_parameter("speed", "30.0");
context.register_parameter("lane", "1");
assert!(context.validate_entity_ref("ego").is_ok());
assert!(context.validate_entity_ref("target").is_ok());
assert!(context.validate_entity_ref("unknown").is_err());
assert!(context.validate_parameter_ref("speed").is_ok());
assert!(context.validate_parameter_ref("lane").is_ok());
assert!(context.validate_parameter_ref("unknown").is_err());
}
#[test]
fn test_validation_context_builder() {
let context = ValidationContextBuilder::new()
.with_standard_rules()
.with_entity("ego", "vehicle")
.with_entity("target", "pedestrian")
.with_parameter("speed", "25.0")
.with_parameter("distance", "100.0")
.build();
assert_eq!(context.entities().len(), 2);
assert_eq!(context.parameters().len(), 2);
assert!(context.validate_entity_ref("ego").is_ok());
assert!(context.validate_parameter_ref("speed").is_ok());
}
#[test]
fn test_validation_rules() {
let rule = EntityReferenceValidationRule;
assert_eq!(rule.name(), "EntityReferenceValidation");
assert!(!rule.description().is_empty());
let rule = ParameterReferenceValidationRule;
assert_eq!(rule.name(), "ParameterReferenceValidation");
assert!(!rule.description().is_empty());
let rule = CatalogReferenceValidationRule;
assert_eq!(rule.name(), "CatalogReferenceValidation");
assert!(!rule.description().is_empty());
let rule = StoryboardStructureValidationRule;
assert_eq!(rule.name(), "StoryboardStructureValidation");
assert!(!rule.description().is_empty());
}
}