Skip to main content

openscenario_rs/types/
basic.rs

1//! Basic data types with expression and parameter support
2//!
3//! This file contains:
4//! - Value<T> enum for literals, parameters, and expressions (${param}, ${expr})
5//! - Implementation of all basic OpenSCENARIO types (String, Double, Boolean, etc.)
6//! - Parameter resolution logic and expression evaluation
7//! - Serde serialization/deserialization for XML attributes
8//! - Validation helpers for parameter names and expression syntax
9//!
10use crate::error::{Error, Result};
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12use std::collections::HashMap;
13use std::fmt;
14use std::str::FromStr;
15
16// Value enum that can hold either a literal value, a parameter reference, or an expression
17//
18// OpenSCENARIO supports parameter references using ${parameterName} syntax and
19// mathematical expressions using ${expression} syntax.
20// This enum allows us to represent both compile-time literals and runtime parameters/expressions.
21//
22// # Type Parameters
23//
24// * `T` - The type of the literal value. Must implement `Clone` for the `resolve()` method to work.
25//
26// # Note
27//
28// While the `Value<T>` enum itself doesn't require `T: Clone` at the struct level,
29// the `resolve()` method and other utility methods require `T: Clone`. Attempting to
30// call `resolve()` on a `Value<T>` where `T` doesn't implement `Clone` will result in
31// a compilation error.
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub enum Value<T> {
34    /// A literal value known at parse time
35    Literal(T),
36    /// A parameter reference that will be resolved at runtime
37    Parameter(String),
38    /// A mathematical expression that will be evaluated at runtime
39    Expression(String),
40}
41
42impl<T> Value<T>
43where
44    T: FromStr + Clone,
45    T::Err: std::fmt::Display,
46{
47    /// Resolve this value using the provided parameter map
48    pub fn resolve(&self, params: &HashMap<String, String>) -> Result<T> {
49        match self {
50            Value::Literal(value) => Ok(value.clone()),
51            Value::Parameter(param_name) => {
52                let param_value = params
53                    .get(param_name)
54                    .ok_or_else(|| Error::parameter_error(param_name, "parameter not found"))?;
55
56                param_value.parse::<T>().map_err(|e| {
57                    Error::parameter_error(
58                        param_name,
59                        &format!("failed to parse '{}': {}", param_value, e),
60                    )
61                })
62            }
63            Value::Expression(expr) => {
64                // For now, we'll treat expressions as parameters that need to be resolved
65                // In a full implementation, we would parse and evaluate the mathematical expression
66                let resolved_expr = resolve_expression::<T>(expr, params)?;
67                resolved_expr.parse::<T>().map_err(|e| {
68                    Error::parameter_error(
69                        expr,
70                        &format!(
71                            "failed to parse expression result '{}': {}",
72                            resolved_expr, e
73                        ),
74                    )
75                })
76            }
77        }
78    }
79
80    /// Get the literal value if this is a literal, otherwise None
81    #[inline]
82    pub fn as_literal(&self) -> Option<&T> {
83        match self {
84            Value::Literal(value) => Some(value),
85            Value::Parameter(_) => None,
86            Value::Expression(_) => None,
87        }
88    }
89
90    /// Get the parameter name if this is a parameter, otherwise None
91    #[inline]
92    pub fn as_parameter(&self) -> Option<&str> {
93        match self {
94            Value::Literal(_) => None,
95            Value::Parameter(name) => Some(name),
96            Value::Expression(_) => None,
97        }
98    }
99
100    /// Get the expression if this is an expression, otherwise None
101    #[inline]
102    pub fn as_expression(&self) -> Option<&str> {
103        match self {
104            Value::Literal(_) => None,
105            Value::Parameter(_) => None,
106            Value::Expression(expr) => Some(expr),
107        }
108    }
109}
110
111impl<T: Clone> Value<T> {
112    /// Create a literal value
113    #[inline]
114    pub fn literal(value: T) -> Self {
115        Value::Literal(value)
116    }
117
118    /// Create a parameter reference
119    #[inline]
120    pub fn parameter(name: String) -> Self {
121        Value::Parameter(name)
122    }
123
124    /// Create an expression
125    #[inline]
126    pub fn expression(expr: String) -> Self {
127        Value::Expression(expr)
128    }
129}
130
131// Custom serde implementation to handle ${param} and ${expression} syntax
132impl<'de, T> Deserialize<'de> for Value<T>
133where
134    T: Deserialize<'de> + FromStr,
135    T::Err: std::fmt::Display,
136{
137    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
138    where
139        D: Deserializer<'de>,
140    {
141        let s = String::deserialize(deserializer)?;
142
143        // Handle empty strings for Double type - return error for invalid empty values
144        if s.is_empty() && std::any::type_name::<T>().contains("f64") {
145            return Err(serde::de::Error::custom(
146                "Empty string is not a valid value for Double type",
147            ));
148        }
149
150        // Check if this is a parameter reference or expression
151        if s.starts_with("${") && s.ends_with('}') && s.len() > 3 {
152            let content = &s[2..s.len() - 1];
153            // Check if it's a simple parameter (no operators)
154            if is_valid_parameter_name(content) && !content.contains(|c| "+-*/%()".contains(c)) {
155                Ok(Value::Parameter(content.to_string()))
156            } else {
157                // Treat as expression
158                Ok(Value::Expression(content.to_string()))
159            }
160        } else if s.starts_with("$") && s.len() > 1 {
161            // Handle $param format (without curly braces)
162            let content = &s[1..];
163            if is_valid_parameter_name(content) && !content.contains(|c| "+-*/%()".contains(c)) {
164                Ok(Value::Parameter(content.to_string()))
165            } else {
166                // Not a valid parameter, treat as literal
167                match s.parse::<T>() {
168                    Ok(value) => Ok(Value::Literal(value)),
169                    Err(e) => Err(serde::de::Error::custom(format!(
170                        "Failed to parse '{}': {}",
171                        s, e
172                    ))),
173                }
174            }
175        } else {
176            // Try to parse as literal value
177            match s.parse::<T>() {
178                Ok(value) => Ok(Value::Literal(value)),
179                Err(e) => Err(serde::de::Error::custom(format!(
180                    "Failed to parse '{}': {}",
181                    s, e
182                ))),
183            }
184        }
185    }
186}
187
188impl<T> Serialize for Value<T>
189where
190    T: Serialize + fmt::Display,
191{
192    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
193    where
194        S: Serializer,
195    {
196        match self {
197            Value::Literal(value) => value.to_string().serialize(serializer),
198            Value::Parameter(name) => format!("${{{}}}", name).serialize(serializer),
199            Value::Expression(expr) => format!("${{{}}}", expr).serialize(serializer),
200        }
201    }
202}
203
204// Implement Display trait for Value<T> to enable use in println! and format! macros
205impl<T> fmt::Display for Value<T>
206where
207    T: fmt::Display,
208{
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        match self {
211            Value::Literal(value) => write!(f, "{}", value),
212            Value::Parameter(name) => write!(f, "${{{}}}", name),
213            Value::Expression(expr) => write!(f, "${{{}}}", expr),
214        }
215    }
216}
217
218// OpenSCENARIO basic type aliases
219pub type OSString = Value<String>;
220pub type Double = Value<f64>;
221pub type Int = Value<i32>;
222pub type UnsignedInt = Value<u32>;
223pub type UnsignedShort = Value<u16>;
224pub type Boolean = Value<bool>;
225
226pub type DateTime = Value<chrono::DateTime<chrono::Utc>>;
227
228
229
230/// Parse a parameter reference from a string
231///
232/// Returns the parameter name if the string matches ${paramName} pattern
233pub fn parse_parameter_reference(s: &str) -> Option<String> {
234    if s.starts_with("${") && s.ends_with('}') && s.len() > 3 {
235        let param_name = &s[2..s.len() - 1];
236        if is_valid_parameter_name(param_name) {
237            Some(param_name.to_string())
238        } else {
239            None
240        }
241    } else {
242        None
243    }
244}
245
246/// Check if a string is an expression (contains mathematical operators)
247#[inline]
248pub fn is_expression(s: &str) -> bool {
249    s.contains(|c| "+-*/%()".contains(c))
250}
251
252// Check if a parameter name is valid
253//
254// Valid parameter names contain only alphanumeric characters and underscores
255pub fn is_valid_parameter_name(name: &str) -> bool {
256    !name.is_empty()
257        && name.chars().all(|c| c.is_alphanumeric() || c == '_')
258        && !name.chars().next().unwrap().is_ascii_digit() // Can't start with digit
259}
260
261/// Resolve a mathematical expression by parsing and evaluating it
262///
263/// This implementation uses the full expression parser and evaluator
264fn resolve_expression<T: FromStr>(expr: &str, params: &HashMap<String, String>) -> Result<String>
265where
266    T::Err: std::fmt::Display,
267{
268    // Use the full expression evaluator
269    match crate::expression::evaluate_expression::<f64>(expr, params) {
270        Ok(result) => Ok(result.to_string()),
271        Err(_) => {
272            // Fallback to parameter substitution if expression parsing fails
273            let mut result = expr.to_string();
274
275            // Find and replace parameter references in the expression
276            for (param_name, param_value) in params {
277                let param_ref = format!("${{{}}}", param_name);
278                if result.contains(&param_ref) {
279                    result = result.replace(&param_ref, param_value);
280                }
281            }
282
283            Ok(result)
284        }
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use quick_xml;
292
293    #[test]
294    fn test_parameter_reference_parsing() {
295        assert_eq!(
296            parse_parameter_reference("${speed}"),
297            Some("speed".to_string())
298        );
299        assert_eq!(
300            parse_parameter_reference("${vehicle_speed}"),
301            Some("vehicle_speed".to_string())
302        );
303        assert_eq!(parse_parameter_reference("literal"), None);
304        assert_eq!(parse_parameter_reference("${}"), None);
305        assert_eq!(parse_parameter_reference("${123}"), None); // Invalid: starts with digit
306    }
307
308    #[test]
309    fn test_parameter_name_validation() {
310        assert!(is_valid_parameter_name("speed"));
311        assert!(is_valid_parameter_name("vehicle_speed"));
312        assert!(is_valid_parameter_name("speed123"));
313        assert!(!is_valid_parameter_name("123speed")); // Can't start with digit
314        assert!(!is_valid_parameter_name("")); // Can't be empty
315        assert!(!is_valid_parameter_name("speed-limit")); // No hyphens
316    }
317
318    #[test]
319    fn test_value_creation() {
320        let literal = Value::<f64>::literal(10.0);
321        assert!(matches!(literal, Value::Literal(10.0)));
322
323        let parameter = Value::<String>::parameter("speed".to_string());
324        assert!(matches!(parameter, Value::Parameter(_)));
325
326        let expression = Value::<String>::expression("speed + 10".to_string());
327        assert!(matches!(expression, Value::Expression(_)));
328    }
329
330    #[test]
331    fn test_value_resolution() {
332        let mut params = HashMap::new();
333        params.insert("speed".to_string(), "30.0".to_string());
334        params.insert("acceleration".to_string(), "2.5".to_string());
335
336        // Test literal resolution
337        let literal = Value::<f64>::literal(10.0);
338        assert_eq!(literal.resolve(&params).unwrap(), 10.0);
339
340        // Test parameter resolution
341        let parameter = Value::<f64>::parameter("speed".to_string());
342        assert_eq!(parameter.resolve(&params).unwrap(), 30.0);
343
344        // Test expression resolution (basic)
345        let expression = Value::<String>::expression("speed".to_string());
346        assert_eq!(expression.resolve(&params).unwrap(), "30");
347    }
348
349    #[test]
350    fn test_parameter_declaration_creation() {
351        // Test basic creation
352        let param = ParameterDeclaration::new(
353            "MaxSpeed".to_string(),
354            ParameterType::Double,
355            "60.0".to_string(),
356        );
357
358        assert_eq!(param.name.as_literal().unwrap(), "MaxSpeed");
359        assert_eq!(param.parameter_type, ParameterType::Double);
360        assert_eq!(param.value.as_literal().unwrap(), "60.0");
361        assert!(!param.has_constraints());
362    }
363
364    #[test]
365    fn test_parameter_declaration_with_constraints() {
366        let constraints = ValueConstraintGroup::new(vec![
367            ValueConstraint::greater_than("0.0".to_string()),
368            ValueConstraint::less_than("100.0".to_string()),
369        ]);
370
371        let param = ParameterDeclaration::with_constraints(
372            "Speed".to_string(),
373            ParameterType::Double,
374            "30.0".to_string(),
375            vec![constraints],
376        );
377
378        assert!(param.has_constraints());
379        let constraint_group = &param.constraint_groups[0];
380        assert_eq!(constraint_group.value_constraints.len(), 2);
381        assert_eq!(
382            constraint_group.value_constraints[0].rule,
383            Rule::GreaterThan
384        );
385        assert_eq!(constraint_group.value_constraints[1].rule, Rule::LessThan);
386    }
387
388    #[test]
389    fn test_parameter_declaration_add_constraint() {
390        let mut param =
391            ParameterDeclaration::new("Age".to_string(), ParameterType::Int, "25".to_string());
392
393        // Initially no constraints
394        assert!(!param.has_constraints());
395
396        // Add first constraint
397        param.add_constraint(ValueConstraint::greater_than("0".to_string()));
398        assert!(param.has_constraints());
399
400        // Add second constraint
401        param.add_constraint(ValueConstraint::less_than("120".to_string()));
402
403        let constraints = &param.constraint_groups[0];
404        assert_eq!(constraints.value_constraints.len(), 2);
405    }
406
407    #[test]
408    fn test_value_constraint_helpers() {
409        let eq_constraint = ValueConstraint::equal_to("test".to_string());
410        assert_eq!(eq_constraint.rule, Rule::EqualTo);
411        assert_eq!(eq_constraint.value.as_literal().unwrap(), "test");
412
413        let gt_constraint = ValueConstraint::greater_than("10".to_string());
414        assert_eq!(gt_constraint.rule, Rule::GreaterThan);
415
416        let lt_constraint = ValueConstraint::less_than("50".to_string());
417        assert_eq!(lt_constraint.rule, Rule::LessThan);
418    }
419
420    #[test]
421    fn test_range_creation() {
422        let range = Range::new(0.0, 100.0);
423        assert_eq!(range.lower_limit.as_literal().unwrap(), &0.0);
424        assert_eq!(range.upper_limit.as_literal().unwrap(), &100.0);
425
426        let default_range = Range::default();
427        assert_eq!(default_range.lower_limit.as_literal().unwrap(), &0.0);
428        assert_eq!(default_range.upper_limit.as_literal().unwrap(), &100.0);
429    }
430
431    #[test]
432    fn test_parameter_declarations_container() {
433        let mut declarations = ParameterDeclarations::default();
434        assert!(declarations.parameter_declarations.is_empty());
435
436        declarations
437            .parameter_declarations
438            .push(ParameterDeclaration::new(
439                "Speed".to_string(),
440                ParameterType::Double,
441                "30.0".to_string(),
442            ));
443
444        declarations
445            .parameter_declarations
446            .push(ParameterDeclaration::new(
447                "VehicleName".to_string(),
448                ParameterType::String,
449                "Ego".to_string(),
450            ));
451
452        assert_eq!(declarations.parameter_declarations.len(), 2);
453        assert_eq!(
454            declarations.parameter_declarations[0].parameter_type,
455            ParameterType::Double
456        );
457        assert_eq!(
458            declarations.parameter_declarations[1].parameter_type,
459            ParameterType::String
460        );
461    }
462
463    #[test]
464    fn test_directory_creation() {
465        // Test basic creation
466        let dir = Directory::new("/path/to/catalogs".to_string());
467        assert_eq!(dir.path.as_literal().unwrap(), "/path/to/catalogs");
468
469        // Test parameter creation
470        let param_dir = Directory::from_parameter("CatalogPath".to_string());
471        assert_eq!(param_dir.path.as_parameter().unwrap(), "CatalogPath");
472
473        // Test default
474        let default_dir = Directory::default();
475        assert_eq!(default_dir.path.as_literal().unwrap(), "");
476    }
477
478    #[test]
479    fn test_directory_path_resolution() {
480        let mut params = HashMap::new();
481        params.insert("CatalogPath".to_string(), "/catalogs/vehicles".to_string());
482
483        // Test literal path resolution
484        let dir = Directory::new("/path/to/catalogs".to_string());
485        assert_eq!(dir.resolve_path(&params).unwrap(), "/path/to/catalogs");
486
487        // Test parameter path resolution
488        let param_dir = Directory::from_parameter("CatalogPath".to_string());
489        assert_eq!(
490            param_dir.resolve_path(&params).unwrap(),
491            "/catalogs/vehicles"
492        );
493    }
494
495    #[test]
496    fn test_directory_path_validation() {
497        // Valid paths
498        let valid_dir = Directory::new("/path/to/catalogs".to_string());
499        assert!(valid_dir.validate_path());
500
501        let relative_dir = Directory::new("./catalogs".to_string());
502        assert!(relative_dir.validate_path());
503
504        // Invalid paths
505        let empty_dir = Directory::new("".to_string());
506        assert!(!empty_dir.validate_path());
507
508        let null_dir = Directory::new("path\0with\0null".to_string());
509        assert!(!null_dir.validate_path());
510
511        // Parameters are considered valid at this stage
512        let param_dir = Directory::from_parameter("CatalogPath".to_string());
513        assert!(param_dir.validate_path());
514    }
515
516    #[test]
517    fn test_directory_serialization() {
518        let dir = Directory::new("/path/to/catalogs".to_string());
519
520        // Test JSON serialization
521        let json = serde_json::to_string(&dir).unwrap();
522        assert!(json.contains("path"));
523        assert!(json.contains("/path/to/catalogs"));
524
525        // Test JSON deserialization
526        let deserialized: Directory = serde_json::from_str(&json).unwrap();
527        assert_eq!(deserialized.path.as_literal().unwrap(), "/path/to/catalogs");
528    }
529
530    #[test]
531    fn test_scientific_notation_parsing() {
532        // Test values from real XOSC files that are causing parsing issues
533        let test_values = [
534            "0.0000000000000000e+00",
535            "1.5000000000000000e+00",
536            "9.2884257876425379e-04",
537            "3.7479999999999983e+01",
538            "-2.8099999999999987e+00",
539        ];
540
541        for val in test_values {
542            println!("Testing: '{}'", val);
543
544            // Test direct f64 parsing
545            match val.parse::<f64>() {
546                Ok(f) => println!("  Direct f64::parse: {}", f),
547                Err(e) => println!("  Direct f64::parse ERROR: {}", e),
548            }
549
550            // Test Value<f64> deserialization via JSON
551            let json_str = format!("\"{}\"", val);
552            match serde_json::from_str::<Double>(&json_str) {
553                Ok(double_val) => {
554                    println!("  Value<f64> JSON: {:?}", double_val);
555                    if let Some(literal_val) = double_val.as_literal() {
556                        println!("  Literal value: {}", literal_val);
557                    }
558                }
559                Err(e) => println!("  Value<f64> JSON ERROR: {}", e),
560            }
561
562            // Test Value<f64> deserialization via XML
563            let xml_str = format!("<test>{}</test>", val);
564            match quick_xml::de::from_str::<Double>(&xml_str) {
565                Ok(double_val) => {
566                    println!("  Value<f64> XML: {:?}", double_val);
567                    if let Some(literal_val) = double_val.as_literal() {
568                        println!("  XML Literal value: {}", literal_val);
569                    }
570                }
571                Err(e) => println!("  Value<f64> XML ERROR: {}", e),
572            }
573            println!();
574        }
575    }
576
577    #[test]
578    fn test_parameter_declaration_multiple_constraint_groups() {
579        // Test the ALKS scenario pattern with multiple constraint groups
580        let constraint_group1 =
581            ValueConstraintGroup::new(vec![ValueConstraint::equal_to("1".to_string())]);
582
583        let constraint_group2 =
584            ValueConstraintGroup::new(vec![ValueConstraint::equal_to("-1".to_string())]);
585
586        let param = ParameterDeclaration::with_constraints(
587            "SideVehicle_InitPosition_RelativeLaneId".to_string(),
588            ParameterType::Int,
589            "1".to_string(),
590            vec![constraint_group1, constraint_group2],
591        );
592
593        // Verify we have multiple constraint groups
594        assert!(param.has_constraints());
595        assert_eq!(param.constraint_groups.len(), 2);
596
597        // Check first constraint group
598        assert_eq!(param.constraint_groups[0].value_constraints.len(), 1);
599        assert_eq!(
600            param.constraint_groups[0].value_constraints[0].rule,
601            Rule::EqualTo
602        );
603        assert_eq!(
604            param.constraint_groups[0].value_constraints[0]
605                .value
606                .as_literal()
607                .unwrap(),
608            "1"
609        );
610
611        // Check second constraint group
612        assert_eq!(param.constraint_groups[1].value_constraints.len(), 1);
613        assert_eq!(
614            param.constraint_groups[1].value_constraints[0].rule,
615            Rule::EqualTo
616        );
617        assert_eq!(
618            param.constraint_groups[1].value_constraints[0]
619                .value
620                .as_literal()
621                .unwrap(),
622            "-1"
623        );
624    }
625
626    #[test]
627    fn test_alks_scenario_constraint_pattern() {
628        // Test the exact pattern from ALKS scenario file
629        let xml = r#"
630        <ParameterDeclaration name="SideVehicle_InitPosition_RelativeLaneId" parameterType="int" value="1">
631          <ConstraintGroup>
632            <ValueConstraint rule="equalTo" value="1"></ValueConstraint>
633          </ConstraintGroup>
634          <ConstraintGroup>
635            <ValueConstraint rule="equalTo" value="-1"></ValueConstraint>
636          </ConstraintGroup>
637        </ParameterDeclaration>
638        "#;
639
640        // This should now parse without the "duplicate field" error
641        let result = quick_xml::de::from_str::<ParameterDeclaration>(xml);
642        assert!(
643            result.is_ok(),
644            "Failed to parse ALKS constraint pattern: {:?}",
645            result.err()
646        );
647
648        let param = result.unwrap();
649        assert_eq!(
650            param.name.as_literal().unwrap(),
651            "SideVehicle_InitPosition_RelativeLaneId"
652        );
653        assert_eq!(param.parameter_type, ParameterType::Int);
654        assert_eq!(param.value.as_literal().unwrap(), "1");
655        assert_eq!(param.constraint_groups.len(), 2);
656    }
657
658    #[test]
659    fn test_value_display_trait() {
660        // Test Display implementation for Value<T>
661        let literal_value = Value::<f64>::literal(42.5);
662        assert_eq!(format!("{}", literal_value), "42.5");
663
664        let parameter_value = Value::<String>::parameter("speed".to_string());
665        assert_eq!(format!("{}", parameter_value), "${speed}");
666
667        let expression_value = Value::<String>::expression("speed * 2".to_string());
668        assert_eq!(format!("{}", expression_value), "${speed * 2}");
669
670        // Test with different types
671        let bool_literal = Value::<bool>::literal(true);
672        assert_eq!(format!("{}", bool_literal), "true");
673
674        let string_literal = Value::<String>::literal("hello".to_string());
675        assert_eq!(format!("{}", string_literal), "hello");
676
677        // Test with our type aliases
678        let double_value = Double::literal(3.14);
679        assert_eq!(format!("{}", double_value), "3.14");
680
681        let os_string_param = OSString::parameter("vehicle_name".to_string());
682        assert_eq!(format!("{}", os_string_param), "${vehicle_name}");
683
684        let boolean_expr = Boolean::expression("speed > 30".to_string());
685        assert_eq!(format!("{}", boolean_expr), "${speed > 30}");
686    }
687}
688
689// Data Container Types for Scenario Structure
690
691use crate::types::enums::{ParameterType, Rule};
692
693/// Parameter declarations container
694#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
695pub struct ParameterDeclarations {
696    #[serde(rename = "ParameterDeclaration", default)]
697    pub parameter_declarations: Vec<ParameterDeclaration>,
698}
699
700/// Individual parameter declaration
701#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
702pub struct ParameterDeclaration {
703    #[serde(rename = "@name")]
704    pub name: OSString,
705    #[serde(rename = "@parameterType")]
706    pub parameter_type: ParameterType,
707    #[serde(rename = "@value")]
708    pub value: OSString,
709    #[serde(
710        rename = "ConstraintGroup",
711        default,
712        skip_serializing_if = "Vec::is_empty"
713    )]
714    pub constraint_groups: Vec<ValueConstraintGroup>,
715}
716
717impl Default for ParameterDeclaration {
718    fn default() -> Self {
719        Self {
720            name: OSString::literal("DefaultParameter".to_string()),
721            parameter_type: ParameterType::String,
722            value: OSString::literal("".to_string()),
723            constraint_groups: Vec::new(),
724        }
725    }
726}
727
728/// Parameter constraints container
729#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
730pub struct ValueConstraintGroup {
731    #[serde(rename = "ValueConstraint")]
732    pub value_constraints: Vec<ValueConstraint>,
733}
734
735/// Individual parameter value constraint
736#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
737pub struct ValueConstraint {
738    #[serde(rename = "@rule")]
739    pub rule: Rule,
740    #[serde(rename = "@value")]
741    pub value: OSString,
742}
743
744/// Value range specification
745#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
746pub struct Range {
747    #[serde(rename = "@lowerLimit")]
748    pub lower_limit: Double,
749    #[serde(rename = "@upperLimit")]
750    pub upper_limit: Double,
751}
752
753impl Default for ValueConstraint {
754    fn default() -> Self {
755        Self {
756            rule: Rule::EqualTo,
757            value: OSString::literal("0".to_string()),
758        }
759    }
760}
761
762impl Default for Range {
763    fn default() -> Self {
764        Self {
765            lower_limit: Double::literal(0.0),
766            upper_limit: Double::literal(100.0),
767        }
768    }
769}
770
771// Helper methods for ParameterDeclaration
772impl ParameterDeclaration {
773    /// Create a new parameter declaration with the given name, type, and value
774    pub fn new(name: String, parameter_type: ParameterType, value: String) -> Self {
775        Self {
776            name: OSString::literal(name),
777            parameter_type,
778            value: OSString::literal(value),
779            constraint_groups: Vec::new(),
780        }
781    }
782
783    /// Create a parameter declaration with constraints
784    pub fn with_constraints(
785        name: String,
786        parameter_type: ParameterType,
787        value: String,
788        constraints: Vec<ValueConstraintGroup>,
789    ) -> Self {
790        Self {
791            name: OSString::literal(name),
792            parameter_type,
793            value: OSString::literal(value),
794            constraint_groups: constraints,
795        }
796    }
797
798    /// Add a constraint to this parameter declaration
799    pub fn add_constraint(&mut self, constraint: ValueConstraint) {
800        if let Some(group) = self.constraint_groups.last_mut() {
801            group.value_constraints.push(constraint);
802        } else {
803            self.constraint_groups.push(ValueConstraintGroup {
804                value_constraints: vec![constraint],
805            });
806        }
807    }
808
809    /// Check if the parameter has constraints
810    pub fn has_constraints(&self) -> bool {
811        !self.constraint_groups.is_empty()
812    }
813}
814
815/// Directory path reference for catalog files
816///
817/// This type represents a directory path that contains catalog files.
818/// It's used by all catalog location types to specify where to find
819/// catalog definitions that can be referenced by scenarios.
820#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
821pub struct Directory {
822    /// Path to the directory containing catalog files
823    #[serde(rename = "@path")]
824    pub path: OSString,
825}
826
827// Helper methods for Directory
828impl Directory {
829    /// Create a new Directory with the given path
830    pub fn new(path: String) -> Self {
831        Self {
832            path: OSString::literal(path),
833        }
834    }
835
836    /// Create a Directory from a parameter reference
837    pub fn from_parameter(param_name: String) -> Self {
838        Self {
839            path: OSString::parameter(param_name),
840        }
841    }
842
843    /// Get the resolved path string
844    pub fn resolve_path(&self, params: &HashMap<String, String>) -> Result<String> {
845        self.path.resolve(params)
846    }
847
848    /// Check if the directory path is valid (basic validation)
849    pub fn validate_path(&self) -> bool {
850        if let Some(literal_path) = self.path.as_literal() {
851            !literal_path.is_empty() && !literal_path.contains('\0')
852        } else {
853            // Parameters and expressions are assumed valid at this stage
854            true
855        }
856    }
857}
858
859impl Default for Directory {
860    fn default() -> Self {
861        Self::new(String::new())
862    }
863}
864
865// Helper methods for ValueConstraintGroup
866impl ValueConstraintGroup {
867    /// Create a new value constraint group with the given constraints
868    pub fn new(constraints: Vec<ValueConstraint>) -> Self {
869        Self {
870            value_constraints: constraints,
871        }
872    }
873
874    /// Add a constraint to the group
875    pub fn add_constraint(&mut self, constraint: ValueConstraint) {
876        self.value_constraints.push(constraint);
877    }
878}
879
880// Helper methods for ValueConstraint
881impl ValueConstraint {
882    /// Create a new value constraint
883    pub fn new(rule: Rule, value: String) -> Self {
884        Self {
885            rule,
886            value: OSString::literal(value),
887        }
888    }
889
890    /// Create an equality constraint
891    pub fn equal_to(value: String) -> Self {
892        Self::new(Rule::EqualTo, value)
893    }
894
895    /// Create a greater than constraint
896    pub fn greater_than(value: String) -> Self {
897        Self::new(Rule::GreaterThan, value)
898    }
899
900    /// Create a less than constraint
901    pub fn less_than(value: String) -> Self {
902        Self::new(Rule::LessThan, value)
903    }
904}
905
906// Helper methods for Range
907impl Range {
908    /// Create a new range with the given limits
909    ///
910    /// # Panics
911    /// Panics if lower > upper when both are literals
912    pub fn new(lower: f64, upper: f64) -> Self {
913        debug_assert!(lower <= upper, "Range lower limit must be <= upper limit");
914        Self {
915            lower_limit: Double::literal(lower),
916            upper_limit: Double::literal(upper),
917        }
918    }
919
920    /// Create a new range with validation
921    pub fn try_new(lower: f64, upper: f64) -> Result<Self> {
922        if lower > upper {
923            return Err(Error::validation_error(
924                "Range",
925                "Range lower limit must be <= upper limit",
926            ));
927        }
928        Ok(Self {
929            lower_limit: Double::literal(lower),
930            upper_limit: Double::literal(upper),
931        })
932    }
933}