Rust API Reference
Complete API reference for using DDEX Builder in Rust with deterministic XML generation and full type safety.
Installation
[dependencies]
ddex-builder = "0.2.5"
ddex-core = "0.2.5"
tokio = { version = "1.0", features = ["full"] } # For async features
serde_json = "1.0" # For JSON serialization
uuid = { version = "1.0", features = ["v4"] } # For ID generation
Basic Usage
Simple Build
use ddex_builder::DdexBuilder;
use ddex_core::models::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let builder = DdexBuilder::new();
    
    let build_request = BuildRequest {
        message_header: MessageHeader {
            message_id: "MSG_001".to_string(),
            message_sender_name: "My Label".to_string(),
            message_recipient_name: "Spotify".to_string(),
            ..Default::default()
        },
        version: ERNVersion::Ern43,
        releases: vec![Release {
            release_id: "REL_001".to_string(),
            title: vec![LocalizedString {
                text: "My Amazing Album".to_string(),
                language_code: Some("en".to_string()),
                ..Default::default()
            }],
            display_artist: "Amazing Artist".to_string(),
            release_type: ReleaseType::Album,
            tracks: vec![Track {
                resource_reference: "TR_001".to_string(),
                title: "Track 1".to_string(),
                isrc: Some("USABC1234567".to_string()),
                duration: Some(std::time::Duration::from_secs(180)),
                ..Default::default()
            }],
            ..Default::default()
        }],
        ..Default::default()
    };
    
    let xml = builder.build(&build_request)?;
    println!("Generated XML: {} bytes", xml.len());
    
    Ok(())
}
Async Build
use ddex_builder::DdexBuilder;
use ddex_core::models::BuildRequest;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let builder = DdexBuilder::new();
    let request = BuildRequest { /* ... */ };
    
    let result = builder.build_async(&request).await?;
    
    println!("Generated XML: {} bytes", result.xml.len());
    println!("Warnings: {:?}", result.warnings);
    
    Ok(())
}
Core Types
DdexBuilder
The main builder struct with configuration and presets.
pub struct DdexBuilder {
    // Internal configuration
}
impl DdexBuilder {
    /// Create a new builder with default configuration
    pub fn new() -> Self;
    
    /// Create a builder with custom configuration
    pub fn with_config(config: BuildConfig) -> Self;
    
    /// Build DDEX XML synchronously
    pub fn build(&self, request: &BuildRequest) -> Result<String, BuildError>;
    
    /// Build DDEX XML asynchronously
    pub async fn build_async(&self, request: &BuildRequest) -> Result<BuildResult, BuildError>;
    
    /// Build with detailed result information
    pub fn build_detailed(&self, request: &BuildRequest) -> Result<DetailedBuildResult, BuildError>;
    
    /// Apply a preset configuration
    pub fn apply_preset(&mut self, preset: &str) -> Result<(), BuildError>;
    
    /// Get available presets
    pub fn get_available_presets(&self) -> Vec<String>;
    
    /// Perform preflight validation
    pub fn preflight(&self, request: &BuildRequest) -> PreflightResult;
    
    /// Canonicalize existing XML
    pub fn canonicalize(&self, xml: &str) -> Result<String, BuildError>;
    
    /// Generate diff between original and new
    pub fn diff(&self, original_xml: &str, request: &BuildRequest) -> Result<DiffResult, BuildError>;
}
BuildConfig
Comprehensive configuration for deterministic builds.
#[derive(Debug, Clone)]
pub struct BuildConfig {
    /// Determinism configuration
    pub determinism: DeterminismConfig,
    
    /// ID generation strategy
    pub id_strategy: IdStrategy,
    
    /// Validation level
    pub validation_level: ValidationLevel,
    
    /// Preset configuration
    pub preset: Option<String>,
    
    /// Lock preset to prevent changes
    pub preset_locked: bool,
    
    /// Maximum memory usage
    pub memory_limit: Option<usize>,
    
