1use crate::{CmlError, Result};
33use serde::{Deserialize, Serialize};
34use std::collections::{HashMap, HashSet};
35use std::path::Path;
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Profile {
40 pub name: String,
42
43 pub version: String,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub extends: Option<String>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub description: Option<String>,
53
54 #[serde(default, skip_serializing_if = "Vec::is_empty")]
56 pub include: Vec<String>,
57
58 #[serde(default, skip_serializing_if = "Vec::is_empty")]
60 pub exclude: Vec<String>,
61
62 #[serde(default)]
64 pub elements: HashMap<String, ElementDef>,
65
66 #[serde(default)]
68 pub attributes: HashMap<String, AttributeDef>,
69
70 #[serde(default)]
72 pub types: HashMap<String, Vec<String>>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ElementDef {
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub description: Option<String>,
81
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub content: Option<String>,
85
86 #[serde(default)]
88 pub attributes: HashMap<String, AttributeDef>,
89
90 #[serde(default, skip_serializing_if = "Vec::is_empty")]
92 pub children: Vec<String>,
93
94 #[serde(default, skip_serializing_if = "Vec::is_empty")]
96 pub parents: Vec<String>,
97
98 #[serde(default, skip_serializing_if = "Vec::is_empty")]
100 pub required_children: Vec<String>,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub min_occurs: Option<u32>,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub max_occurs: Option<u32>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct AttributeDef {
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub description: Option<String>,
117
118 #[serde(rename = "type", default = "default_attr_type")]
120 pub attr_type: String,
121
122 #[serde(default)]
124 pub required: bool,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub default: Option<String>,
129
130 #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
132 pub enum_values: Vec<String>,
133
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub pattern: Option<String>,
137}
138
139fn default_attr_type() -> String {
140 "string".to_string()
141}
142
143#[derive(Debug, Clone, Default, Serialize, Deserialize)]
149pub struct ProfileConstraints {
150 #[serde(default)]
152 pub profile: String,
153
154 #[serde(default)]
156 pub version: String,
157
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub extends: Option<String>,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub description: Option<String>,
165
166 #[serde(default)]
168 pub document: Option<DocumentConstraint>,
169
170 #[serde(default)]
172 pub elements: HashMap<String, ElementConstraint>,
173
174 #[serde(default)]
176 pub attributes: HashMap<String, AttributeConstraint>,
177
178 #[serde(default)]
180 pub hierarchy: HashMap<String, HierarchyConstraint>,
181
182 #[serde(default)]
184 pub nesting: Option<NestingConstraint>,
185
186 #[serde(default)]
188 pub list_constraints: Option<ListConstraints>,
189
190 #[serde(default)]
192 pub semantic_rules: HashMap<String, SemanticRule>,
193}
194
195#[derive(Debug, Clone, Default, Serialize, Deserialize)]
197pub struct DocumentConstraint {
198 #[serde(default)]
200 pub required_attributes: Vec<String>,
201
202 #[serde(default)]
204 pub required_children: Vec<String>,
205
206 #[serde(default)]
208 pub child_order: Vec<String>,
209}
210
211#[derive(Debug, Clone, Default, Serialize, Deserialize)]
213pub struct ElementConstraint {
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub min_occurs: Option<u32>,
217
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub max_occurs: Option<u32>,
221
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub min_children: Option<u32>,
225
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub max_children: Option<u32>,
229
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub min_content: Option<u32>,
233
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub min_length: Option<u32>,
237
238 #[serde(default)]
240 pub required_attributes: Vec<String>,
241
242 #[serde(default)]
244 pub required_children: Vec<String>,
245
246 #[serde(default)]
248 pub required_child_types: Vec<String>,
249
250 #[serde(default)]
252 pub preserve_whitespace: bool,
253
254 #[serde(default)]
256 pub no_nesting: bool,
257
258 #[serde(default)]
260 pub allow_nesting: bool,
261
262 #[serde(default)]
264 pub reserved: bool,
265
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub warning: Option<String>,
269
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub size: Option<SizeConstraint>,
273}
274
275#[derive(Debug, Clone, Default, Serialize, Deserialize)]
277pub struct SizeConstraint {
278 pub min: Option<u32>,
279 pub max: Option<u32>,
280}
281
282#[derive(Debug, Clone, Default, Serialize, Deserialize)]
284pub struct AttributeConstraint {
285 #[serde(skip_serializing_if = "Option::is_none")]
287 pub description: Option<String>,
288
289 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
291 pub attr_type: Option<String>,
292
293 #[serde(skip_serializing_if = "Option::is_none")]
295 pub format: Option<String>,
296
297 #[serde(skip_serializing_if = "Option::is_none")]
299 pub pattern: Option<String>,
300
301 #[serde(rename = "enum", default)]
303 pub enum_values: Vec<String>,
304
305 #[serde(default)]
307 pub recommended: Vec<String>,
308
309 #[serde(default)]
311 pub unique: bool,
312
313 #[serde(skip_serializing_if = "Option::is_none")]
315 pub min: Option<i32>,
316
317 #[serde(skip_serializing_if = "Option::is_none")]
319 pub max: Option<i32>,
320
321 #[serde(skip_serializing_if = "Option::is_none")]
323 pub default: Option<serde_json::Value>,
324}
325
326#[derive(Debug, Clone, Default, Serialize, Deserialize)]
328pub struct HierarchyConstraint {
329 #[serde(default)]
331 pub allowed_parents: Vec<String>,
332
333 #[serde(default)]
335 pub allowed_children: Vec<String>,
336
337 #[serde(skip_serializing_if = "Option::is_none")]
339 pub max_occurs: Option<u32>,
340
341 #[serde(default)]
343 pub must_be_first: bool,
344}
345
346#[derive(Debug, Clone, Default, Serialize, Deserialize)]
348pub struct NestingConstraint {
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub max_section_depth: Option<u32>,
352
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub max_inline_depth: Option<u32>,
356
357 #[serde(default)]
359 pub no_self_nesting: Vec<String>,
360}
361
362#[derive(Debug, Clone, Default, Serialize, Deserialize)]
364pub struct ListConstraints {
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub ordered: Option<OrderedListConstraint>,
368
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub unordered: Option<UnorderedListConstraint>,
372}
373
374#[derive(Debug, Clone, Default, Serialize, Deserialize)]
376pub struct OrderedListConstraint {
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub enforce_order: Option<String>,
380
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub description: Option<String>,
384}
385
386#[derive(Debug, Clone, Default, Serialize, Deserialize)]
388pub struct UnorderedListConstraint {
389 #[serde(skip_serializing_if = "Option::is_none")]
391 pub description: Option<String>,
392}
393
394#[derive(Debug, Clone, Default, Serialize, Deserialize)]
396pub struct SemanticRule {
397 #[serde(skip_serializing_if = "Option::is_none")]
399 pub description: Option<String>,
400
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub prefer: Option<String>,
404
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub instead_of: Option<String>,
408}
409
410#[derive(Debug, Clone, Default)]
412pub struct ResolvedConstraints {
413 pub profile: String,
415
416 pub document: Option<DocumentConstraint>,
418
419 pub elements: HashMap<String, ElementConstraint>,
421
422 pub attributes: HashMap<String, AttributeConstraint>,
424
425 pub hierarchy: HashMap<String, HierarchyConstraint>,
427
428 pub nesting: Option<NestingConstraint>,
430
431 pub list_constraints: Option<ListConstraints>,
433
434 pub semantic_rules: HashMap<String, SemanticRule>,
436}
437
438#[derive(Debug, Clone)]
440pub struct ResolvedProfile {
441 pub name: String,
443
444 pub version: String,
446
447 pub elements: HashMap<String, ElementDef>,
449
450 pub types: HashMap<String, Vec<String>>,
452}
453
454pub struct ProfileRegistry {
456 profiles: HashMap<String, Profile>,
458
459 resolved: HashMap<String, ResolvedProfile>,
461}
462
463impl ProfileRegistry {
464 pub fn new() -> Self {
466 Self {
467 profiles: HashMap::new(),
468 resolved: HashMap::new(),
469 }
470 }
471
472 pub fn with_builtins() -> Result<Self> {
474 let mut registry = Self::new();
475 registry.load_builtin_profiles()?;
476 Ok(registry)
477 }
478
479 fn load_builtin_profiles(&mut self) -> Result<()> {
481 let core: Profile = serde_json::from_str(include_str!(
483 "../schemas/0.2/profiles/core/core.json"
484 ))
485 .map_err(|e| CmlError::ValidationError(format!("Failed to parse core profile: {}", e)))?;
486 self.profiles.insert("core".to_string(), core);
487
488 let standard: Profile = serde_json::from_str(include_str!(
490 "../schemas/0.2/profiles/standard/standard.json"
491 ))
492 .map_err(|e| {
493 CmlError::ValidationError(format!("Failed to parse standard profile: {}", e))
494 })?;
495 self.profiles.insert("standard".to_string(), standard);
496
497 let legal: Profile = serde_json::from_str(include_str!(
499 "../schemas/0.2/profiles/legal/legal.json"
500 ))
501 .map_err(|e| CmlError::ValidationError(format!("Failed to parse legal profile: {}", e)))?;
502 self.profiles.insert("legal".to_string(), legal);
503
504 let legal_constitution: Profile = serde_json::from_str(include_str!(
506 "../schemas/0.2/profiles/legal/constitution/constitution.json"
507 ))
508 .map_err(|e| {
509 CmlError::ValidationError(format!("Failed to parse legal:constitution profile: {}", e))
510 })?;
511 self.profiles.insert("legal:constitution".to_string(), legal_constitution);
512
513 let code: Profile = serde_json::from_str(include_str!(
515 "../schemas/0.2/profiles/code/code.json"
516 ))
517 .map_err(|e| CmlError::ValidationError(format!("Failed to parse code profile: {}", e)))?;
518 self.profiles.insert("code".to_string(), code);
519
520 let code_api: Profile = serde_json::from_str(include_str!(
522 "../schemas/0.2/profiles/code/api/api.json"
523 ))
524 .map_err(|e| {
525 CmlError::ValidationError(format!("Failed to parse code:api profile: {}", e))
526 })?;
527 self.profiles.insert("code:api".to_string(), code_api);
528
529 let wiki: Profile = serde_json::from_str(include_str!(
531 "../schemas/0.2/profiles/wiki/wiki.json"
532 ))
533 .map_err(|e| {
534 CmlError::ValidationError(format!("Failed to parse wiki profile: {}", e))
535 })?;
536 self.profiles.insert("wiki".to_string(), wiki);
537
538 Ok(())
539 }
540
541 pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
543 let content = std::fs::read_to_string(path.as_ref())?;
544 let profile: Profile = serde_json::from_str(&content).map_err(|e| {
545 CmlError::ValidationError(format!("Failed to parse profile: {}", e))
546 })?;
547 self.profiles.insert(profile.name.clone(), profile);
548 Ok(())
549 }
550
551 pub fn load_from_str(&mut self, json: &str) -> Result<()> {
553 let profile: Profile = serde_json::from_str(json)
554 .map_err(|e| CmlError::ValidationError(format!("Failed to parse profile: {}", e)))?;
555 self.profiles.insert(profile.name.clone(), profile);
556 Ok(())
557 }
558
559 pub fn get(&mut self, name: &str) -> Result<&ResolvedProfile> {
561 if self.resolved.contains_key(name) {
563 return Ok(self.resolved.get(name).unwrap());
564 }
565
566 let resolved = self.resolve_profile(name)?;
568 self.resolved.insert(name.to_string(), resolved);
569 Ok(self.resolved.get(name).unwrap())
570 }
571
572 fn resolve_profile(&self, name: &str) -> Result<ResolvedProfile> {
574 let profile = self.profiles.get(name).ok_or_else(|| {
575 CmlError::ValidationError(format!("Profile not found: {}", name))
576 })?;
577
578 let mut elements: HashMap<String, ElementDef> = HashMap::new();
580 let mut types: HashMap<String, Vec<String>> = HashMap::new();
581
582 if let Some(parent_name) = &profile.extends {
583 let parent = self.resolve_profile(parent_name)?;
584
585 if !profile.include.is_empty() {
587 let include_set: HashSet<&str> = profile.include.iter().map(|s| s.as_str()).collect();
589 for (name, def) in parent.elements {
590 if include_set.contains(name.as_str()) {
591 elements.insert(name, def);
592 }
593 }
594 } else if !profile.exclude.is_empty() {
595 let exclude_set: HashSet<&str> = profile.exclude.iter().map(|s| s.as_str()).collect();
597 for (name, def) in parent.elements {
598 if !exclude_set.contains(name.as_str()) {
599 elements.insert(name, def);
600 }
601 }
602 } else {
603 elements = parent.elements;
605 }
606
607 types = parent.types;
609 }
610
611 for (name, def) in &profile.elements {
613 elements.insert(name.clone(), def.clone());
614 }
615
616 for (name, values) in &profile.types {
618 types.insert(name.clone(), values.clone());
619 }
620
621 Ok(ResolvedProfile {
622 name: profile.name.clone(),
623 version: profile.version.clone(),
624 elements,
625 types,
626 })
627 }
628
629 pub fn is_element_allowed(&mut self, profile: &str, element: &str) -> Result<bool> {
631 let resolved = self.get(profile)?;
632 Ok(resolved.elements.contains_key(element))
633 }
634
635 pub fn get_type_values(&mut self, profile: &str, type_name: &str) -> Result<Option<Vec<String>>> {
637 let resolved = self.get(profile)?;
638 Ok(resolved.types.get(type_name).cloned())
639 }
640
641 pub fn validate_type_value(
643 &mut self,
644 profile: &str,
645 type_name: &str,
646 value: &str,
647 ) -> Result<bool> {
648 let resolved = self.get(profile)?;
649 match resolved.types.get(type_name) {
650 Some(values) => Ok(values.contains(&value.to_string())),
651 None => Ok(true), }
653 }
654}
655
656impl Default for ProfileRegistry {
657 fn default() -> Self {
658 Self::new()
659 }
660}
661
662pub struct ConstraintRegistry {
668 constraints: HashMap<String, ProfileConstraints>,
670
671 resolved: HashMap<String, ResolvedConstraints>,
673}
674
675impl ConstraintRegistry {
676 pub fn new() -> Self {
678 Self {
679 constraints: HashMap::new(),
680 resolved: HashMap::new(),
681 }
682 }
683
684 pub fn with_builtins() -> Result<Self> {
686 let mut registry = Self::new();
687 registry.load_builtin_constraints()?;
688 Ok(registry)
689 }
690
691 fn load_builtin_constraints(&mut self) -> Result<()> {
693 let core: ProfileConstraints = serde_json::from_str(include_str!(
695 "../schemas/0.2/profiles/core/constraints.json"
696 ))
697 .map_err(|e| {
698 CmlError::ValidationError(format!("Failed to parse core constraints: {}", e))
699 })?;
700 self.constraints.insert("core".to_string(), core);
701
702 let standard: ProfileConstraints = serde_json::from_str(include_str!(
704 "../schemas/0.2/profiles/standard/constraints.json"
705 ))
706 .map_err(|e| {
707 CmlError::ValidationError(format!("Failed to parse standard constraints: {}", e))
708 })?;
709 self.constraints.insert("standard".to_string(), standard);
710
711 let legal: ProfileConstraints = serde_json::from_str(include_str!(
713 "../schemas/0.2/profiles/legal/constraints.json"
714 ))
715 .map_err(|e| {
716 CmlError::ValidationError(format!("Failed to parse legal constraints: {}", e))
717 })?;
718 self.constraints.insert("legal".to_string(), legal);
719
720 let code: ProfileConstraints = serde_json::from_str(include_str!(
722 "../schemas/0.2/profiles/code/constraints.json"
723 ))
724 .map_err(|e| {
725 CmlError::ValidationError(format!("Failed to parse code constraints: {}", e))
726 })?;
727 self.constraints.insert("code".to_string(), code);
728
729 let wiki: ProfileConstraints = serde_json::from_str(include_str!(
731 "../schemas/0.2/profiles/wiki/constraints.json"
732 ))
733 .map_err(|e| {
734 CmlError::ValidationError(format!("Failed to parse wiki constraints: {}", e))
735 })?;
736 self.constraints.insert("wiki".to_string(), wiki);
737
738 Ok(())
739 }
740
741 pub fn load_from_str(&mut self, json: &str) -> Result<()> {
743 let constraints: ProfileConstraints = serde_json::from_str(json)
744 .map_err(|e| CmlError::ValidationError(format!("Failed to parse constraints: {}", e)))?;
745 self.constraints.insert(constraints.profile.clone(), constraints);
746 Ok(())
747 }
748
749 pub fn get(&mut self, name: &str) -> Result<&ResolvedConstraints> {
751 if self.resolved.contains_key(name) {
752 return Ok(self.resolved.get(name).unwrap());
753 }
754
755 let resolved = self.resolve_constraints(name)?;
756 self.resolved.insert(name.to_string(), resolved);
757 Ok(self.resolved.get(name).unwrap())
758 }
759
760 fn resolve_constraints(&self, name: &str) -> Result<ResolvedConstraints> {
762 let constraints = self.constraints.get(name).ok_or_else(|| {
763 CmlError::ValidationError(format!("Constraints not found: {}", name))
764 })?;
765
766 let mut resolved = ResolvedConstraints {
767 profile: name.to_string(),
768 ..Default::default()
769 };
770
771 if let Some(parent_name) = &constraints.extends {
773 let parent = self.resolve_constraints(parent_name)?;
774 resolved.document = parent.document;
775 resolved.elements = parent.elements;
776 resolved.attributes = parent.attributes;
777 resolved.hierarchy = parent.hierarchy;
778 resolved.nesting = parent.nesting;
779 resolved.list_constraints = parent.list_constraints;
780 resolved.semantic_rules = parent.semantic_rules;
781 }
782
783 if constraints.document.is_some() {
785 resolved.document = constraints.document.clone();
786 }
787
788 for (name, constraint) in &constraints.elements {
789 resolved.elements.insert(name.clone(), constraint.clone());
790 }
791
792 for (name, constraint) in &constraints.attributes {
793 resolved.attributes.insert(name.clone(), constraint.clone());
794 }
795
796 for (name, constraint) in &constraints.hierarchy {
797 resolved.hierarchy.insert(name.clone(), constraint.clone());
798 }
799
800 if constraints.nesting.is_some() {
801 resolved.nesting = constraints.nesting.clone();
802 }
803
804 if constraints.list_constraints.is_some() {
805 resolved.list_constraints = constraints.list_constraints.clone();
806 }
807
808 for (name, rule) in &constraints.semantic_rules {
809 resolved.semantic_rules.insert(name.clone(), rule.clone());
810 }
811
812 Ok(resolved)
813 }
814}
815
816impl Default for ConstraintRegistry {
817 fn default() -> Self {
818 Self::new()
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use super::*;
825
826 #[test]
827 fn test_load_core_profile() {
828 let json = r#"{
829 "name": "test-core",
830 "version": "0.2",
831 "elements": {
832 "cml": { "content": "block" },
833 "header": { "content": "block" },
834 "body": { "content": "block" },
835 "footer": { "content": "block" }
836 }
837 }"#;
838
839 let profile: Profile = serde_json::from_str(json).unwrap();
840 assert_eq!(profile.name, "test-core");
841 assert_eq!(profile.elements.len(), 4);
842 }
843
844 #[test]
845 fn test_profile_inheritance() {
846 let mut registry = ProfileRegistry::new();
847
848 registry
850 .load_from_str(
851 r#"{
852 "name": "parent",
853 "version": "0.2",
854 "elements": {
855 "a": { "content": "text" },
856 "b": { "content": "text" },
857 "c": { "content": "text" }
858 }
859 }"#,
860 )
861 .unwrap();
862
863 registry
865 .load_from_str(
866 r#"{
867 "name": "child",
868 "version": "0.2",
869 "extends": "parent",
870 "include": ["a", "b"],
871 "elements": {
872 "d": { "content": "text" }
873 }
874 }"#,
875 )
876 .unwrap();
877
878 let resolved = registry.get("child").unwrap();
879 assert!(resolved.elements.contains_key("a"));
880 assert!(resolved.elements.contains_key("b"));
881 assert!(!resolved.elements.contains_key("c")); assert!(resolved.elements.contains_key("d")); }
884
885 #[test]
886 fn test_type_vocabularies() {
887 let mut registry = ProfileRegistry::new();
888
889 registry
890 .load_from_str(
891 r#"{
892 "name": "typed",
893 "version": "0.2",
894 "types": {
895 "date": ["created", "updated", "published"],
896 "section": ["intro", "body", "conclusion"]
897 }
898 }"#,
899 )
900 .unwrap();
901
902 assert!(registry.validate_type_value("typed", "date", "created").unwrap());
903 assert!(registry.validate_type_value("typed", "date", "published").unwrap());
904 assert!(!registry.validate_type_value("typed", "date", "invalid").unwrap());
905 }
906}