Skip to main content
Glama
variant.rs24.6 kB
use std::{ collections::{ HashMap, VecDeque, hash_map::Entry, }, future::Future, }; use itertools::Itertools; use object_tree::{ Hash, HashedNode, }; use petgraph::prelude::*; use tokio::sync::Mutex; use url::Url; use super::{ PkgResult, SiPkgActionFunc, SiPkgError, SiPkgLeafFunction, SiPkgManagementFunc, SiPkgProp, SiPkgPropData, SiPkgSiPropFunc, SiPkgSocket, Source, }; use crate::{ AttrFuncInputSpec, MapKeyFuncSpec, PropSpec, PropSpecBuilder, PropSpecKind, SchemaVariantSpec, SchemaVariantSpecBuilder, SchemaVariantSpecComponentType, SchemaVariantSpecData, SchemaVariantSpecPropRoot, SiPkgAuthFunc, SiPkgRootPropFunc, node::{ PkgNode, PropChildNode, SchemaVariantChildNode, }, }; #[derive(Clone, Debug)] pub struct SiPkgSchemaVariantData { version: String, link: Option<Url>, color: Option<String>, component_type: SchemaVariantSpecComponentType, func_unique_id: String, description: Option<String>, } impl SiPkgSchemaVariantData { pub fn version(&self) -> &str { self.version.as_ref() } pub fn link(&self) -> Option<&Url> { self.link.as_ref() } pub fn color(&self) -> Option<&str> { self.color.as_deref() } pub fn component_type(&self) -> SchemaVariantSpecComponentType { self.component_type } pub fn func_unique_id(&self) -> &str { self.func_unique_id.as_str() } pub fn description(&self) -> Option<&str> { self.description.as_deref() } } #[derive(Clone, Debug)] pub struct SiPkgSchemaVariant<'a> { version: String, pub data: Option<SiPkgSchemaVariantData>, unique_id: Option<String>, deleted: bool, is_builtin: bool, hash: Hash, source: Source<'a>, } macro_rules! impl_variant_children_from_graph { ($fn_name:ident, SchemaVariantChildNode::$child_node:ident, $pkg_type:ident) => { pub fn $fn_name(&self) -> PkgResult<Vec<$pkg_type>> { let mut entries = vec![]; if let Some(child_idxs) = self .source .graph .neighbors_directed(self.source.node_idx, Outgoing) .find(|node_idx| { matches!( &self.source.graph[*node_idx].inner(), PkgNode::SchemaVariantChild(SchemaVariantChildNode::$child_node) ) }) { let child_node_idxs: Vec<_> = self .source .graph .neighbors_directed(child_idxs, Outgoing) .collect(); for child_idx in child_node_idxs { entries.push($pkg_type::from_graph(self.source.graph, child_idx)?); } } Ok(entries) } }; } impl<'a> SiPkgSchemaVariant<'a> { pub fn from_graph( graph: &'a Graph<HashedNode<PkgNode>, ()>, node_idx: NodeIndex, ) -> PkgResult<Self> { let schema_variant_hashed_node = &graph[node_idx]; let schema_variant_node = match schema_variant_hashed_node.inner() { PkgNode::SchemaVariant(node) => node.clone(), unexpected => { return Err(SiPkgError::UnexpectedPkgNodeType( PkgNode::SCHEMA_VARIANT_KIND_STR, unexpected.node_kind_str(), )); } }; let schema_variant = Self { version: schema_variant_node.version.to_owned(), data: schema_variant_node.data.map(|data| SiPkgSchemaVariantData { version: schema_variant_node.version, link: data.link, color: data.color, component_type: data.component_type, func_unique_id: data.func_unique_id, description: data.description, }), unique_id: schema_variant_node.unique_id, deleted: schema_variant_node.deleted, is_builtin: schema_variant_node.is_builtin, hash: schema_variant_hashed_node.hash(), source: Source::new(graph, node_idx), }; Ok(schema_variant) } pub fn data(&self) -> Option<&SiPkgSchemaVariantData> { self.data.as_ref() } pub fn version(&self) -> &str { self.version.as_ref() } pub fn unique_id(&self) -> Option<&str> { self.unique_id.as_deref() } pub fn source(&self) -> Source<'a> { self.source.clone() } pub fn deleted(&self) -> bool { self.deleted } pub fn is_builtin(&self) -> bool { self.is_builtin } impl_variant_children_from_graph!(sockets, SchemaVariantChildNode::Sockets, SiPkgSocket); impl_variant_children_from_graph!( leaf_functions, SchemaVariantChildNode::LeafFunctions, SiPkgLeafFunction ); impl_variant_children_from_graph!( action_funcs, SchemaVariantChildNode::ActionFuncs, SiPkgActionFunc ); impl_variant_children_from_graph!(auth_funcs, SchemaVariantChildNode::AuthFuncs, SiPkgAuthFunc); impl_variant_children_from_graph!( si_prop_funcs, SchemaVariantChildNode::SiPropFuncs, SiPkgSiPropFunc ); impl_variant_children_from_graph!(secrets, SchemaVariantChildNode::Secrets, SiPkgProp); impl_variant_children_from_graph!( secret_definitions, SchemaVariantChildNode::SecretDefinition, SiPkgProp ); impl_variant_children_from_graph!( root_prop_funcs, SchemaVariantChildNode::RootPropFuncs, SiPkgRootPropFunc ); impl_variant_children_from_graph!( management_funcs, SchemaVariantChildNode::ManagementFuncs, SiPkgManagementFunc ); fn prop_stack_from_source<I>( source: &Source<'a>, node_idx: NodeIndex, parent_info: Option<I>, ) -> PkgResult<Vec<(SiPkgProp<'a>, Option<I>)>> where I: ToOwned + Clone + std::fmt::Debug, { Ok( match source .graph .neighbors_directed(node_idx, Outgoing) .find(|node_idx| { matches!( &source.graph[*node_idx].inner(), PkgNode::PropChild(PropChildNode::Props) ) }) { Some(prop_child_idxs) => { // Create the child props let child_props = source .graph .neighbors_directed(prop_child_idxs, Outgoing) .map(|child_idx| SiPkgProp::from_graph(source.graph, child_idx)) .map_ok(|child_prop| (child_prop, parent_info.to_owned())); // If the node has a child order, honor it let node = source .graph .node_weight(node_idx) .ok_or(SiPkgError::prop_tree_invalid("node index not found"))? .inner(); match node.child_order() { Some(child_order) => { // Map the child props by name so we can collect them in order let mut props_by_name = HashMap::new(); for child_prop in child_props { let child_prop = child_prop?; if props_by_name .insert(child_prop.0.name().to_owned(), child_prop) .is_some() { return Err(SiPkgError::prop_tree_invalid("duplicate prop")); } } // Collect the child props in reverse order let child_props = child_order .iter() .rev() .map(|name| { props_by_name .remove(name) .ok_or(SiPkgError::prop_tree_invalid("dup prop order")) }) .try_collect()?; if !props_by_name.is_empty() { return Err(SiPkgError::prop_tree_invalid("prop order missing")); } child_props } // If there is no child order, just return the child props None => child_props.try_collect()?, } } // If there are no child props, return empty vec None => vec![], }, ) } async fn get_prop_root_idx( &self, prop_root: SchemaVariantSpecPropRoot, ) -> PkgResult<Option<NodeIndex>> { let maybe_node_index = self .source .graph .neighbors_directed(self.source.node_idx, Outgoing) .find(|node_idx| { let node = &self.source.graph[*node_idx].inner(); match prop_root { SchemaVariantSpecPropRoot::Domain => matches!( node, PkgNode::SchemaVariantChild(SchemaVariantChildNode::Domain) ), SchemaVariantSpecPropRoot::ResourceValue => matches!( node, PkgNode::SchemaVariantChild(SchemaVariantChildNode::ResourceValue) ), SchemaVariantSpecPropRoot::Secrets => matches!( node, PkgNode::SchemaVariantChild(SchemaVariantChildNode::Secrets) ), SchemaVariantSpecPropRoot::SecretDefinition => { matches!( node, PkgNode::SchemaVariantChild(SchemaVariantChildNode::SecretDefinition) ) } SchemaVariantSpecPropRoot::Resource | SchemaVariantSpecPropRoot::Code | SchemaVariantSpecPropRoot::Qualification => false, } }); // NOTE(victor, fletcher): Previous versions of prop trees didn't have the Secrets field // under root, so we need to successfully ignore them for backwards compatibility if maybe_node_index.is_some() || prop_root == SchemaVariantSpecPropRoot::Secrets { Ok(maybe_node_index) } else { let maybe_kind = match prop_root { SchemaVariantSpecPropRoot::Domain => SchemaVariantChildNode::Domain.kind_str(), SchemaVariantSpecPropRoot::ResourceValue => { SchemaVariantChildNode::ResourceValue.kind_str() } SchemaVariantSpecPropRoot::Secrets => SchemaVariantChildNode::Secrets.kind_str(), SchemaVariantSpecPropRoot::SecretDefinition => { SchemaVariantChildNode::SecretDefinition.kind_str() } _ => "resource", }; Err(SiPkgError::SchemaVariantChildNotFound(maybe_kind)) } } pub async fn visit_prop_tree<F, Fut, I, C, E>( &self, prop_root: SchemaVariantSpecPropRoot, process_prop_fn: F, parent_info: Option<I>, context: C, ) -> Result<(), E> where F: Fn(SiPkgProp<'a>, Option<I>, C) -> Fut, Fut: Future<Output = Result<Option<I>, E>>, E: std::convert::From<SiPkgError>, I: ToOwned + Clone + std::fmt::Debug, C: 'a + Copy, { if let Some(prop_root_idx) = self.get_prop_root_idx(prop_root).await? { let mut child_node_idxs: Vec<_> = self .source .graph .neighbors_directed(prop_root_idx, Outgoing) .collect(); let prop_root_node_idx = match child_node_idxs.pop() { Some(idx) => idx, None => Err(SiPkgError::PropRootNotFound(prop_root, self.hash()))?, }; if !child_node_idxs.is_empty() { Err(SiPkgError::PropRootMultipleFound(prop_root, self.hash()))?; } // Skip processing the "root" prop for this tree as a `dal::SchemaVariant::new` already // guarantees such a prop has already been created. Rather, we will push all immediate // children of the domain prop to be ready for processing. let mut stack = Self::prop_stack_from_source(&self.source, prop_root_node_idx, parent_info)?; while let Some((prop, parent_info)) = stack.pop() { let node_idx = prop.source().node_idx; let new_parent_info = process_prop_fn(prop, parent_info.to_owned(), context).await?; stack.extend(Self::prop_stack_from_source( &self.source, node_idx, new_parent_info, )?); } } Ok(()) } pub fn hash(&self) -> Hash { self.hash } async fn build_prop_specs( &self, prop_root: SchemaVariantSpecPropRoot, builder: &mut SchemaVariantSpecBuilder, ) -> PkgResult<()> { let context = PropSpecVisitContext { prop_stack: Mutex::new(VecDeque::new()), prop_parents: Mutex::new(HashMap::new()), }; self.visit_prop_tree(prop_root, create_prop_stack, None, &context) .await?; let prop_stack = context.prop_stack.into_inner(); let prop_parents = context.prop_parents.into_inner(); let mut prop_children: HashMap<String, Vec<PropSpec>> = HashMap::new(); let mut root_children = vec![]; // This is a depth-first post-order traversal (parents after children), with the // hitch that the child order is reversed because we're reversing a depth-first // pre-order traversal for (path, mut prop) in prop_stack { if let Some(children) = prop_children.get(&path) { match prop .get_kind() .ok_or(SiPkgError::prop_tree_invalid("Prop missing a kind"))? { PropSpecKind::Array | PropSpecKind::Map => { if children.len() > 1 { return Err(SiPkgError::prop_tree_invalid( "Array or map has more than one direct child", )); } let type_prop = children.first().ok_or(SiPkgError::prop_tree_invalid( "Array or map prop missing type prop", ))?; prop.type_prop(type_prop.clone()); } PropSpecKind::Object => { let mut children = children.clone(); // We have to reverse the children to be able to process them in the correct // order, because we processed the children in reverse order (the prop stack // traverses props depth-first, post-order with reverse child order due // to how things get pushed on it) children.reverse(); prop.entries(children); } _ => { return Err(SiPkgError::prop_tree_invalid( "Leaf prop (String, Number, Boolean) cannot have children", )); } } } let spec = prop.build()?; match prop_parents.get(&path) { Some(parent_path) => match prop_children.entry(parent_path.clone()) { Entry::Occupied(mut occupied) => { occupied.get_mut().push(spec); } Entry::Vacant(vacant) => { vacant.insert(vec![spec]); } }, None => { root_children.push(spec); } } } // We have to reverse the children to be able to process them in the correct // order, because we processed the children in reverse order (the prop stack // traverses props depth-first, post-order with reverse child order due // to how things get pushed on it) root_children.reverse(); for spec in root_children { builder.prop(prop_root, spec); } Ok(()) } pub async fn to_spec(&self) -> PkgResult<SchemaVariantSpec> { let mut builder = SchemaVariantSpec::builder(); builder .version(self.version()) .deleted(self.deleted) .is_builtin(self.is_builtin); if let Some(unique_id) = self.unique_id() { builder.unique_id(unique_id); } if let Some(data) = self.data() { let mut data_builder = SchemaVariantSpecData::builder(); data_builder.version(self.version()); data_builder.component_type(data.component_type()); if let Some(link) = data.link() { data_builder.link(link.to_owned()); } if let Some(color) = data.color() { data_builder.color(color); } data_builder.description(data.description().map(ToOwned::to_owned)); data_builder.func_unique_id(data.func_unique_id()); builder.data(data_builder.build()?); } for action_func in self.action_funcs()? { builder.action_func(action_func.try_into()?); } for func in self.auth_funcs()? { builder.auth_func(func.try_into()?); } for func in self.leaf_functions()? { builder.leaf_function(func.try_into()?); } for socket in self.sockets()? { builder.socket(socket.try_into()?); } for si_prop_func in self.si_prop_funcs()? { builder.si_prop_func(si_prop_func.try_into()?); } for management_func in self.management_funcs()? { builder.management_func(management_func.try_into()?); } for func in self.root_prop_funcs()? { builder.root_prop_func(func.try_into()?); } self.build_prop_specs(SchemaVariantSpecPropRoot::Domain, &mut builder) .await?; self.build_prop_specs(SchemaVariantSpecPropRoot::Secrets, &mut builder) .await?; self.build_prop_specs(SchemaVariantSpecPropRoot::ResourceValue, &mut builder) .await?; Ok(builder.build()?) } } #[derive(Debug)] // These have to be some kind of tokio type with interior mutability because we need the Send trait // for axum to be happy (it rejects using RefCell) struct PropSpecVisitContext { // All the props in depth-first order prop_stack: Mutex<VecDeque<(String, PropSpecBuilder)>>, // A map from the prop path to prop's parent path prop_parents: Mutex<HashMap<String, String>>, } const PROP_PATH_SEPARATOR: &str = "\x0B"; async fn create_prop_stack( spec: SiPkgProp<'_>, parent_path: Option<String>, ctx: &PropSpecVisitContext, ) -> PkgResult<Option<String>> { let path = match &parent_path { Some(parent_path) => format!("{}{}{}", parent_path, PROP_PATH_SEPARATOR, spec.name()), None => spec.name().to_owned(), }; let mut builder = PropSpec::builder(); builder.has_data(false); let default_value = match &spec { SiPkgProp::String { data, .. } | SiPkgProp::Boolean { data, .. } | SiPkgProp::Number { data, .. } => { data.as_ref().and_then(|data| data.default_value.to_owned()) } _ => None, }; match &spec { SiPkgProp::String { .. } => { builder.kind(PropSpecKind::String); if let Some(dv) = default_value { builder.default_value(dv); } } SiPkgProp::Json { .. } => { builder.kind(PropSpecKind::Json); if let Some(dv) = default_value { builder.default_value(serde_json::to_value(dv)?); } } SiPkgProp::Boolean { .. } => { builder.kind(PropSpecKind::Boolean); if let Some(dv) = default_value { builder.default_value(serde_json::to_value(dv)?); } } SiPkgProp::Number { .. } => { builder.kind(PropSpecKind::Number); if let Some(dv) = default_value { builder.default_value(dv); } } SiPkgProp::Float { .. } => { builder.kind(PropSpecKind::Float); if let Some(dv) = default_value { builder.default_value(dv); } } SiPkgProp::Object { .. } => { builder.kind(PropSpecKind::Object); } SiPkgProp::Array { .. } => { builder.kind(PropSpecKind::Array); } SiPkgProp::Map { .. } => { builder.kind(PropSpecKind::Map); for map_key_func in spec.map_key_funcs()? { builder.map_key_func(MapKeyFuncSpec::try_from(map_key_func)?); } } } match &spec { SiPkgProp::String { name, data, .. } | SiPkgProp::Json { name, data, .. } | SiPkgProp::Map { name, data, .. } | SiPkgProp::Array { name, data, .. } | SiPkgProp::Number { name, data, .. } | SiPkgProp::Float { name, data, .. } | SiPkgProp::Object { name, data, .. } | SiPkgProp::Boolean { name, data, .. } => { builder.name(name); if let Some(SiPkgPropData { name, hidden, widget_kind, widget_options, func_unique_id, doc_link, documentation, default_value, validation_format, ui_optionals, }) = data { builder .has_data(true) .name(name) .hidden(*hidden) .widget_kind(*widget_kind) .ui_optionals(ui_optionals.clone()); if let Some(widget_options) = widget_options { builder.widget_options(widget_options.to_owned()); } if let Some(func_unique_id) = func_unique_id { builder.func_unique_id(func_unique_id.as_str()); for input in spec.inputs()? { builder.input(AttrFuncInputSpec::try_from(input)?); } } if let Some(doc_link) = doc_link { builder.doc_link(doc_link.to_owned()); } if let Some(documentation) = documentation { builder.documentation(documentation.to_owned()); } if let Some(default_value) = default_value { builder.default_value(default_value.to_owned()); } if let Some(validation_format) = validation_format { builder.validation_format(validation_format.to_owned()); } } } } let mut stack = ctx.prop_stack.lock().await; stack.push_front((path.to_owned(), builder)); if let Some(parent_path) = parent_path { match ctx.prop_parents.lock().await.entry(path.to_owned()) { Entry::Occupied(_) => { return Err(SiPkgError::prop_tree_invalid( "Prop has more than one parent", )); } Entry::Vacant(entry) => { entry.insert(parent_path); } } } Ok(Some(path)) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server