argumentation_schemes/
registry.rs1use crate::scheme::SchemeSpec;
5use crate::types::{SchemeCategory, SchemeId};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
10pub struct CatalogRegistry {
11 schemes: Vec<SchemeSpec>,
12 by_id: HashMap<SchemeId, usize>,
13 by_key: HashMap<String, usize>,
14}
15
16impl CatalogRegistry {
17 pub fn new() -> Self {
19 Self {
20 schemes: Vec::new(),
21 by_id: HashMap::new(),
22 by_key: HashMap::new(),
23 }
24 }
25
26 pub fn register(&mut self, scheme: SchemeSpec) {
30 let key = scheme.key();
31 let idx = self.schemes.len();
32 self.by_id.insert(scheme.id, idx);
33 self.by_key.insert(key, idx);
34 self.schemes.push(scheme);
35 }
36
37 pub fn by_id(&self, id: SchemeId) -> Option<&SchemeSpec> {
39 self.by_id.get(&id).map(|&idx| &self.schemes[idx])
40 }
41
42 pub fn by_key(&self, key: &str) -> Option<&SchemeSpec> {
44 self.by_key.get(key).map(|&idx| &self.schemes[idx])
45 }
46
47 pub fn by_category(&self, category: SchemeCategory) -> Vec<&SchemeSpec> {
49 self.schemes
50 .iter()
51 .filter(|s| s.category == category)
52 .collect()
53 }
54
55 pub fn all(&self) -> &[SchemeSpec] {
57 &self.schemes
58 }
59
60 pub fn len(&self) -> usize {
62 self.schemes.len()
63 }
64
65 pub fn is_empty(&self) -> bool {
67 self.schemes.is_empty()
68 }
69
70 #[must_use]
78 pub fn with_walton_catalog() -> Self {
79 crate::catalog::default_catalog()
80 }
81
82 #[must_use]
87 pub fn by_name(&self, name: &str) -> Option<&SchemeSpec> {
88 self.schemes.iter().find(|s| s.name == name)
89 }
90}
91
92impl Default for CatalogRegistry {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::critical::CriticalQuestion;
102 use crate::scheme::*;
103 use crate::types::*;
104
105 fn test_scheme(id: u32, name: &str, cat: SchemeCategory) -> SchemeSpec {
106 SchemeSpec {
107 id: SchemeId(id),
108 name: name.into(),
109 category: cat,
110 premises: vec![PremiseSlot::new("p", "premise", SlotRole::Proposition)],
111 conclusion: ConclusionTemplate::positive("c", "?p"),
112 critical_questions: vec![CriticalQuestion::new(
113 1,
114 "?p?",
115 Challenge::PremiseTruth("p".into()),
116 )],
117 metadata: SchemeMetadata {
118 citation: "test".into(),
119 domain_tags: vec![],
120 presumptive: true,
121 strength: SchemeStrength::Moderate,
122 },
123 }
124 }
125
126 #[test]
127 fn register_and_lookup_by_id() {
128 let mut reg = CatalogRegistry::new();
129 reg.register(test_scheme(1, "Test Scheme", SchemeCategory::Epistemic));
130 assert!(reg.by_id(SchemeId(1)).is_some());
131 assert!(reg.by_id(SchemeId(99)).is_none());
132 }
133
134 #[test]
135 fn lookup_by_key_uses_snake_case_name() {
136 let mut reg = CatalogRegistry::new();
137 reg.register(test_scheme(
138 1,
139 "Argument from Expert Opinion",
140 SchemeCategory::Epistemic,
141 ));
142 assert!(reg.by_key("argument_from_expert_opinion").is_some());
143 assert!(reg.by_key("Argument from Expert Opinion").is_none());
144 }
145
146 #[test]
147 fn filter_by_category_returns_only_matching() {
148 let mut reg = CatalogRegistry::new();
149 reg.register(test_scheme(1, "Scheme A", SchemeCategory::Epistemic));
150 reg.register(test_scheme(2, "Scheme B", SchemeCategory::Practical));
151 reg.register(test_scheme(3, "Scheme C", SchemeCategory::Epistemic));
152 assert_eq!(reg.by_category(SchemeCategory::Epistemic).len(), 2);
153 assert_eq!(reg.by_category(SchemeCategory::Practical).len(), 1);
154 assert_eq!(reg.by_category(SchemeCategory::Causal).len(), 0);
155 }
156
157 #[test]
158 fn len_and_is_empty_track_registrations() {
159 let mut reg = CatalogRegistry::new();
160 assert!(reg.is_empty());
161 assert_eq!(reg.len(), 0);
162 reg.register(test_scheme(1, "A", SchemeCategory::Causal));
163 reg.register(test_scheme(2, "B", SchemeCategory::Causal));
164 assert!(!reg.is_empty());
165 assert_eq!(reg.len(), 2);
166 }
167}