Skip to main content
Glama
schema.rs11.7 kB
//! This module contains the ability to construct a [`schema`](PropertyEditorSchema) for a //! [`SchemaVariant`](crate::SchemaVariant)'s properties. use std::collections::{ HashMap, VecDeque, }; use serde::{ Deserialize, Serialize, }; use si_pkg::PropSpecWidgetKind; use strum::{ AsRefStr, Display, EnumString, }; use crate::{ DalContext, Prop, PropId, PropKind, SchemaVariant, SchemaVariantId, prop::{ PropPath, WidgetOptions, }, property_editor::{ PropertyEditorPropId, PropertyEditorResult, }, }; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PropertyEditorSchema { pub root_prop_id: PropertyEditorPropId, pub props: HashMap<PropertyEditorPropId, PropertyEditorProp>, pub child_props: HashMap<PropertyEditorPropId, Vec<PropertyEditorPropId>>, } impl PropertyEditorSchema { pub async fn assemble( ctx: &DalContext, schema_variant_id: SchemaVariantId, has_resource: bool, ) -> PropertyEditorResult<Self> { let mut props = HashMap::new(); let mut child_props = HashMap::new(); let builder = PropertyEditorPropBuilder::new(ctx, schema_variant_id).await?; // Get the root prop and load it into the work queue. let root_prop_id = Prop::find_prop_id_by_path(ctx, schema_variant_id, &PropPath::new(["root"])).await?; let root_prop = Prop::get_by_id(ctx, root_prop_id).await?; let root_property_editor_prop = builder.build(ctx, root_prop, has_resource).await?; let root_property_editor_prop_id = root_property_editor_prop.id; props.insert(root_property_editor_prop_id, root_property_editor_prop); let mut work_queue = VecDeque::from([(root_prop_id, root_property_editor_prop_id)]); while let Some((prop_id, property_editor_prop_id)) = work_queue.pop_front() { // Collect all child props. let mut cache = Vec::new(); { let child_props = Prop::direct_child_props_ordered(ctx, prop_id).await?; for child_prop in child_props { // Skip anything at and under "/root/secret_definition", // also skip hidden props EXCEPT /root/resource_value if prop_id == root_prop_id && child_prop.name == "secret_definition" || (child_prop.hidden && child_prop.name != "resource_value") { continue; } cache.push(child_prop); } } // Now that we have the child props, prepare the property editor props and load the work queue. let mut child_property_editor_prop_ids = Vec::new(); for child_prop in cache { let child_prop_id = child_prop.id; let child_property_editor_prop = builder.build(ctx, child_prop, has_resource).await?; // Load the work queue with the child prop. work_queue.push_back((child_prop_id, child_property_editor_prop.id)); // Cache the child property editor props to eventually insert into the child property editor props map. child_property_editor_prop_ids.push(child_property_editor_prop.id); // Insert the child property editor prop into the props map. props.insert(child_property_editor_prop.id, child_property_editor_prop); } child_props.insert(property_editor_prop_id, child_property_editor_prop_ids); } Ok(PropertyEditorSchema { root_prop_id: root_prop_id.into(), props, child_props, }) } } /// A builder for creating [`PropertyEditorProps`](PropertyEditorProp). #[derive(Debug)] pub struct PropertyEditorPropBuilder { /// If the [`SchemaVariant`] is a secret-defining [`SchemaVariant`], this will be populated /// with the [`PropId`](Prop) of the [`Prop`] underneath "/root/secrets" corresponding to /// the definition. origin_secret_prop_id: Option<PropId>, } impl PropertyEditorPropBuilder { pub async fn new( ctx: &DalContext, schema_variant_id: SchemaVariantId, ) -> PropertyEditorResult<Self> { // We need to know if we are secret-defining schema variant and what the corresponding // "/root/secrets" prop is. We need to know this because a secret prop can only be edited // if it is the origin secret prop on its corresponding secret-defining component. let origin_secret_prop_id = if SchemaVariant::is_secret_defining(ctx, schema_variant_id) .await? { let output_socket = SchemaVariant::find_output_socket_for_secret_defining_id(ctx, schema_variant_id) .await?; Some( Prop::find_prop_id_by_path( ctx, schema_variant_id, &PropPath::new(["root", "secrets", output_socket.name()]), ) .await?, ) } else { None }; Ok(Self { origin_secret_prop_id, }) } /// This non-consuming method creates a [`PropertyEditorProp`] with a given [`Prop`]. pub async fn build( &self, ctx: &DalContext, prop: Prop, has_resource: bool, ) -> PropertyEditorResult<PropertyEditorProp> { let default_can_be_set_by_socket = !prop.input_socket_sources(ctx).await?.is_empty(); let mut is_create_only = false; let filtered_widget_options = prop.widget_options.map(|options| { options .into_iter() .filter(|option| { if option.label() == "si_create_only_prop" { is_create_only = true; false } else { true } }) .collect::<WidgetOptions>() }); Ok(PropertyEditorProp { id: prop.id.into(), name: prop.name, kind: prop.kind.into(), widget_kind: PropertyEditorPropWidgetKind::new( prop.widget_kind, filtered_widget_options, ), doc_link: prop.doc_link, documentation: prop.documentation, validation_format: prop.validation_format, default_can_be_set_by_socket, is_origin_secret: match self.origin_secret_prop_id { Some(prop_id) => prop_id == prop.id, None => false, }, create_only: is_create_only && has_resource, }) } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PropertyEditorProp { pub id: PropertyEditorPropId, pub name: String, pub kind: PropertyEditorPropKind, pub widget_kind: PropertyEditorPropWidgetKind, pub doc_link: Option<String>, pub documentation: Option<String>, pub validation_format: Option<String>, pub default_can_be_set_by_socket: bool, pub is_origin_secret: bool, pub create_only: bool, } #[remain::sorted] #[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum PropertyEditorPropKind { Array, Boolean, Float, Integer, Json, Map, Object, String, } impl From<PropKind> for PropertyEditorPropKind { fn from(prop_kind: PropKind) -> Self { match prop_kind { PropKind::Array => Self::Array, PropKind::Boolean => Self::Boolean, PropKind::Float => Self::Float, PropKind::Integer => Self::Integer, PropKind::Json => Self::Json, PropKind::Object => Self::Object, PropKind::String => Self::String, PropKind::Map => Self::Map, } } } #[remain::sorted] #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "camelCase")] pub enum PropertyEditorPropWidgetKind { Array, Checkbox, CodeEditor, Color, ComboBox { options: Option<WidgetOptions> }, Header, Map, Password, Secret { options: Option<WidgetOptions> }, Select { options: Option<WidgetOptions> }, Text, TextArea, } /// This type goes into the content store. It cannot be reordered, and existing variants cannot be changed. new variants can be added to the end. #[derive( AsRefStr, Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Display, EnumString, Copy, )] #[serde(rename_all = "camelCase")] #[strum(serialize_all = "camelCase")] pub enum WidgetKind { Array, Checkbox, CodeEditor, Color, /// Provides a text input with auto-completion for corresponding "primitive" (e.g. string, number, boolean) /// [`PropKinds`](crate::PropKind). ComboBox, Header, Map, Password, Secret, /// Provides a select box for corresponding "primitive" (e.g. string, number, boolean) /// [`PropKinds`](crate::PropKind). Select, Text, TextArea, } impl From<WidgetKind> for PropSpecWidgetKind { fn from(value: WidgetKind) -> Self { match value { WidgetKind::Array => Self::Array, WidgetKind::Checkbox => Self::Checkbox, WidgetKind::CodeEditor => Self::CodeEditor, WidgetKind::Header => Self::Header, WidgetKind::Map => Self::Map, WidgetKind::Password => Self::Password, WidgetKind::Select => Self::Select, WidgetKind::Color => Self::Color, WidgetKind::Secret => Self::Secret, WidgetKind::Text => Self::Text, WidgetKind::TextArea => Self::TextArea, WidgetKind::ComboBox => Self::ComboBox, } } } impl From<&PropSpecWidgetKind> for WidgetKind { fn from(value: &PropSpecWidgetKind) -> Self { match value { PropSpecWidgetKind::Array => Self::Array, PropSpecWidgetKind::Checkbox => Self::Checkbox, PropSpecWidgetKind::CodeEditor => Self::CodeEditor, PropSpecWidgetKind::Header => Self::Header, PropSpecWidgetKind::Map => Self::Map, PropSpecWidgetKind::Password => Self::Password, PropSpecWidgetKind::Select => Self::Select, PropSpecWidgetKind::Color => Self::Color, PropSpecWidgetKind::Secret => Self::Secret, PropSpecWidgetKind::Text => Self::Text, PropSpecWidgetKind::TextArea => Self::TextArea, PropSpecWidgetKind::ComboBox => Self::ComboBox, } } } impl PropertyEditorPropWidgetKind { pub fn new(widget_kind: WidgetKind, widget_options: Option<WidgetOptions>) -> Self { match widget_kind { WidgetKind::Array => Self::Array, WidgetKind::Checkbox => Self::Checkbox, WidgetKind::CodeEditor => Self::CodeEditor, WidgetKind::Header => Self::Header, WidgetKind::Map => Self::Map, WidgetKind::Password => Self::Password, WidgetKind::Select => Self::Select { options: widget_options, }, WidgetKind::Color => Self::Color, WidgetKind::Secret => Self::Secret { options: widget_options, }, WidgetKind::Text => Self::Text, WidgetKind::TextArea => Self::TextArea, WidgetKind::ComboBox => Self::ComboBox { options: widget_options, }, } } }

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