openscenario-rs 0.3.0

Rust library for parsing and manipulating OpenSCENARIO files
Documentation
# Development Guide

This document provides essential patterns, troubleshooting guidance, and architectural insights for working with the openscenario-rs codebase.

## Type System Patterns

### Value<T> Type Usage

The library uses `Value<T>` types (e.g., `Double`, `OSString`) to support parameterization in OpenSCENARIO files. Understanding the correct usage patterns is crucial:

**Constructor Parameters vs Struct Fields**:
- **Constructor parameters**: Use concrete types (`f64`, `String`) for ergonomics
- **Struct fields**: Use `Value<T>` types (`Double`, `OSString`) for parameterization support
- **Conversion**: Automatic via `::literal()` constructors

```rust
// ✅ Correct: Constructor takes f64
impl TrafficSwarmAction {
    pub fn new(velocity: f64) -> Self {
        Self {
            velocity: Some(Double::literal(velocity)), // Wrapped in Value<T>
            // ...
        }
    }
}

// ✅ Correct: Field uses Value<T> type
pub struct TrafficSwarmAction {
    pub velocity: Option<Double>, // Value<f64> wrapper
}
```

**Comparison Patterns**:
When testing or comparing `Value<T>` types, use `.as_literal()` to extract wrapped values:

```rust
// ✅ Correct: Extract literal value for comparison
assert_eq!(action.velocity.as_ref().unwrap().as_literal(), Some(&10.0));

// ❌ Incorrect: Direct comparison fails
// assert_eq!(action.velocity, Some(10.0));
```

**Common Value<T> Types**:
- `Double` = `Value<f64>`
- `OSString` = `Value<String>` 
- `UnsignedInt` = `Value<u32>`
- `PositiveDouble` = `Value<f64>` (with validation)

## XML Serialization Patterns

### Optional Field Handling

Optional fields in structs require careful serialization handling to prevent empty attribute issues:

```rust
// ✅ Correct: Skip serialization of None values
#[derive(Serialize, Deserialize)]
pub struct TrafficSwarmAction {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub velocity: Option<Double>,
    
    #[serde(skip_serializing_if = "Option::is_none")]
    pub traffic_definition: Option<TrafficDefinition>,
}
```

**Problem**: Without `skip_serializing_if`, optional fields serialize as empty attributes (`velocity=""`), which fail to deserialize back to `Value<T>` types.

**Solution**: Always use `skip_serializing_if = "Option::is_none"` for optional `Value<T>` fields.

## Common Compilation Fixes

### Value<T> Type Mismatches

**Issue**: Direct assignment of raw values to `Value<T>` fields
```rust
// ❌ Incorrect
action.velocity = Some(10.0);

// ✅ Correct
action.velocity = Some(Double::literal(10.0));
```

**Issue**: Direct comparison of `Value<T>` with raw values
```rust
// ❌ Incorrect
assert_eq!(action.velocity, Some(10.0));

// ✅ Correct
assert_eq!(action.velocity.as_ref().unwrap().as_literal(), Some(&10.0));
```

**Issue**: OSString comparison issues
```rust
// ❌ Incorrect
assert_eq!(road.name, "Highway1");

// ✅ Correct
assert_eq!(road.name.as_literal(), Some(&"Highway1".to_string()));
```

### Float Literal Syntax

**Issue**: Invalid float literals in tests
```rust
// ❌ Incorrect
let value = 10.;

// ✅ Correct
let value = 10.0;
```

## Testing Patterns

### XML Round-Trip Tests

When writing XML serialization tests, ensure proper handling of optional fields:

```rust
#[test]
fn test_xml_round_trip() {
    let original = TrafficSwarmAction {
        velocity: Some(Double::literal(10.0)),
        traffic_definition: None, // Will be skipped in serialization
    };
    
    let xml = quick_xml::se::to_string(&original).unwrap();
    let deserialized: TrafficSwarmAction = quick_xml::de::from_str(&xml).unwrap();
    
    // Use as_literal() for Value<T> comparisons
    assert_eq!(
        deserialized.velocity.as_ref().unwrap().as_literal(),
        Some(&10.0)
    );
}
```

## Schema Compliance

### XSD Compliance Patterns

OpenSCENARIO-rs achieves 95%+ XSD validation compliance. Follow these patterns for maintaining compliance:

#### Optional Attribute Pattern

```rust
// ✅ Correct: XSD-compliant optional attribute
#[derive(Serialize, Deserialize)]
pub struct LaneChangeAction {
    #[serde(
        rename = "@targetLaneOffset",
        default,
        skip_serializing_if = "Option::is_none",
        deserialize_with = "deserialize_optional_double"
    )]
    pub target_lane_offset: Option<Double>,
}

// ❌ Incorrect: Serializes empty string for None values
#[serde(rename = "@targetLaneOffset", default)]
pub target_lane_offset: Option<Double>,
```

#### Custom Deserializer for Empty Strings

