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 =
499 serde_json::from_str(include_str!("../schemas/0.2/profiles/legal/legal.json"))
500 .map_err(|e| {
501 CmlError::ValidationError(format!("Failed to parse legal profile: {}", e))
502 })?;
503 self.profiles.insert("legal".to_string(), legal);
504
505 let legal_constitution: Profile = serde_json::from_str(include_str!(
507 "../schemas/0.2/profiles/legal/constitution/constitution.json"
508 ))
509 .map_err(|e| {
510 CmlError::ValidationError(format!("Failed to parse legal:constitution profile: {}", e))
511 })?;
512 self.profiles
513 .insert("legal:constitution".to_string(), legal_constitution);
514
515 let code: Profile = serde_json::from_str(include_str!(
517 "../schemas/0.2/profiles/code/code.json"
518 ))
519 .map_err(|e| CmlError::ValidationError(format!("Failed to parse code profile: {}", e)))?;
520 self.profiles.insert("code".to_string(), code);
521
522 let code_api: Profile =
524 serde_json::from_str(include_str!("../schemas/0.2/profiles/code/api/api.json"))
525 .map_err(|e| {
526 CmlError::ValidationError(format!("Failed to parse code:api profile: {}", e))
527 })?;
528 self.profiles.insert("code:api".to_string(), code_api);
529
530 let wiki: Profile = serde_json::from_str(include_str!(
532 "../schemas/0.2/profiles/wiki/wiki.json"
533 ))
534 .map_err(|e| CmlError::ValidationError(format!("Failed to parse wiki profile: {}", e)))?;
535 self.profiles.insert("wiki".to_string(), wiki);
536
537 Ok(())
538 }
539
540 pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
542 let content = std::fs::read_to_string(path.as_ref())?;
543 let profile: Profile = serde_json::from_str(&content)
544 .map_err(|e| CmlError::ValidationError(format!("Failed to parse profile: {}", e)))?;
545 self.profiles.insert(profile.name.clone(), profile);
546 Ok(())
547 }
548
549 pub fn load_from_str(&mut self, json: &str) -> Result<()> {
551 let profile: Profile = serde_json::from_str(json)
552 .map_err(|e| CmlError::ValidationError(format!("Failed to parse profile: {}", e)))?;
553 self.profiles.insert(profile.name.clone(), profile);
554 Ok(())
555 }
556
557 pub fn get(&mut self, name: &str) -> Result<&ResolvedProfile> {
559 if self.resolved.contains_key(name) {
561 return Ok(self.resolved.get(name).unwrap());
562 }
563
564 let resolved = self.resolve_profile(name)?;
566 self.resolved.insert(name.to_string(), resolved);
567 Ok(self.resolved.get(name).unwrap())
568 }
569
570 fn resolve_profile(&self, name: &str) -> Result<ResolvedProfile> {
572 let profile = self
573 .profiles
574 .get(name)
575 .ok_or_else(|| CmlError::ValidationError(format!("Profile not found: {}", name)))?;
576
577 let mut elements: HashMap<String, ElementDef> = HashMap::new();
579 let mut types: HashMap<String, Vec<String>> = HashMap::new();
580
581 if let Some(parent_name) = &profile.extends {
582 let parent = self.resolve_profile(parent_name)?;
583
584 if !profile.include.is_empty() {
586 let include_set: HashSet<&str> =
588 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> =
597 profile.exclude.iter().map(|s| s.as_str()).collect();
598 for (name, def) in parent.elements {
599 if !exclude_set.contains(name.as_str()) {
600 elements.insert(name, def);
601 }
602 }
603 } else {
604 elements = parent.elements;
606 }
607
608 types = parent.types;
610 }
611
612 for (name, def) in &profile.elements {
614 elements.insert(name.clone(), def.clone());
615 }
616
617 for (name, values) in &profile.types {
619 types.insert(name.clone(), values.clone());
620 }
621
622 Ok(ResolvedProfile {
623 name: profile.name.clone(),
624 version: profile.version.clone(),
625 elements,
626 types,
627 })
628 }
629
630 pub fn is_element_allowed(&mut self, profile: &str, element: &str) -> Result<bool> {
632 let resolved = self.get(profile)?;
633 Ok(resolved.elements.contains_key(element))
634 }
635
636 pub fn get_type_values(
638 &mut self,
639 profile: &str,
640 type_name: &str,
641 ) -> Result<Option<Vec<String>>> {
642 let resolved = self.get(profile)?;
643 Ok(resolved.types.get(type_name).cloned())
644 }
645
646 pub fn validate_type_value(
648 &mut self,
649 profile: &str,
650 type_name: &str,
651 value: &str,
652 ) -> Result<bool> {
653 let resolved = self.get(profile)?;
654 match resolved.types.get(type_name) {
655 Some(values) => Ok(values.contains(&value.to_string())),
656 None => Ok(true), }
658 }
659}
660
661impl Default for ProfileRegistry {
662 fn default() -> Self {
663 Self::new()
664 }
665}
666
667pub struct ConstraintRegistry {
673 constraints: HashMap<String, ProfileConstraints>,
675
676 resolved: HashMap<String, ResolvedConstraints>,
678}
679
680impl ConstraintRegistry {
681 pub fn new() -> Self {
683 Self {
684 constraints: HashMap::new(),
685 resolved: HashMap::new(),
686 }
687 }
688
689 pub fn with_builtins() -> Result<Self> {
691 let mut registry = Self::new();
692 registry.load_builtin_constraints()?;
693 Ok(registry)
694 }
695
696 fn load_builtin_constraints(&mut self) -> Result<()> {
698 let core: ProfileConstraints = serde_json::from_str(include_str!(
700 "../schemas/0.2/profiles/core/constraints.json"
701 ))
702 .map_err(|e| {
703 CmlError::ValidationError(format!("Failed to parse core constraints: {}", e))
704 })?;
705 self.constraints.insert("core".to_string(), core);
706
707 let standard: ProfileConstraints = serde_json::from_str(include_str!(
709 "../schemas/0.2/profiles/standard/constraints.json"
710 ))
711 .map_err(|e| {
712 CmlError::ValidationError(format!("Failed to parse standard constraints: {}", e))
713 })?;
714 self.constraints.insert("standard".to_string(), standard);
715
716 let legal: ProfileConstraints = serde_json::from_str(include_str!(
718 "../schemas/0.2/profiles/legal/constraints.json"
719 ))
720 .map_err(|e| {
721 CmlError::ValidationError(format!("Failed to parse legal constraints: {}", e))
722 })?;
723 self.constraints.insert("legal".to_string(), legal);
724
725 let code: ProfileConstraints = serde_json::from_str(include_str!(
727 "../schemas/0.2/profiles/code/constraints.json"
728 ))
729 .map_err(|e| {
730 CmlError::ValidationError(format!("Failed to parse code constraints: {}", e))
731 })?;
732 self.constraints.insert("code".to_string(), code);
733
734 let wiki: ProfileConstraints = serde_json::from_str(include_str!(
736 "../schemas/0.2/profiles/wiki/constraints.json"
737 ))
738 .map_err(|e| {
739 CmlError::ValidationError(format!("Failed to parse wiki constraints: {}", e))
740 })?;
741 self.constraints.insert("wiki".to_string(), wiki);
742
743 Ok(())
744 }
745
746 pub fn load_from_str(&mut self, json: &str) -> Result<()> {
748 let constraints: ProfileConstraints = serde_json::from_str(json).map_err(|e| {
749 CmlError::ValidationError(format!("Failed to parse constraints: {}", e))
750 })?;
751 self.constraints
752 .insert(constraints.profile.clone(), constraints);
753 Ok(())
754 }
755
756 pub fn get(&mut self, name: &str) -> Result<&ResolvedConstraints> {
758 if self.resolved.contains_key(name) {
759 return Ok(self.resolved.get(name).unwrap());
760 }
761
762 let resolved = self.resolve_constraints(name)?;
763 self.resolved.insert(name.to_string(), resolved);
764 Ok(self.resolved.get(name).unwrap())
765 }
766
767 fn resolve_constraints(&self, name: &str) -> Result<ResolvedConstraints> {
769 let constraints = self
770 .constraints
771 .get(name)
772 .ok_or_else(|| CmlError::ValidationError(format!("Constraints not found: {}", name)))?;
773
774 let mut resolved = ResolvedConstraints {
775 profile: name.to_string(),
776 ..Default::default()
777 };
778
779 if let Some(parent_name) = &constraints.extends {
781 let parent = self.resolve_constraints(parent_name)?;
782 resolved.document = parent.document;
783 resolved.elements = parent.elements;
784 resolved.attributes = parent.attributes;
785 resolved.hierarchy = parent.hierarchy;
786 resolved.nesting = parent.nesting;
787 resolved.list_constraints = parent.list_constraints;
788 resolved.semantic_rules = parent.semantic_rules;
789 }
790
791 if constraints.document.is_some() {
793 resolved.document = constraints.document.clone();
794 }
795
796 for (name, constraint) in &constraints.elements {
797 resolved.elements.insert(name.clone(), constraint.clone());
798 }
799
800 for (name, constraint) in &constraints.attributes {
801 resolved.attributes.insert(name.clone(), constraint.clone());
802 }
803
804 for (name, constraint) in &constraints.hierarchy {
805 resolved.hierarchy.insert(name.clone(), constraint.clone());
806 }
807
808 if constraints.nesting.is_some() {
809 resolved.nesting = constraints.nesting.clone();
810 }
811
812 if constraints.list_constraints.is_some() {
813 resolved.list_constraints = constraints.list_constraints.clone();
814 }
815
816 for (name, rule) in &constraints.semantic_rules {
817 resolved.semantic_rules.insert(name.clone(), rule.clone());
818 }
819
820 Ok(resolved)
821 }
822}
823
824impl Default for ConstraintRegistry {
825 fn default() -> Self {
826 Self::new()
827 }
828}
829
830#[cfg(test)]
831mod tests {
832 use super::*;
833
834 #[test]
835 fn test_load_core_profile() {
836 let json = r#"{
837 "name": "test-core",
838 "version": "0.2",
839 "elements": {
840 "cml": { "content": "block" },
841 "header": { "content": "block" },
842 "body": { "content": "block" },
843 "footer": { "content": "block" }
844 }
845 }"#;
846
847 let profile: Profile = serde_json::from_str(json).unwrap();
848 assert_eq!(profile.name, "test-core");
849 assert_eq!(profile.elements.len(), 4);
850 }
851
852 #[test]
853 fn test_profile_inheritance() {
854 let mut registry = ProfileRegistry::new();
855
856 registry
858 .load_from_str(
859 r#"{
860 "name": "parent",
861 "version": "0.2",
862 "elements": {
863 "a": { "content": "text" },
864 "b": { "content": "text" },
865 "c": { "content": "text" }
866 }
867 }"#,
868 )
869 .unwrap();
870
871 registry
873 .load_from_str(
874 r#"{
875 "name": "child",
876 "version": "0.2",
877 "extends": "parent",
878 "include": ["a", "b"],
879 "elements": {
880 "d": { "content": "text" }
881 }
882 }"#,
883 )
884 .unwrap();
885
886 let resolved = registry.get("child").unwrap();
887 assert!(resolved.elements.contains_key("a"));
888 assert!(resolved.elements.contains_key("b"));
889 assert!(!resolved.elements.contains_key("c")); assert!(resolved.elements.contains_key("d")); }
892
893 #[test]
894 fn test_type_vocabularies() {
895 let mut registry = ProfileRegistry::new();
896
897 registry
898 .load_from_str(
899 r#"{
900 "name": "typed",
901 "version": "0.2",
902 "types": {
903 "date": ["created", "updated", "published"],
904 "section": ["intro", "body", "conclusion"]
905 }
906 }"#,
907 )
908 .unwrap();
909
910 assert!(registry
911 .validate_type_value("typed", "date", "created")
912 .unwrap());
913 assert!(registry
914 .validate_type_value("typed", "date", "published")
915 .unwrap());
916 assert!(!registry
917 .validate_type_value("typed", "date", "invalid")
918 .unwrap());
919 }
920}