use std::collections::HashMap;
use serde_json::Value;
use crate::node::StepNode;
use crate::{CapabilityManifest, GuardKind};
#[derive(Debug, thiserror::Error)]
pub enum NodeError {
#[error("unknown node type '{0}' (not registered)")]
UnknownType(String),
#[error("node '{node_type}' has invalid config: {reason}")]
InvalidConfig {
node_type: String,
reason: String,
},
#[error("invalid capability manifest: {0}")]
InvalidCapability(String),
#[error("capability '{0}' is already registered")]
DuplicateCapability(String),
}
pub type StepFactory = fn(config: &Value) -> Result<Box<dyn StepNode>, NodeError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldType {
String,
Number,
Bool,
Array,
Object,
Any,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldSpec {
pub key: String,
pub ty: FieldType,
pub required: bool,
}
impl FieldSpec {
pub fn required(key: impl Into<String>, ty: FieldType) -> Self {
Self {
key: key.into(),
ty,
required: true,
}
}
pub fn optional(key: impl Into<String>, ty: FieldType) -> Self {
Self {
key: key.into(),
ty,
required: false,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct NodeSchema {
pub fields: Vec<FieldSpec>,
}
#[derive(Debug)]
pub struct NodeRegistry {
steps: HashMap<String, StepFactory>,
ingress: std::collections::HashSet<String>,
fan_out: std::collections::HashSet<String>,
side_effect_guards: std::collections::HashSet<String>,
guard_kinds: HashMap<String, GuardKind>,
capabilities: HashMap<String, CapabilityManifest>,
schemas: HashMap<String, NodeSchema>,
}
impl NodeRegistry {
pub fn empty() -> Self {
Self {
steps: HashMap::new(),
ingress: Default::default(),
fan_out: Default::default(),
side_effect_guards: Default::default(),
guard_kinds: Default::default(),
capabilities: Default::default(),
schemas: HashMap::new(),
}
}
pub fn with_builtins() -> Self {
let mut r = Self::empty();
crate::builtins::register_builtins(&mut r);
r
}
pub fn register_step(&mut self, node_type: impl Into<String>, factory: StepFactory) {
self.steps.insert(node_type.into(), factory);
}
pub fn register_ingress(&mut self, node_type: impl Into<String>) {
self.ingress.insert(node_type.into());
}
pub fn register_fan_out(&mut self, node_type: impl Into<String>) {
self.fan_out.insert(node_type.into());
}
pub fn is_fan_out_capable(&self, node_type: &str) -> bool {
self.fan_out.contains(node_type)
}
pub fn register_side_effect_guard(&mut self, node_type: impl Into<String>) {
self.register_guard(node_type, GuardKind::Authorization);
}
pub fn register_guard(&mut self, node_type: impl Into<String>, kind: GuardKind) {
let node_type = node_type.into();
self.side_effect_guards.insert(node_type.clone());
self.guard_kinds.insert(node_type, kind);
}
pub fn guard_kind(&self, node_type: &str) -> Option<GuardKind> {
self.guard_kinds.get(node_type).copied()
}
pub fn is_side_effect_guard(&self, node_type: &str) -> bool {
self.side_effect_guards.contains(node_type)
}
pub fn is_ingress(&self, node_type: &str) -> bool {
self.ingress.contains(node_type) || node_type.starts_with("ingress.")
}
pub fn is_step(&self, node_type: &str) -> bool {
self.steps.contains_key(node_type)
}
pub fn build_step(
&self,
node_type: &str,
config: &Value,
) -> Result<Box<dyn StepNode>, NodeError> {
let factory = self
.steps
.get(node_type)
.ok_or_else(|| NodeError::UnknownType(node_type.to_string()))?;
factory(config)
}
pub fn known_step_types(&self) -> impl Iterator<Item = &str> {
self.steps.keys().map(|s| s.as_str())
}
pub fn register_schema(&mut self, node_type: impl Into<String>, schema: NodeSchema) {
self.schemas.insert(node_type.into(), schema);
}
pub fn schema(&self, node_type: &str) -> Option<&NodeSchema> {
self.schemas.get(node_type)
}
pub fn register_capability(&mut self, manifest: CapabilityManifest) -> Result<(), NodeError> {
manifest
.validate()
.map_err(|error| NodeError::InvalidCapability(error.to_string()))?;
if self.capabilities.contains_key(&manifest.id) {
return Err(NodeError::DuplicateCapability(manifest.id));
}
self.capabilities.insert(manifest.id.clone(), manifest);
Ok(())
}
pub fn capability(&self, id: &str) -> Option<&CapabilityManifest> {
self.capabilities.get(id)
}
pub fn capability_manifests(&self) -> impl Iterator<Item = &CapabilityManifest> {
self.capabilities.values()
}
}