    /// Build timeout
    pub timeout_ms: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct DeterminismConfig {
    /// Canonicalization mode
    pub canon_mode: CanonMode,
    
    /// Element sorting strategy
    pub sort_strategy: SortStrategy,
    
    /// Output formatting mode
    pub output_mode: OutputMode,
    
    /// Line ending style
    pub line_ending: LineEnding,
    
    /// Unicode normalization
    pub unicode_normalization: UnicodeNorm,
    
    /// Enable reproducibility verification
    pub verify_determinism: bool,
    
    /// Emit reproducibility banner
    pub emit_banner: bool,
}
#[derive(Debug, Clone)]
pub enum CanonMode {
    /// DDEX Builder Canonicalization v1.0
    DbC14n,
    /// Pretty-printed XML
    Pretty,
    /// Compact XML
    Compact,
}
#[derive(Debug, Clone)]
pub enum IdStrategy {
    /// Random UUIDs
    Uuid,
    /// Time-ordered UUIDs
    UuidV7,
    /// Sequential IDs (A1, A2, etc.)
    Sequential,
    /// Content-based stable hashes
    StableHash { recipe: String },
}
BuildRequest
The input structure for building DDEX XML.
#[derive(Debug, Clone)]
pub struct BuildRequest {
    /// Message header information
    pub message_header: MessageHeader,
    
    /// DDEX version to generate
    pub version: ERNVersion,
    
    /// DDEX profile (AudioAlbum, AudioSingle, etc.)
    pub profile: Option<ERNProfile>,
    
    /// Message control type
    pub message_control_type: MessageControlType,
    
    /// Releases to include
    pub releases: Vec<Release>,
    
    /// Deals and commercial terms
    pub deals: Vec<Deal>,
    
    /// Parties (labels, publishers, etc.)
    pub parties: Vec<Party>,
    
    /// Resources (tracks, images, videos)
    pub resources: Vec<Resource>,
    
    /// Extensions and custom elements
    pub extensions: HashMap<String, serde_json::Value>,
}
impl BuildRequest {
    /// Create a minimal build request
    pub fn minimal(
        message_id: &str,
        sender: &str,
        recipient: &str,
    ) -> Self;
    
    /// Add a release to the request
    pub fn with_release(mut self, release: Release) -> Self;
    
    /// Add deals for a release
    pub fn with_deals(mut self, deals: Vec<Deal>) -> Self;
    
    /// Validate the request structure
    pub fn validate(&self) -> Result<(), ValidationError>;
}
BuildResult
Detailed results from the build process.
#[derive(Debug, Clone)]
pub struct BuildResult {
    /// Generated XML content
    pub xml: String,
    
    /// Build warnings (non-fatal issues)
    pub warnings: Vec<BuildWarning>,
    
    /// Build statistics
    pub statistics: BuildStatistics,
    
    /// Canonical hash (if enabled)
    pub canonical_hash: Option<String>,
    
    /// Reproducibility information
    pub reproducibility_info: Option<ReproducibilityInfo>,
    
    /// Generation metadata
    pub metadata: BuildMetadata,
}
#[derive(Debug, Clone)]
pub struct BuildStatistics {
    /// Build duration
    pub build_duration: std::time::Duration,
    
    /// Memory usage peak
    pub peak_memory_bytes: usize,
    
    /// Generated XML size
    pub xml_size_bytes: usize,
    
    /// Number of elements generated
    pub element_count: usize,
    
    /// Number of references linked
    pub reference_count: usize,
}
#[derive(Debug, Clone)]
pub struct BuildWarning {
    /// Warning code
    pub code: String,
    
    /// Human-readable message
    pub message: String,
    
    /// Location in the input data
    pub location: Option<String>,
    
    /// Suggested fix
    pub suggestion: Option<String>,
}
Error Handling
Comprehensive error types with actionable information.
#[derive(Debug, thiserror::Error)]
pub enum BuildError {
    #[error("Missing required field: {field} at {location}")]
    MissingRequired {
        field: String,
        location: String,
    },
    
    #[error("Invalid format for {field}: {value}")]
    InvalidFormat {
        field: String,
        value: String,
    },
    
    #[error("Reference not found: {reference} of type {reference_type}")]
    ReferenceNotFound {
        reference: String,
        reference_type: String,
    },
    
    #[error("Circular reference detected: {chain}")]
    CircularReference {
        chain: String,
    },
    
    #[error("Preset not found: {preset}")]
    PresetNotFound {
        preset: String,
    },
    
    #[error("Preset locked: cannot modify {preset}")]
    PresetLocked {
        preset: String,
    },
    
    #[error("Memory limit exceeded: {used} > {limit} bytes")]
    MemoryLimitExceeded {
        used: usize,
        limit: usize,
    },
    
    #[error("Build timeout after {timeout_ms}ms")]
    Timeout {
        timeout_ms: u64,
    },
    
    #[error("XML generation error: {message}")]
    XmlGeneration {
        message: String,
    },
}
impl BuildError {
    /// Get error code for programmatic handling
    pub fn code(&self) -> &str;
    
