1use std::collections::HashMap;
8
9use serde_json::Value;
10
11use crate::node::StepNode;
12use crate::{CapabilityManifest, GuardKind};
13
14#[derive(Debug, thiserror::Error)]
16pub enum NodeError {
17 #[error("unknown node type '{0}' (not registered)")]
19 UnknownType(String),
20 #[error("node '{node_type}' has invalid config: {reason}")]
22 InvalidConfig {
23 node_type: String,
25 reason: String,
27 },
28 #[error("invalid capability manifest: {0}")]
30 InvalidCapability(String),
31 #[error("capability '{0}' is already registered")]
33 DuplicateCapability(String),
34}
35
36pub type StepFactory = fn(config: &Value) -> Result<Box<dyn StepNode>, NodeError>;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum FieldType {
42 String,
44 Number,
46 Bool,
48 Array,
50 Object,
52 Any,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct FieldSpec {
59 pub key: String,
61 pub ty: FieldType,
63 pub required: bool,
65}
66
67impl FieldSpec {
68 pub fn required(key: impl Into<String>, ty: FieldType) -> Self {
70 Self {
71 key: key.into(),
72 ty,
73 required: true,
74 }
75 }
76
77 pub fn optional(key: impl Into<String>, ty: FieldType) -> Self {
79 Self {
80 key: key.into(),
81 ty,
82 required: false,
83 }
84 }
85}
86
87#[derive(Debug, Clone, Default, PartialEq, Eq)]
89pub struct NodeSchema {
90 pub fields: Vec<FieldSpec>,
92}
93
94#[derive(Debug)]
97pub struct NodeRegistry {
98 steps: HashMap<String, StepFactory>,
99 ingress: std::collections::HashSet<String>,
100 fan_out: std::collections::HashSet<String>,
101 side_effect_guards: std::collections::HashSet<String>,
102 guard_kinds: HashMap<String, GuardKind>,
103 capabilities: HashMap<String, CapabilityManifest>,
104 schemas: HashMap<String, NodeSchema>,
105}
106
107impl NodeRegistry {
108 pub fn empty() -> Self {
110 Self {
111 steps: HashMap::new(),
112 ingress: Default::default(),
113 fan_out: Default::default(),
114 side_effect_guards: Default::default(),
115 guard_kinds: Default::default(),
116 capabilities: Default::default(),
117 schemas: HashMap::new(),
118 }
119 }
120
121 pub fn with_builtins() -> Self {
123 let mut r = Self::empty();
124 crate::builtins::register_builtins(&mut r);
125 r
126 }
127
128 pub fn register_step(&mut self, node_type: impl Into<String>, factory: StepFactory) {
130 self.steps.insert(node_type.into(), factory);
131 }
132
133 pub fn register_ingress(&mut self, node_type: impl Into<String>) {
135 self.ingress.insert(node_type.into());
136 }
137
138 pub fn register_fan_out(&mut self, node_type: impl Into<String>) {
141 self.fan_out.insert(node_type.into());
142 }
143
144 pub fn is_fan_out_capable(&self, node_type: &str) -> bool {
146 self.fan_out.contains(node_type)
147 }
148
149 pub fn register_side_effect_guard(&mut self, node_type: impl Into<String>) {
151 self.register_guard(node_type, GuardKind::Authorization);
152 }
153
154 pub fn register_guard(&mut self, node_type: impl Into<String>, kind: GuardKind) {
156 let node_type = node_type.into();
157 self.side_effect_guards.insert(node_type.clone());
158 self.guard_kinds.insert(node_type, kind);
159 }
160
161 pub fn guard_kind(&self, node_type: &str) -> Option<GuardKind> {
163 self.guard_kinds.get(node_type).copied()
164 }
165
166 pub fn is_side_effect_guard(&self, node_type: &str) -> bool {
168 self.side_effect_guards.contains(node_type)
169 }
170
171 pub fn is_ingress(&self, node_type: &str) -> bool {
173 self.ingress.contains(node_type) || node_type.starts_with("ingress.")
174 }
175
176 pub fn is_step(&self, node_type: &str) -> bool {
178 self.steps.contains_key(node_type)
179 }
180
181 pub fn build_step(
183 &self,
184 node_type: &str,
185 config: &Value,
186 ) -> Result<Box<dyn StepNode>, NodeError> {
187 let factory = self
188 .steps
189 .get(node_type)
190 .ok_or_else(|| NodeError::UnknownType(node_type.to_string()))?;
191 factory(config)
192 }
193
194 pub fn known_step_types(&self) -> impl Iterator<Item = &str> {
196 self.steps.keys().map(|s| s.as_str())
197 }
198
199 pub fn register_schema(&mut self, node_type: impl Into<String>, schema: NodeSchema) {
201 self.schemas.insert(node_type.into(), schema);
202 }
203
204 pub fn schema(&self, node_type: &str) -> Option<&NodeSchema> {
206 self.schemas.get(node_type)
207 }
208
209 pub fn register_capability(&mut self, manifest: CapabilityManifest) -> Result<(), NodeError> {
211 manifest
212 .validate()
213 .map_err(|error| NodeError::InvalidCapability(error.to_string()))?;
214 if self.capabilities.contains_key(&manifest.id) {
215 return Err(NodeError::DuplicateCapability(manifest.id));
216 }
217 self.capabilities.insert(manifest.id.clone(), manifest);
218 Ok(())
219 }
220
221 pub fn capability(&self, id: &str) -> Option<&CapabilityManifest> {
223 self.capabilities.get(id)
224 }
225
226 pub fn capability_manifests(&self) -> impl Iterator<Item = &CapabilityManifest> {
228 self.capabilities.values()
229 }
230}