```rust
fn deserialize_optional_double<'de, D>(deserializer: D) -> Result<Option<Double>, D::Error>
where
    D: Deserializer<'de>,
{
    // Handle empty strings gracefully for backward compatibility
    // Empty strings become None, valid values become Some(Double)
}
```

#### XSD Choice Group Implementation

```rust
// ✅ Correct: Custom serialization for choice groups
impl Serialize for EntityCondition {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(1))?;
        match self {
            EntityCondition::SpeedCondition(condition) => {
                map.serialize_entry("SpeedCondition", condition)?;
            }
            // ... other variants
        }
        map.end()
    }
}
```

### Verifying OpenSCENARIO XSD Compliance

When modifying serialization behavior, always verify against the official schema:

1. Check if attributes are required (`use="required"`) or optional
2. Verify element occurrence constraints (`minOccurs`, `maxOccurs`)
3. Note deprecated elements/attributes in the schema
4. Ensure serialization matches schema expectations
5. **Test XSD validation**: Use tools to validate generated XML

**XSD Validation Testing**:
```bash
# Use examples to validate XSD compliance
cargo run --example test_lane_change_serialization
```

**Common XSD Issues**:
- Empty attributes (`targetLaneOffset=""`) instead of omission
- Incorrect choice group structure (flat vs nested elements)
- Attributes serialized as elements or vice versa

## Troubleshooting Checklist

### Test Compilation Failures

1. **Value<T> type mismatches**:
   - Check if raw values are assigned to `Value<T>` fields
   - Use `::literal()` constructors for wrapping
   - Use `.as_literal()` for comparisons

2. **XML serialization issues**:
   - Add `skip_serializing_if = "Option::is_none"` to optional fields
   - Verify schema compliance for serialization changes

3. **Float syntax errors**:
   - Ensure all float literals end with `.0` (e.g., `10.0` not `10.`)

4. **String comparison issues**:
   - Use `.as_literal()` for `OSString` comparisons
   - Convert string literals to `String` when needed

### Architecture Validation

When uncertain about type usage patterns:

1. **Search existing code**: Look for similar implementations in the codebase
2. **Check constructor patterns**: See how other structs handle `Value<T>` conversion
3. **Verify test patterns**: Follow established testing conventions
4. **Consult schema**: Ensure changes align with OpenSCENARIO XSD requirements

## Recent Fixes Applied

### XSD Validation Compliance (Latest - Resolved)

**Problem**: XSD validation failures due to improper XML serialization patterns.

**Root Cause**: 
- `LaneChangeAction` serialized empty `targetLaneOffset=""` instead of omitting attribute
- `EntityCondition` enum didn't match XSD choice group structure requirements
- Various attribute vs element serialization inconsistencies

**Solution**: Implemented comprehensive XSD compliance patterns:

**Files Modified**:
- `src/types/actions/movement.rs` - Added `skip_serializing_if` to `targetLaneOffset`
- `src/types/conditions/entity.rs` - Custom `Serialize` for choice groups
- `tests/xsd_validation_test.rs` - Comprehensive XSD compliance tests
- `examples/test_lane_change_serialization.rs` - Validation example
- `docs/xsd_validation_fixes.md` - Complete implementation guide

**Key Patterns**:
- Use `skip_serializing_if = "Option::is_none"` for all optional XML attributes
- Implement custom `Serialize` for XSD choice groups using `SerializeMap`
- Use custom deserializers for graceful empty string handling
- Always use `@` prefix for XML attributes in serde annotations

### TrafficSwarmAction XML Serialization (Resolved)

**Problem**: `test_xml_round_trip_traffic_swarm` failing due to empty attribute serialization.

**Root Cause**: Optional `Value<T>` fields serialized as empty attributes, failing deserialization.

**Solution**: Added `skip_serializing_if = "Option::is_none"` to optional fields in `TrafficSwarmAction`.

**Files Modified**:
- `src/types/actions/traffic.rs` - Main fix + test updates
- `src/types/actions/control.rs` - Value<T> comparison fixes
- `src/types/actions/movement.rs` - Raw value assignment fixes
- `src/types/road.rs` - OSString comparison fixes
- `src/types/positions/road.rs` - Multiple Value<T> fixes
- `tests/position_types_test.rs` - Float literal and type fixes
- `tests/openscenario_integration_test.rs` & `tests/scenario_parsing_integration_test.rs` - Minor fixes

**Key Pattern**: Always use `skip_serializing_if = "Option::is_none"` for optional `Value<T>` fields to prevent empty attribute serialization issues.

## Contributing Guidelines

1. **Follow existing patterns**: Study neighboring files before implementing
2. **Test thoroughly**: Include XML round-trip tests for serializable types
3. **Verify schema compliance**: Ensure changes align with OpenSCENARIO XSD
4. **Use proper types**: Follow `Value<T>` usage patterns consistently
5. **Document significant changes**: Update this guide when discovering new patterns