    /// Get detailed location information
    pub fn location(&self) -> Option<&str>;
    
    /// Get suggestions for fixing the error
    pub fn suggestions(&self) -> Vec<String>;
}
Deterministic Builds
DB-C14N/1.0 Canonicalization
use ddex_builder::{DdexBuilder, BuildConfig, DeterminismConfig, CanonMode};
fn create_canonical_builder() -> DdexBuilder {
    let determinism = DeterminismConfig {
        canon_mode: CanonMode::DbC14n,
        sort_strategy: SortStrategy::Canonical,
        output_mode: OutputMode::DbC14n,
        line_ending: LineEnding::LF,
        unicode_normalization: UnicodeNorm::NFC,
        verify_determinism: true,
        emit_banner: true,
    };
    
    let config = BuildConfig {
        determinism,
        id_strategy: IdStrategy::StableHash {
            recipe: "v1".to_string(),
        },
        validation_level: ValidationLevel::Strict,
        ..Default::default()
    };
    
    DdexBuilder::with_config(config)
}
fn build_deterministic(request: &BuildRequest) -> Result<String, BuildError> {
    let builder = create_canonical_builder();
    
    // This will produce identical output every time
    let result = builder.build_detailed(request)?;
    
    if result.reproducibility_info.is_some() {
        println!("✅ Build is reproducible");
        if let Some(hash) = result.canonical_hash {
            println!("Canonical hash: {}", hash);
        }
    }
    
    Ok(result.xml)
}
Stable Hash IDs
Content-based deterministic ID generation.
use ddex_builder::{DdexBuilder, IdStrategy, StableHashConfig};
fn create_stable_hash_builder() -> DdexBuilder {
    let config = BuildConfig {
        id_strategy: IdStrategy::StableHash {
            recipe: "v1".to_string(),
        },
        ..Default::default()
    };
    
    DdexBuilder::with_config(config)
}
// These will generate the same IDs for the same content
fn demonstrate_stable_ids() -> Result<(), BuildError> {
    let builder = create_stable_hash_builder();
    
    let request = BuildRequest {
        releases: vec![Release {
            title: vec![LocalizedString {
                text: "Same Title".to_string(),
                language_code: Some("en".to_string()),
                ..Default::default()
            }],
            upc: Some("123456789012".to_string()),
            ..Default::default()
        }],
        ..Default::default()
    };
    
    let xml1 = builder.build(&request)?;
    let xml2 = builder.build(&request)?;
    
    assert_eq!(xml1, xml2); // Identical output
    
    Ok(())
}
Preset System
Platform-specific configurations with provenance tracking.
use ddex_builder::{DdexBuilder, PresetInfo};
fn use_spotify_preset() -> Result<(), BuildError> {
    let mut builder = DdexBuilder::new();
    
    // Apply Spotify-specific configuration
    builder.apply_preset("spotify_audio_43")?;
    
    // Check preset information
    let preset_info = builder.get_preset_info("spotify_audio_43")?;
    println!("Preset: {} v{}", preset_info.name, preset_info.version);
    println!("Source: {}", preset_info.provenance_url);
    
    // Lock preset to prevent accidental changes
    builder.lock_preset()?;
    
    let request = BuildRequest { /* ... */ };
    let xml = builder.build(&request)?;
    
    Ok(())
}
fn list_available_presets() -> Result<(), BuildError> {
    let builder = DdexBuilder::new();
    let presets = builder.get_available_presets();
    
    for preset in presets {
        let info = builder.get_preset_info(&preset)?;
        println!("{}: {}", preset, info.description);
        println!("  Source: {}", info.source);
        println!("  Version: {}", info.version);
    }
    
    Ok(())
}
Advanced Features
Preflight Validation
use ddex_builder::{DdexBuilder, PreflightResult, ValidationLevel};
fn validate_before_build(request: &BuildRequest) -> Result<(), BuildError> {
    let builder = DdexBuilder::new();
    let preflight = builder.preflight(request);
    
    if !preflight.is_valid {
        println!("❌ Preflight validation failed:");
        for error in &preflight.errors {
            println!("  Error: {} at {}", error.message, error.location.as_ref().unwrap_or(&"unknown".to_string()));
            if let Some(suggestion) = &error.suggestion {
                println!("    Suggestion: {}", suggestion);
            }
        }
        return Err(BuildError::PreflightFailed);
    }
    
    println!("✅ Preflight validation passed");
    for warning in &preflight.warnings {
        println!("  Warning: {}", warning.message);
    }
    
    // Show validation statistics
    println!("Validation stats:");
    println!("  Total fields: {}", preflight.statistics.total_fields);
    println!("  Valid fields: {}", preflight.statistics.validated_fields);
    println!("  Missing required: {:?}", preflight.statistics.missing_required_fields);
    
    Ok(())
}
Streaming Builds
For generating large DDEX catalogs efficiently.
use ddex_builder::{DdexBuilder, StreamingBuilder, StreamConfig};
use futures::stream::StreamExt;
#[tokio::main]
async fn build_large_catalog() -> Result<(), Box<dyn std::error::Error>> {
    let config = StreamConfig {
        batch_size: 100,
        memory_limit: 500 * 1024 * 1024, // 500MB
    };
    
    let streaming_builder = StreamingBuilder::new(config);
    
    // Create a stream of releases from database/file
    let release_stream = get_releases_stream().await?;
    
    let mut xml_stream = streaming_builder.build_stream(release_stream);
    
    while let Some(xml_chunk) = xml_stream.next().await {
        let chunk = xml_chunk?;
        
        // Write chunk to file or send to destination
        write_chunk_to_file(&chunk).await?;
    }
    
    Ok(())
}
async fn get_releases_stream() -> Result<impl Stream<Item = Release>, Box<dyn std::error::Error>> {
    // Implementation would return a stream of releases
    unimplemented!()
}
async fn write_chunk_to_file(chunk: &str) -> Result<(), Box<dyn std::error::Error>> {
    // Write XML chunk to output file
    unimplemented!()
}
Custom Extension Handling
use ddex_builder::{DdexBuilder, ExtensionProcessor};
use serde_json::Value;
struct MyExtensionProcessor;
impl ExtensionProcessor for MyExtensionProcessor {
    fn process_extension(
        &self,
        key: &str,
        value: &Value,
        context: &ExtensionContext,
    ) -> Result<XmlElement, ProcessError> {
        match key {
            "customMetadata" => {
                // Convert JSON to XML element
                Ok(XmlElement {
                    name: "CustomMetadata".to_string(),
                    attributes: HashMap::new(),
                    content: value.to_string(),
                })
            }
            _ => Err(ProcessError::UnknownExtension(key.to_string())),
        }
    }
}
fn build_with_extensions() -> Result<(), BuildError> {
    let mut builder = DdexBuilder::new();
    builder.set_extension_processor(Box::new(MyExtensionProcessor));
    
    let request = BuildRequest {
        extensions: {
            let mut ext = HashMap::new();
            ext.insert("customMetadata".to_string(), serde_json::json!({
                "label": "Special Edition",
                "priority": "high"
            }));
            ext
        },
        ..Default::default()
    };
    
    let xml = builder.build(&request)?;
    
    Ok(())
}
Batch Processing
use ddex_builder::{DdexBuilder, BatchProcessor, BatchConfig};
use rayon::prelude::*;
fn batch_build_releases(releases: Vec<BuildRequest>) -> Result<Vec<String>, BuildError> {
    let config = BatchConfig {
        parallel: true,
        max_workers: num_cpus::get(),
        memory_per_worker: 100 * 1024 * 1024, // 100MB per worker
    };
    
    let processor = BatchProcessor::new(config);
    
    // Process in parallel
    let results: Result<Vec<_>, _> = releases
        .par_iter()
        .map(|request| {
            let builder = DdexBuilder::new();
            builder.build(request)
        })
        .collect();
    
    results
}
// Alternative: Use the built-in batch processor
fn batch_build_with_processor(requests: Vec<BuildRequest>) -> Result<Vec<String>, BuildError> {
    let builder = DdexBuilder::new();
    let results = builder.build_batch(&requests)?;
    
    for (i, result) in results.iter().enumerate() {
        if let Some(warnings) = &result.warnings {
            println!("Request {}: {} warnings", i, warnings.len());
        }
    }
    
    Ok(results.into_iter().map(|r| r.xml).collect())
}
Integration Examples
With ddex-parser (Round-trip)
use ddex_parser::DdexParser;
use ddex_builder::DdexBuilder;
fn round_trip_example(original_xml: &str) -> Result<String, Box<dyn std::error::Error>> {
    // Parse original DDEX
    let parser = DdexParser::new();
    let mut parsed = parser.parse(original_xml)?;
    
    // Modify the data
    if let Some(release) = parsed.flat.releases.get_mut(0) {
        release.title = "Modified Title".to_string();
        release.tracks.push(Track {
            title: "Bonus Track".to_string(),
            position: release.tracks.len() as u32 + 1,
            isrc: Some("USXYZ2024001".to_string()),
            duration: Some(std::time::Duration::from_secs(240)),
            ..Default::default()
        });
    }
    
    // Build new XML with modifications
    let builder = DdexBuilder::new();
    let build_request = parsed.to_build_request();
    let new_xml = builder.build(&build_request)?;
    
    Ok(new_xml)
}
With Database Integration
use ddex_builder::{DdexBuilder, BuildRequest};
use sqlx::{PgPool, Row};
use serde_json;
async fn build_from_database(
    release_id: &str,
    pool: &PgPool,
) -> Result<String, Box<dyn std::error::Error>> {
    // Fetch release data from database
    let release_row = sqlx::query!(
        "SELECT * FROM releases WHERE id = $1",
        release_id
    )
    .fetch_one(pool)
    .await?;
    
    let tracks_rows = sqlx::query!(
        "SELECT * FROM tracks WHERE release_id = $1 ORDER BY position",
        release_id
    )
    .fetch_all(pool)
    .await?;
    
    // Convert database rows to DDEX models
    let release = Release {
        release_id: release_row.id,
        title: vec![LocalizedString {
            text: release_row.title,
            language_code: Some("en".to_string()),
            ..Default::default()
        }],
        display_artist: release_row.artist,
        upc: release_row.upc,
        tracks: tracks_rows.into_iter().map(|track| Track {
            title: track.title,
            position: track.position as u32,
            isrc: track.isrc,
            duration: track.duration_seconds.map(|d| std::time::Duration::from_secs(d as u64)),
            ..Default::default()
        }).collect(),
        ..Default::default()
    };
    
    let build_request = BuildRequest {
        message_header: MessageHeader {
            message_id: format!("REL_{}", release_id),
            message_sender_name: "My Label".to_string(),
            message_recipient_name: "Platform".to_string(),
            ..Default::default()
        },
        releases: vec![release],
        ..Default::default()
    };
    
    let builder = DdexBuilder::new();
    let xml = builder.build(&build_request)?;
    
    Ok(xml)
}
CLI Tool
The builder also provides a command-line interface:
# Install CLI
cargo install ddex-builder
# Build from JSON
ddex-builder build --from-json request.json --out release.xml
# Use preset
ddex-builder build --from-json request.json --preset spotify_audio_43
# Enable deterministic mode
ddex-builder build --from-json request.json --db-c14n --id stable-hash:v1
# Verify determinism
ddex-builder build --from-json request.json --verify-determinism 3
# Canonicalize existing XML
ddex-builder canon input.xml > canonical.xml
# Generate diff
ddex-builder diff --old original.xml --from-json modified.json
# Show preset information
ddex-builder preset-info spotify_audio_43
# Validate build request
ddex-builder preflight request.json
Performance Tips
Optimization Strategies
use ddex_builder::{DdexBuilder, BuildConfig, ValidationLevel};
fn create_optimized_builder() -> DdexBuilder {
    let config = BuildConfig {
        // Skip validation for trusted input
        validation_level: ValidationLevel::Basic,
        
        // Use fast ID generation for non-production builds
        id_strategy: IdStrategy::Sequential,
        
        // Disable determinism checks for speed
        determinism: DeterminismConfig {
            verify_determinism: false,
            emit_banner: false,
            ..Default::default()
        },
        
        // Reasonable memory limits
        memory_limit: Some(200 * 1024 * 1024), // 200MB
        
        ..Default::default()
    };
    
    DdexBuilder::with_config(config)
}
Memory Management
fn build_large_catalog_efficiently(
    releases: Vec<BuildRequest>,
) -> Result<Vec<String>, BuildError> {
    let builder = DdexBuilder::new();
    let mut results = Vec::new();
    
    // Process in chunks to manage memory
    for chunk in releases.chunks(100) {
        let chunk_results: Result<Vec<_>, _> = chunk
            .iter()
            .map(|request| builder.build(request))
            .collect();
        
        results.extend(chunk_results?);
        
        // Force garbage collection between chunks if needed
        // (This is more relevant for other languages)
    }
    
    Ok(results)
}
Documentation Links
- Core Types: ddex-core documentation
- Builder: ddex-builder documentation
- DB-C14N Spec: Canonicalization guide
- Examples: GitHub repository
Next Steps
- Parser API - Learn to parse DDEX XML in Rust
- Canonicalization - Understanding deterministic output
- Presets Guide - Platform-specific configurations
- Examples - Complete working examples