ddex_core/
namespace.rs

1//! # DDEX Namespace Management
2//!
3//! This module provides comprehensive namespace management for the DDEX Suite,
4//! handling multiple DDEX versions and custom extensions with proper prefix
5//! management and collision detection.
6
7use crate::models::versions::ERNVersion;
8use indexmap::{IndexMap, IndexSet};
9
10/// DDEX namespace registry for comprehensive namespace management
11#[derive(Debug, Clone)]
12pub struct NamespaceRegistry {
13    /// Known DDEX namespace URIs mapped to their information
14    namespaces: IndexMap<String, NamespaceInfo>,
15    /// Default prefixes for known namespaces
16    default_prefixes: IndexMap<String, String>,
17    /// Custom namespace registrations
18    custom_namespaces: IndexMap<String, NamespaceInfo>,
19    /// Prefix collision detection
20    reserved_prefixes: IndexSet<String>,
21}
22
23/// Information about a namespace
24#[derive(Debug, Clone, PartialEq)]
25pub struct NamespaceInfo {
26    /// The namespace URI
27    pub uri: String,
28    /// Preferred prefix
29    pub preferred_prefix: String,
30    /// Alternative acceptable prefixes
31    pub alternative_prefixes: Vec<String>,
32    /// DDEX standard this namespace belongs to
33    pub standard: DDEXStandard,
34    /// Version information if applicable
35    pub version: Option<String>,
36    /// Whether this namespace is required for the standard
37    pub required: bool,
38}
39
40/// DDEX standards supported
41#[derive(Debug, Clone, PartialEq)]
42pub enum DDEXStandard {
43    /// Electronic Release Notification
44    ERN,
45    /// Audio Video Vocabulary
46    AVS,
47    /// Musical Work Electronic Delivery
48    MEAD,
49    /// Performer Information Exchange
50    PIE,
51    /// Recording Information Notification
52    RIN,
53    /// Rights and Remuneration Information
54    RRI,
55    /// Sales and Usage Reporting
56    DSRF,
57    /// XML Schema Instance
58    XMLSchema,
59    /// Custom/Unknown standard
60    Custom(String),
61}
62
63/// Namespace scope tracking for inheritance
64#[derive(Debug, Clone)]
65pub struct NamespaceScope {
66    /// Active namespace declarations at this scope
67    pub declarations: IndexMap<String, String>, // prefix -> uri
68    /// Parent scope for inheritance
69    pub parent: Option<Box<NamespaceScope>>,
70    /// Element depth for debugging
71    pub depth: usize,
72}
73
74/// Namespace conflict resolution strategy
75#[derive(Debug, Clone, PartialEq)]
76pub enum ConflictResolution {
77    /// Use the first declared prefix
78    PreferFirst,
79    /// Use the most recently declared prefix
80    PreferLatest,
81    /// Generate a unique prefix
82    GenerateUnique,
83    /// Throw an error
84    Error,
85}
86
87impl NamespaceRegistry {
88    /// Create a new namespace registry with all known DDEX namespaces
89    pub fn new() -> Self {
90        let mut registry = Self {
91            namespaces: IndexMap::new(),
92            default_prefixes: IndexMap::new(),
93            custom_namespaces: IndexMap::new(),
94            reserved_prefixes: IndexSet::new(),
95        };
96
97        registry.initialize_ddex_namespaces();
98        registry
99    }
100
101    /// Initialize all known DDEX namespaces
102    fn initialize_ddex_namespaces(&mut self) {
103        // ERN namespaces
104        self.register_namespace(NamespaceInfo {
105            uri: "http://ddex.net/xml/ern/382".to_string(),
106            preferred_prefix: "ern".to_string(),
107            alternative_prefixes: vec!["ern382".to_string()],
108            standard: DDEXStandard::ERN,
109            version: Some("3.8.2".to_string()),
110            required: true,
111        });
112
113        self.register_namespace(NamespaceInfo {
114            uri: "http://ddex.net/xml/ern/42".to_string(),
115            preferred_prefix: "ern".to_string(),
116            alternative_prefixes: vec!["ern42".to_string()],
117            standard: DDEXStandard::ERN,
118            version: Some("4.2".to_string()),
119            required: true,
120        });
121
122        self.register_namespace(NamespaceInfo {
123            uri: "http://ddex.net/xml/ern/43".to_string(),
124            preferred_prefix: "ern".to_string(),
125            alternative_prefixes: vec!["ern43".to_string()],
126            standard: DDEXStandard::ERN,
127            version: Some("4.3".to_string()),
128            required: true,
129        });
130
131        // AVS namespaces
132        self.register_namespace(NamespaceInfo {
133            uri: "http://ddex.net/xml/avs".to_string(),
134            preferred_prefix: "avs".to_string(),
135            alternative_prefixes: vec!["ddexavs".to_string()],
136            standard: DDEXStandard::AVS,
137            version: None,
138            required: false,
139        });
140
141        self.register_namespace(NamespaceInfo {
142            uri: "http://ddex.net/xml/avs/avs".to_string(),
143            preferred_prefix: "avs".to_string(),
144            alternative_prefixes: vec!["ddexavs".to_string()],
145            standard: DDEXStandard::AVS,
146            version: None,
147            required: false,
148        });
149
150        // MEAD namespaces
151        self.register_namespace(NamespaceInfo {
152            uri: "http://ddex.net/xml/mead/mead".to_string(),
153            preferred_prefix: "mead".to_string(),
154            alternative_prefixes: vec!["ddexmead".to_string()],
155            standard: DDEXStandard::MEAD,
156            version: None,
157            required: false,
158        });
159
160        // PIE namespaces
161        self.register_namespace(NamespaceInfo {
162            uri: "http://ddex.net/xml/pie/pie".to_string(),
163            preferred_prefix: "pie".to_string(),
164            alternative_prefixes: vec!["ddexpie".to_string()],
165            standard: DDEXStandard::PIE,
166            version: None,
167            required: false,
168        });
169
170        // RIN namespaces
171        self.register_namespace(NamespaceInfo {
172            uri: "http://ddex.net/xml/rin/rin".to_string(),
173            preferred_prefix: "rin".to_string(),
174            alternative_prefixes: vec!["ddexrin".to_string()],
175            standard: DDEXStandard::RIN,
176            version: None,
177            required: false,
178        });
179
180        // XML Schema Instance
181        self.register_namespace(NamespaceInfo {
182            uri: "http://www.w3.org/2001/XMLSchema-instance".to_string(),
183            preferred_prefix: "xsi".to_string(),
184            alternative_prefixes: vec!["xmlschema".to_string()],
185            standard: DDEXStandard::XMLSchema,
186            version: None,
187            required: false,
188        });
189
190        // XML Schema
191        self.register_namespace(NamespaceInfo {
192            uri: "http://www.w3.org/2001/XMLSchema".to_string(),
193            preferred_prefix: "xs".to_string(),
194            alternative_prefixes: vec!["xsd".to_string(), "schema".to_string()],
195            standard: DDEXStandard::XMLSchema,
196            version: None,
197            required: false,
198        });
199
200        // Common extension namespaces
201        self.register_namespace(NamespaceInfo {
202            uri: "http://ddex.net/xml/gc".to_string(),
203            preferred_prefix: "gc".to_string(),
204            alternative_prefixes: vec!["ddexgc".to_string()],
205            standard: DDEXStandard::Custom("GC".to_string()),
206            version: None,
207            required: false,
208        });
209    }
210
211    /// Register a new namespace
212    pub fn register_namespace(&mut self, info: NamespaceInfo) {
213        self.default_prefixes
214            .insert(info.uri.clone(), info.preferred_prefix.clone());
215        self.reserved_prefixes.insert(info.preferred_prefix.clone());
216
217        for alt_prefix in &info.alternative_prefixes {
218            self.reserved_prefixes.insert(alt_prefix.clone());
219        }
220
221        self.namespaces.insert(info.uri.clone(), info);
222    }
223
224    /// Register a custom namespace
225    pub fn register_custom_namespace(&mut self, info: NamespaceInfo) -> Result<(), NamespaceError> {
226        // Check for URI conflicts
227        if self.namespaces.contains_key(&info.uri) || self.custom_namespaces.contains_key(&info.uri)
228        {
229            return Err(NamespaceError::UriConflict(info.uri));
230        }
231
232        // Check for prefix conflicts
233        if self.is_prefix_reserved(&info.preferred_prefix) {
234            return Err(NamespaceError::PrefixConflict(info.preferred_prefix));
235        }
236
237        self.reserved_prefixes.insert(info.preferred_prefix.clone());
238        self.custom_namespaces.insert(info.uri.clone(), info);
239        Ok(())
240    }
241
242    /// Detect DDEX version from namespace URI
243    pub fn detect_version(&self, namespace_uri: &str) -> Option<ERNVersion> {
244        match namespace_uri {
245            "http://ddex.net/xml/ern/382" => Some(ERNVersion::V3_8_2),
246            "http://ddex.net/xml/ern/42" => Some(ERNVersion::V4_2),
247            "http://ddex.net/xml/ern/43" => Some(ERNVersion::V4_3),
248            _ => None,
249        }
250    }
251
252    /// Get all namespace URIs for a specific ERN version
253    pub fn get_version_namespaces(&self, version: &ERNVersion) -> Vec<String> {
254        let mut namespaces = vec![];
255
256        // Add the main ERN namespace
257        match version {
258            ERNVersion::V3_8_2 => namespaces.push("http://ddex.net/xml/ern/382".to_string()),
259            ERNVersion::V4_2 => namespaces.push("http://ddex.net/xml/ern/42".to_string()),
260            ERNVersion::V4_3 => namespaces.push("http://ddex.net/xml/ern/43".to_string()),
261        }
262
263        // Add common supporting namespaces
264        namespaces.push("http://ddex.net/xml/avs".to_string());
265        namespaces.push("http://www.w3.org/2001/XMLSchema-instance".to_string());
266
267        namespaces
268    }
269
270    /// Get preferred prefix for a namespace URI
271    pub fn get_preferred_prefix(&self, uri: &str) -> Option<&str> {
272        self.default_prefixes
273            .get(uri)
274            .map(|s| s.as_str())
275            .or_else(|| {
276                self.custom_namespaces
277                    .get(uri)
278                    .map(|info| info.preferred_prefix.as_str())
279            })
280    }
281
282    /// Get namespace info by URI
283    pub fn get_namespace_info(&self, uri: &str) -> Option<&NamespaceInfo> {
284        self.namespaces
285            .get(uri)
286            .or_else(|| self.custom_namespaces.get(uri))
287    }
288
289    /// Check if a prefix is reserved
290    pub fn is_prefix_reserved(&self, prefix: &str) -> bool {
291        self.reserved_prefixes.contains(prefix)
292    }
293
294    /// Generate a unique prefix for a namespace
295    pub fn generate_unique_prefix(&self, base_prefix: &str) -> String {
296        if !self.is_prefix_reserved(base_prefix) {
297            return base_prefix.to_string();
298        }
299
300        let mut counter = 1;
301        loop {
302            let candidate = format!("{}{}", base_prefix, counter);
303            if !self.is_prefix_reserved(&candidate) {
304                return candidate;
305            }
306            counter += 1;
307        }
308    }
309
310    /// Resolve namespace conflicts using the specified strategy
311    pub fn resolve_prefix_conflict(
312        &self,
313        _uri: &str,
314        existing_prefix: &str,
315        new_prefix: &str,
316        strategy: ConflictResolution,
317    ) -> Result<String, NamespaceError> {
318        match strategy {
319            ConflictResolution::PreferFirst => Ok(existing_prefix.to_string()),
320            ConflictResolution::PreferLatest => Ok(new_prefix.to_string()),
321            ConflictResolution::GenerateUnique => Ok(self.generate_unique_prefix(new_prefix)),
322            ConflictResolution::Error => {
323                Err(NamespaceError::PrefixConflict(new_prefix.to_string()))
324            }
325        }
326    }
327
328    /// Create minimal namespace declarations for root element
329    pub fn create_minimal_declarations(
330        &self,
331        used_namespaces: &[String],
332    ) -> IndexMap<String, String> {
333        let mut declarations = IndexMap::new();
334
335        for uri in used_namespaces {
336            if let Some(prefix) = self.get_preferred_prefix(uri) {
337                declarations.insert(prefix.to_string(), uri.clone());
338            }
339        }
340
341        declarations
342    }
343
344    /// Validate namespace declarations against known namespaces
345    pub fn validate_declarations(
346        &self,
347        declarations: &IndexMap<String, String>,
348    ) -> Vec<NamespaceWarning> {
349        let mut warnings = Vec::new();
350
351        for (prefix, uri) in declarations {
352            if let Some(info) = self.get_namespace_info(uri) {
353                // Check if using non-preferred prefix
354                if prefix != &info.preferred_prefix && !info.alternative_prefixes.contains(prefix) {
355                    warnings.push(NamespaceWarning::NonStandardPrefix {
356                        uri: uri.clone(),
357                        used_prefix: prefix.clone(),
358                        preferred_prefix: info.preferred_prefix.clone(),
359                    });
360                }
361            } else {
362                // Unknown namespace
363                warnings.push(NamespaceWarning::UnknownNamespace {
364                    uri: uri.clone(),
365                    prefix: prefix.clone(),
366                });
367            }
368        }
369
370        warnings
371    }
372
373    /// Get all registered namespaces for a standard
374    pub fn get_namespaces_by_standard(&self, standard: &DDEXStandard) -> Vec<&NamespaceInfo> {
375        self.namespaces
376            .values()
377            .chain(self.custom_namespaces.values())
378            .filter(|info| &info.standard == standard)
379            .collect()
380    }
381}
382
383impl NamespaceScope {
384    /// Create a new root scope
385    pub fn new() -> Self {
386        Self {
387            declarations: IndexMap::new(),
388            parent: None,
389            depth: 0,
390        }
391    }
392
393    /// Create a child scope
394    pub fn new_child(&self) -> Self {
395        Self {
396            declarations: IndexMap::new(),
397            parent: Some(Box::new(self.clone())),
398            depth: self.depth + 1,
399        }
400    }
401
402    /// Add a namespace declaration to this scope
403    pub fn declare_namespace(&mut self, prefix: String, uri: String) {
404        self.declarations.insert(prefix, uri);
405    }
406
407    /// Resolve a prefix to its URI, checking parent scopes
408    pub fn resolve_prefix(&self, prefix: &str) -> Option<String> {
409        if let Some(uri) = self.declarations.get(prefix) {
410            Some(uri.clone())
411        } else if let Some(parent) = &self.parent {
412            parent.resolve_prefix(prefix)
413        } else {
414            None
415        }
416    }
417
418    /// Get all active namespace declarations (including inherited)
419    pub fn get_all_declarations(&self) -> IndexMap<String, String> {
420        let mut all_declarations = IndexMap::new();
421
422        // Start with parent declarations
423        if let Some(parent) = &self.parent {
424            all_declarations = parent.get_all_declarations();
425        }
426
427        // Override with current scope declarations
428        for (prefix, uri) in &self.declarations {
429            all_declarations.insert(prefix.clone(), uri.clone());
430        }
431
432        all_declarations
433    }
434
435    /// Check if a namespace is declared in this scope or parents
436    pub fn is_namespace_declared(&self, uri: &str) -> bool {
437        self.declarations
438            .values()
439            .any(|declared_uri| declared_uri == uri)
440            || self
441                .parent
442                .as_ref()
443                .is_some_and(|parent| parent.is_namespace_declared(uri))
444    }
445
446    /// Find the prefix for a namespace URI
447    pub fn find_prefix_for_uri(&self, uri: &str) -> Option<String> {
448        for (prefix, declared_uri) in &self.declarations {
449            if declared_uri == uri {
450                return Some(prefix.clone());
451            }
452        }
453
454        if let Some(parent) = &self.parent {
455            parent.find_prefix_for_uri(uri)
456        } else {
457            None
458        }
459    }
460}
461
462/// Namespace-related errors
463#[derive(Debug, Clone, PartialEq)]
464pub enum NamespaceError {
465    /// URI conflict with existing namespace
466    UriConflict(String),
467    /// Prefix conflict with reserved prefix
468    PrefixConflict(String),
469    /// Invalid namespace URI format
470    InvalidUri(String),
471    /// Circular namespace dependency
472    CircularDependency(Vec<String>),
473}
474
475impl std::fmt::Display for NamespaceError {
476    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477        match self {
478            NamespaceError::UriConflict(uri) => write!(f, "Namespace URI conflict: {}", uri),
479            NamespaceError::PrefixConflict(prefix) => write!(f, "Prefix conflict: {}", prefix),
480            NamespaceError::InvalidUri(uri) => write!(f, "Invalid namespace URI: {}", uri),
481            NamespaceError::CircularDependency(chain) => {
482                write!(f, "Circular namespace dependency: {}", chain.join(" -> "))
483            }
484        }
485    }
486}
487
488impl std::error::Error for NamespaceError {}
489
490/// Namespace-related warnings
491#[derive(Debug, Clone, PartialEq)]
492pub enum NamespaceWarning {
493    /// Using non-standard prefix for known namespace
494    NonStandardPrefix {
495        uri: String,
496        used_prefix: String,
497        preferred_prefix: String,
498    },
499    /// Unknown/unregistered namespace
500    UnknownNamespace { uri: String, prefix: String },
501    /// Redundant namespace declaration
502    RedundantDeclaration { uri: String, prefix: String },
503}
504
505impl std::fmt::Display for NamespaceWarning {
506    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
507        match self {
508            NamespaceWarning::NonStandardPrefix {
509                uri,
510                used_prefix,
511                preferred_prefix,
512            } => {
513                write!(
514                    f,
515                    "Non-standard prefix '{}' for namespace '{}', prefer '{}'",
516                    used_prefix, uri, preferred_prefix
517                )
518            }
519            NamespaceWarning::UnknownNamespace { uri, prefix } => {
520                write!(f, "Unknown namespace '{}' with prefix '{}'", uri, prefix)
521            }
522            NamespaceWarning::RedundantDeclaration { uri, prefix } => {
523                write!(
524                    f,
525                    "Redundant declaration of namespace '{}' with prefix '{}'",
526                    uri, prefix
527                )
528            }
529        }
530    }
531}
532
533impl Default for NamespaceRegistry {
534    fn default() -> Self {
535        Self::new()
536    }
537}
538
539impl Default for NamespaceScope {
540    fn default() -> Self {
541        Self::new()
542    }
543}
544
545#[cfg(test)]
546mod tests {
547    use super::*;
548
549    #[test]
550    fn test_namespace_registry_creation() {
551        let registry = NamespaceRegistry::new();
552        assert!(registry
553            .get_preferred_prefix("http://ddex.net/xml/ern/43")
554            .is_some());
555        assert_eq!(
556            registry.get_preferred_prefix("http://ddex.net/xml/ern/43"),
557            Some("ern")
558        );
559    }
560
561    #[test]
562    fn test_version_detection() {
563        let registry = NamespaceRegistry::new();
564        assert_eq!(
565            registry.detect_version("http://ddex.net/xml/ern/382"),
566            Some(ERNVersion::V3_8_2)
567        );
568        assert_eq!(
569            registry.detect_version("http://ddex.net/xml/ern/42"),
570            Some(ERNVersion::V4_2)
571        );
572        assert_eq!(
573            registry.detect_version("http://ddex.net/xml/ern/43"),
574            Some(ERNVersion::V4_3)
575        );
576        assert_eq!(
577            registry.detect_version("http://unknown.com/namespace"),
578            None
579        );
580    }
581
582    #[test]
583    fn test_custom_namespace_registration() {
584        let mut registry = NamespaceRegistry::new();
585
586        let custom_ns = NamespaceInfo {
587            uri: "http://example.com/custom".to_string(),
588            preferred_prefix: "ex".to_string(),
589            alternative_prefixes: vec!["example".to_string()],
590            standard: DDEXStandard::Custom("Example".to_string()),
591            version: None,
592            required: false,
593        };
594
595        assert!(registry.register_custom_namespace(custom_ns).is_ok());
596        assert_eq!(
597            registry.get_preferred_prefix("http://example.com/custom"),
598            Some("ex")
599        );
600    }
601
602    #[test]
603    fn test_prefix_conflict_detection() {
604        let mut registry = NamespaceRegistry::new();
605
606        let conflicting_ns = NamespaceInfo {
607            uri: "http://example.com/conflict".to_string(),
608            preferred_prefix: "ern".to_string(), // Conflicts with existing ERN prefix
609            alternative_prefixes: vec![],
610            standard: DDEXStandard::Custom("Conflict".to_string()),
611            version: None,
612            required: false,
613        };
614
615        assert!(matches!(
616            registry.register_custom_namespace(conflicting_ns),
617            Err(NamespaceError::PrefixConflict(_))
618        ));
619    }
620
621    #[test]
622    fn test_namespace_scope() {
623        let mut root_scope = NamespaceScope::new();
624        root_scope.declare_namespace("ern".to_string(), "http://ddex.net/xml/ern/43".to_string());
625
626        let mut child_scope = root_scope.new_child();
627        child_scope.declare_namespace("avs".to_string(), "http://ddex.net/xml/avs".to_string());
628
629        // Child should resolve both its own and parent declarations
630        assert_eq!(
631            child_scope.resolve_prefix("ern"),
632            Some("http://ddex.net/xml/ern/43".to_string())
633        );
634        assert_eq!(
635            child_scope.resolve_prefix("avs"),
636            Some("http://ddex.net/xml/avs".to_string())
637        );
638
639        // Parent should not see child declarations
640        assert_eq!(root_scope.resolve_prefix("avs"), None);
641    }
642
643    #[test]
644    fn test_unique_prefix_generation() {
645        let mut registry = NamespaceRegistry::new();
646
647        // Reserve some test prefixes
648        registry.reserved_prefixes.insert("test".to_string());
649        registry.reserved_prefixes.insert("test1".to_string());
650
651        let unique = registry.generate_unique_prefix("test");
652        assert_eq!(unique, "test2");
653    }
654
655    #[test]
656    fn test_minimal_declarations() {
657        let registry = NamespaceRegistry::new();
658        let used_namespaces = vec![
659            "http://ddex.net/xml/ern/43".to_string(),
660            "http://ddex.net/xml/avs".to_string(),
661        ];
662
663        let declarations = registry.create_minimal_declarations(&used_namespaces);
664        assert_eq!(
665            declarations.get("ern"),
666            Some(&"http://ddex.net/xml/ern/43".to_string())
667        );
668        assert_eq!(
669            declarations.get("avs"),
670            Some(&"http://ddex.net/xml/avs".to_string())
671        );
672    }
673}