1use crate::metadata::NodeMetadata;
6use crate::processor::NodeProcessor;
7use std::collections::HashMap;
8
9pub struct NodeRegistry {
18 processors: HashMap<String, Box<dyn NodeProcessor>>,
20}
21
22impl NodeRegistry {
23 pub fn new() -> Self {
25 Self {
26 processors: HashMap::new(),
27 }
28 }
29
30 pub fn register(&mut self, key: &str, processor: Box<dyn NodeProcessor>) {
42 self.processors.insert(key.to_string(), processor);
43 }
44
45 pub fn resolve(
57 &self,
58 node_type: &str,
59 _params: &serde_json::Map<String, serde_json::Value>,
60 ) -> Option<&dyn NodeProcessor> {
61 self.processors.get(node_type).map(|b| b.as_ref())
62 }
63
64 pub fn len(&self) -> usize {
66 self.processors.len()
67 }
68
69 pub fn is_empty(&self) -> bool {
71 self.processors.is_empty()
72 }
73
74 pub fn catalog(&self) -> Vec<NodeMetadata> {
81 self.processors.values().map(|p| p.metadata()).collect()
82 }
83}
84
85impl Default for NodeRegistry {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::context::ProcessContext;
99 use crate::errors::BntoError;
100 use crate::processor::{NodeInput, NodeOutput, OutputFile};
101 use crate::progress::ProgressReporter;
102
103 struct MockProcessor {
107 mock_name: String,
109 }
110
111 impl MockProcessor {
112 fn new(name: &str) -> Self {
113 Self {
114 mock_name: name.to_string(),
115 }
116 }
117 }
118
119 impl NodeProcessor for MockProcessor {
120 fn name(&self) -> &str {
121 &self.mock_name
122 }
123
124 fn process(
125 &self,
126 input: NodeInput,
127 _progress: &ProgressReporter,
128 _ctx: &dyn ProcessContext,
129 ) -> Result<NodeOutput, BntoError> {
130 Ok(NodeOutput {
131 files: vec![OutputFile {
132 data: input.data,
133 filename: input.filename,
134 mime_type: input
135 .mime_type
136 .unwrap_or_else(|| "application/octet-stream".to_string()),
137 }],
138 metadata: serde_json::Map::new(),
139 })
140 }
141 }
142
143 #[test]
146 fn test_new_registry_is_empty() {
147 let registry = NodeRegistry::new();
148 assert!(registry.is_empty());
149 assert_eq!(registry.len(), 0);
150 }
151
152 #[test]
153 fn test_register_and_resolve() {
154 let mut registry = NodeRegistry::new();
155 registry.register("image-compress", Box::new(MockProcessor::new("compress")));
156
157 let params = serde_json::Map::new();
158
159 let processor = registry.resolve("image-compress", ¶ms);
161 assert!(processor.is_some());
162 assert_eq!(processor.unwrap().name(), "compress");
163 }
164
165 #[test]
166 fn test_resolve_unknown_type_returns_none() {
167 let registry = NodeRegistry::new();
168 let params = serde_json::Map::new();
169
170 let processor = registry.resolve("unknown-type", ¶ms);
172 assert!(processor.is_none());
173 }
174
175 #[test]
176 fn test_register_multiple_processors() {
177 let mut registry = NodeRegistry::new();
178 registry.register("image-compress", Box::new(MockProcessor::new("compress")));
179 registry.register("image-resize", Box::new(MockProcessor::new("resize")));
180 registry.register(
181 "spreadsheet-clean",
182 Box::new(MockProcessor::new("clean-csv")),
183 );
184
185 assert_eq!(registry.len(), 3);
186
187 let params = serde_json::Map::new();
188
189 assert_eq!(
191 registry.resolve("image-compress", ¶ms).unwrap().name(),
192 "compress"
193 );
194 assert_eq!(
195 registry.resolve("image-resize", ¶ms).unwrap().name(),
196 "resize"
197 );
198 assert_eq!(
199 registry
200 .resolve("spreadsheet-clean", ¶ms)
201 .unwrap()
202 .name(),
203 "clean-csv"
204 );
205 }
206
207 #[test]
208 fn test_register_overwrites_existing() {
209 let mut registry = NodeRegistry::new();
210 registry.register("image-compress", Box::new(MockProcessor::new("old")));
211 registry.register("image-compress", Box::new(MockProcessor::new("new")));
212
213 let params = serde_json::Map::new();
214
215 assert_eq!(
217 registry.resolve("image-compress", ¶ms).unwrap().name(),
218 "new"
219 );
220 }
221}