use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::vec::Vec;
use crate::error::NodeError;
use crate::kv::{KvPairs, KvWarning, KvWarningKind};
use crate::param::ParamMap;
use crate::schema::NodeGroup;
use crate::traits::{NodeDef, NodeInstance};
pub struct NodeRegistry {
nodes: Vec<&'static dyn NodeDef>,
}
pub struct KvResult {
pub instances: Vec<Box<dyn NodeInstance>>,
pub warnings: Vec<KvWarning>,
}
impl NodeRegistry {
pub fn new() -> Self {
Self { nodes: Vec::new() }
}
pub fn register(&mut self, def: &'static dyn NodeDef) {
self.nodes.push(def);
}
pub fn register_all(&mut self, defs: &[&'static dyn NodeDef]) {
self.nodes.extend_from_slice(defs);
}
pub fn get(&self, id: &str) -> Option<&'static dyn NodeDef> {
self.nodes.iter().find(|n| n.schema().id == id).copied()
}
pub fn all(&self) -> &[&'static dyn NodeDef] {
&self.nodes
}
pub fn by_group(&self, group: NodeGroup) -> Vec<&'static dyn NodeDef> {
self.nodes
.iter()
.filter(|n| n.schema().group == group)
.copied()
.collect()
}
pub fn by_tag(&self, tag: &str) -> Vec<&'static dyn NodeDef> {
self.nodes
.iter()
.filter(|n| n.schema().tags.contains(&tag))
.copied()
.collect()
}
pub fn create(&self, id: &str, params: &ParamMap) -> Result<Box<dyn NodeInstance>, NodeError> {
let def = self
.get(id)
.ok_or_else(|| NodeError::UnknownNode(id.to_string()))?;
def.create(params)
}
pub fn from_querystring(&self, qs: &str) -> KvResult {
let mut kv = KvPairs::from_querystring(qs);
let mut instances = Vec::new();
for def in &self.nodes {
match def.from_kv(&mut kv) {
Ok(Some(instance)) => instances.push(instance),
Ok(None) => {}
Err(e) => {
kv.warn(
def.schema().id,
KvWarningKind::InvalidValue,
alloc::format!("error creating {}: {e}", def.schema().id),
);
}
}
}
let mut warnings: Vec<KvWarning> = kv.warnings().to_vec();
for (key, _value) in kv.unconsumed() {
warnings.push(KvWarning {
key: key.to_string(),
kind: KvWarningKind::UnrecognizedKey,
message: alloc::format!("unrecognized key: {key}"),
});
}
KvResult {
instances,
warnings,
}
}
pub fn to_markdown(&self) -> alloc::string::String {
use alloc::fmt::Write;
let mut md = alloc::string::String::from("# Node Reference\n\n");
let _ = write!(md, "{} nodes registered.\n\n", self.nodes.len());
for def in &self.nodes {
md.push_str(&def.schema().to_markdown());
}
md
}
}
#[cfg(feature = "serde")]
impl NodeRegistry {
pub fn get_by_json_key(&self, key: &str) -> Option<&'static dyn NodeDef> {
self.nodes
.iter()
.find(|n| n.schema().effective_json_key() == key)
.copied()
}
pub fn node_from_json(
&self,
json: &serde_json::Value,
) -> Result<Box<dyn NodeInstance>, NodeError> {
let obj = json
.as_object()
.ok_or_else(|| NodeError::Other("expected JSON object".into()))?;
if obj.len() != 1 {
return Err(NodeError::Other(alloc::format!(
"expected exactly one key, got {}",
obj.len()
)));
}
let (key, inner) = obj.iter().next().unwrap();
let def = self
.get_by_json_key(key)
.ok_or_else(|| NodeError::UnknownNode(key.clone()))?;
let schema = def.schema();
let inner_obj = inner
.as_object()
.ok_or_else(|| NodeError::Other("node params must be a JSON object".into()))?;
if schema.deny_unknown_fields {
for json_key in inner_obj.keys() {
let known = schema.params.iter().any(|p| p.matches_json_key(json_key));
if !known {
return Err(NodeError::UnknownParam {
node: schema.id,
param: json_key.clone(),
});
}
}
}
let param_map = self.json_to_param_map(schema, inner_obj)?;
def.create(¶m_map)
}
pub fn node_to_json(&self, node: &dyn NodeInstance) -> serde_json::Value {
let schema = node.schema();
let params = node.to_params();
let inner = self.param_map_to_json(schema, ¶ms);
serde_json::json!({ schema.effective_json_key(): inner })
}
pub fn pipeline_from_json(
&self,
json: &serde_json::Value,
) -> Result<Vec<Box<dyn NodeInstance>>, NodeError> {
let arr = json
.as_array()
.ok_or_else(|| NodeError::Other("expected JSON array for pipeline".into()))?;
arr.iter().map(|item| self.node_from_json(item)).collect()
}
pub fn pipeline_to_json(&self, nodes: &[Box<dyn NodeInstance>]) -> serde_json::Value {
let arr: Vec<serde_json::Value> = nodes
.iter()
.map(|n| self.node_to_json(n.as_ref()))
.collect();
serde_json::Value::Array(arr)
}
fn json_to_param_map(
&self,
schema: &crate::schema::NodeSchema,
obj: &serde_json::Map<alloc::string::String, serde_json::Value>,
) -> Result<ParamMap, NodeError> {
use crate::param::ParamValue;
let mut map = ParamMap::new();
for param in schema.params {
let json_key = param.effective_json_name();
let value = obj
.get(json_key)
.or_else(|| {
if json_key != param.name {
obj.get(param.name)
} else {
None
}
})
.or_else(|| param.json_aliases.iter().find_map(|a| obj.get(*a)));
let Some(value) = value else { continue };
if value.is_null() {
map.insert(param.name.into(), ParamValue::None);
continue;
}
let pv = match ¶m.kind {
crate::schema::ParamKind::Float { .. } => {
value.as_f64().map(|v| ParamValue::F32(v as f32))
}
crate::schema::ParamKind::Int { .. } => value
.as_i64()
.and_then(|v| i32::try_from(v).ok())
.map(ParamValue::I32),
crate::schema::ParamKind::U32 { .. } => value
.as_u64()
.and_then(|v| u32::try_from(v).ok())
.map(ParamValue::U32),
crate::schema::ParamKind::Bool { .. } => value.as_bool().map(ParamValue::Bool),
crate::schema::ParamKind::Str { .. } => {
value.as_str().map(|s| ParamValue::Str(s.into()))
}
crate::schema::ParamKind::Enum { .. } => {
value.as_str().map(|s| ParamValue::Str(s.into()))
}
crate::schema::ParamKind::Json { .. }
| crate::schema::ParamKind::Object { .. }
| crate::schema::ParamKind::TaggedUnion { .. } => {
Some(ParamValue::Json(value.to_string()))
}
crate::schema::ParamKind::FloatArray { len, .. } => {
value.as_array().and_then(|arr| {
if arr.len() == *len {
let floats: Vec<f32> = arr
.iter()
.filter_map(|v| v.as_f64().map(|f| f as f32))
.collect();
if floats.len() == *len {
Some(ParamValue::F32Array(floats))
} else {
None
}
} else {
None
}
})
}
crate::schema::ParamKind::Color { .. } => value.as_array().and_then(|arr| {
if arr.len() == 4 {
let f: Vec<f32> = arr
.iter()
.filter_map(|v| v.as_f64().map(|f| f as f32))
.collect();
if f.len() == 4 {
Some(ParamValue::Color([f[0], f[1], f[2], f[3]]))
} else {
None
}
} else {
None
}
}),
#[allow(unreachable_patterns)]
_ => Some(ParamValue::Str(value.to_string())),
};
if let Some(pv) = pv {
map.insert(param.name.into(), pv);
}
}
Ok(map)
}
fn param_map_to_json(
&self,
schema: &crate::schema::NodeSchema,
params: &ParamMap,
) -> serde_json::Value {
use crate::param::ParamValue;
let mut obj = serde_json::Map::new();
for param in schema.params {
let Some(value) = params.get(param.name) else {
continue;
};
if value.is_none() {
continue; }
let json_key = param.effective_json_name();
let jv = match value {
ParamValue::None => unreachable!(),
ParamValue::F32(v) => serde_json::json!(*v),
ParamValue::I32(v) => serde_json::json!(*v),
ParamValue::U32(v) => serde_json::json!(*v),
ParamValue::Bool(v) => serde_json::json!(*v),
ParamValue::Str(v) | ParamValue::Enum(v) => serde_json::json!(v),
ParamValue::F32Array(v) => serde_json::json!(v),
ParamValue::Color(v) => serde_json::json!(v),
ParamValue::Json(text) => {
serde_json::from_str(text).unwrap_or(serde_json::Value::Null)
}
};
obj.insert(json_key.into(), jv);
}
serde_json::Value::Object(obj)
}
}
impl Default for NodeRegistry {
fn default() -> Self {
Self::new()
}
}