Skip to main content
Glama
import.rs69.9 kB
use std::{ collections::{ HashMap, HashSet, }, fmt::Debug, path::Path, str::FromStr, }; use chrono::DateTime; use si_events::ulid::Ulid; use si_pkg::{ SchemaVariantSpecPropRoot, SiPkg, SiPkgActionFunc, SiPkgAttrFuncInputView, SiPkgAuthFunc, SiPkgComponent, SiPkgEdge, SiPkgError, SiPkgFunc, SiPkgFuncArgument, SiPkgKind, SiPkgLeafFunction, SiPkgManagementFunc, SiPkgMetadata, SiPkgProp, SiPkgPropData, SiPkgSchema, SiPkgSchemaData, SiPkgSchemaVariant, SiPkgSocket, SiPkgSocketData, SocketSpecKind, }; use telemetry::prelude::*; use tokio::sync::Mutex; use super::{ PkgError, PkgResult, UpdateMode, }; use crate::{ AttributePrototype, AttributePrototypeId, DalContext, EdgeWeightKind, Func, FuncId, InputSocket, OutputSocket, OutputSocketId, Prop, PropId, PropKind, Schema, SchemaVariant, SchemaVariantId, SocketKind, action::prototype::ActionPrototype, attribute::prototype::argument::{ AttributePrototypeArgument, AttributePrototypeArgumentId, value_source::ValueSource, }, authentication_prototype::{ AuthenticationPrototype, AuthenticationPrototypeId, }, func::{ FuncKind, argument::FuncArgument, binding::attribute::AttributeBinding, intrinsics::IntrinsicFunc, leaf::{ LeafInputLocation, LeafKind, }, }, management::prototype::ManagementPrototype, module::{ Module, ModuleId, }, prop::PropPath, schema::variant::SchemaVariantJson, socket::connection_annotation::ConnectionAnnotation, }; #[derive(Clone, Debug)] pub enum Thing { ActionPrototype(ActionPrototype), AuthPrototype(AuthenticationPrototype), // DeprecatedActionPrototype(DeprecatedActionPrototype), // AttributePrototypeArgument(AttributePrototypeArgument), // Component((Component, Node)), // Edge(Edge), Func(Func), #[allow(dead_code)] FuncArgument(FuncArgument), Schema(Schema), SchemaVariant(SchemaVariant), Socket(Box<(Option<InputSocket>, Option<OutputSocket>)>), } pub type ThingMap = super::ChangeSetThingMap<String, Thing>; #[derive(Clone, Debug, Default)] pub struct ImportOptions { /// List of schema names to import. If set to `None`, the importer will import everything pub schemas: Option<Vec<String>>, pub skip_import_funcs: Option<HashMap<String, Func>>, /// If set to `true`, the importer will install the assets from the module /// but will not make a record of the install as an "installed module". pub no_record: bool, /// If set to `true` then we will set the functions to a builtin /// in the UI. They will be marked as such. pub is_builtin: bool, /// Locked schema variants can't be edited directly. Setting this to `true` will create /// editable components. pub create_unlocked: bool, /// The "schema id" for this asset, provided by the module index API pub schema_id: Option<Ulid>, /// A list of "past hashes" for this module, used to find the existing /// schema if a schema_id is not provided pub past_module_hashes: Option<Vec<String>>, /// Whether to skip or update existing functions pub update_mode: UpdateMode, } #[allow(clippy::too_many_arguments)] async fn import_change_set( ctx: &DalContext, metadata: &SiPkgMetadata, funcs: &[SiPkgFunc<'_>], schemas: &[SiPkgSchema<'_>], _components: &[SiPkgComponent<'_>], _edges: &[SiPkgEdge<'_>], installed_module: Option<Module>, thing_map: &mut ThingMap, options: &ImportOptions, ) -> PkgResult<( Vec<SchemaVariantId>, Vec<(String, Vec<bool /*ImportAttributeSkip*/>)>, Vec<bool /*ImportEdgeSkip*/>, )> { // Cache the intrinsic funcs pkg in case we need it. let unsafe_to_install_intrinsic_funcs_pkg = SiPkg::load_from_spec(IntrinsicFunc::pkg_spec()?)?; for func_spec in funcs { if let Some(intrinsic) = IntrinsicFunc::maybe_from_str(func_spec.name()) { let maybe_func_id = match intrinsic { IntrinsicFunc::ResourcePayloadToValue | IntrinsicFunc::NormalizeToArray => { Func::find_id_by_name_and_kind(ctx, func_spec.name(), FuncKind::Intrinsic) .await? } _ => Func::find_id_by_name(ctx, func_spec.name()).await?, }; if let Some(func_id) = maybe_func_id { let func = Func::get_by_id(ctx, func_id).await?; thing_map.insert( func_spec.unique_id().to_owned(), Thing::Func(func.to_owned()), ); } else { let mut override_intrinsic_func_specs = unsafe_to_install_intrinsic_funcs_pkg.funcs_for_name(intrinsic.name())?; let override_intrinsic_func_spec = override_intrinsic_func_specs.pop().ok_or( PkgError::IntrinsicFuncSpecsNoneForName(intrinsic.name().to_owned()), )?; if !override_intrinsic_func_specs.is_empty() { return Err(PkgError::IntrinsicFuncSpecsMultipleForName( intrinsic.name().to_owned(), )); } // We need to override the unique ID so that accessors grab the correct func. thing_map.insert_override( func_spec.unique_id().into(), override_intrinsic_func_spec.unique_id().into(), ); let func = import_func( ctx, &override_intrinsic_func_spec, installed_module.clone(), thing_map, false, options.update_mode.clone(), ) .await?; let args = override_intrinsic_func_spec.arguments()?; if !args.is_empty() { import_func_arguments(ctx, func.id, &args).await?; } } } else { if let Some(Some(func)) = options .skip_import_funcs .as_ref() .map(|skip_funcs| skip_funcs.get(func_spec.unique_id())) { if let Some(module) = installed_module.clone() { module.create_association(ctx, func.id.into()).await?; } // We're not going to import this func but we need it in the map for lookups later thing_map.insert( func_spec.unique_id().to_owned(), Thing::Func(func.to_owned()), ); continue; } let func_spec_data = func_spec .data() .ok_or(PkgError::DataNotFound(func_spec.name().into()))?; // If this func is a transformation, we need to see if it updates an existing func before creating it if func_spec_data.is_transformation() { let func_id = FuncId::from_str(func_spec.unique_id())?; // In the future we may have a strategy to update funcs that already exist, but right now we'll just skip them if Func::get_by_id_opt(ctx, func_id).await?.is_some() { // // if no updated timestamp, skip // let Some(incoming_update_timestamp) = func_spec_data.last_updated_at() else { // continue; // }; // if incoming_update_timestamp > existing_func.timestamp.updated_at { // let func = upsert_func(ctx, func_spec, false).await?; // // thing_map.insert( // func_spec.unique_id().to_owned(), // Thing::Func(func.to_owned()), // ); // // if let Some(module) = installed_module.clone() { // module.create_association(ctx, func.id.into()).await?; // } // } continue; } } let func = import_func( ctx, func_spec, installed_module.clone(), thing_map, options.create_unlocked, options.update_mode.clone(), ) .await?; thing_map.insert( func_spec.unique_id().to_owned(), Thing::Func(func.to_owned()), ); if let Some(module) = installed_module.clone() { module.create_association(ctx, func.id.into()).await?; } let args = func_spec.arguments()?; if !args.is_empty() { import_func_arguments(ctx, func.id, &args).await?; } }; } let mut installed_schema_variant_ids = vec![]; let mut unseen: HashSet<String> = options .schemas .clone() .unwrap_or_default() .iter() .cloned() .collect(); for schema_spec in schemas { let normalized_name = &schema_spec.name().to_string().to_lowercase(); match &options.schemas { None => {} Some(schemas) => { if !schemas.contains(normalized_name) { continue; } } } unseen.remove(normalized_name); debug!( "installing schema '{}' from {}", schema_spec.name(), metadata.name(), ); let schema_variant_ids = import_schema( ctx, schema_spec, installed_module.clone(), thing_map, options.create_unlocked, options.past_module_hashes.clone(), ) .await?; installed_schema_variant_ids.extend(schema_variant_ids); } for schema_name in unseen { error!( "options specified schema '{}', but it's not present on {}", schema_name, metadata.name(), ); } Ok(( installed_schema_variant_ids, vec![], // component_attribute_skips, vec![], // edge_skips, )) } pub async fn import_pkg_from_pkg( ctx: &DalContext, pkg: &SiPkg, options: Option<ImportOptions>, ) -> PkgResult<( Option<ModuleId>, Vec<SchemaVariantId>, Option<Vec<bool /*ImportSkips*/>>, )> { let root_hash = pkg.hash()?.to_string(); let options = options.unwrap_or_default(); if Module::find_by_root_hash(ctx, &root_hash).await?.is_some() { return Err(PkgError::PackageAlreadyInstalled(root_hash)); } let metadata = pkg.metadata()?; let installed_module: Option<Module> = if options.no_record { None } else { Some( Module::new( ctx, metadata.name(), pkg.hash()?.to_string(), metadata.version(), metadata.description(), metadata.created_by(), metadata.created_at(), options.schema_id, ) .await?, ) }; let mut change_set_things = ThingMap::new(); match metadata.kind() { SiPkgKind::Module => { let (installed_schema_variant_ids, _, _) = import_change_set( ctx, &metadata, &pkg.funcs()?, &pkg.schemas()?, &[], &[], installed_module, &mut change_set_things, &options, ) .await?; Ok((None, installed_schema_variant_ids, None)) } SiPkgKind::WorkspaceBackup => Err(PkgError::WorkspaceExportNotSupported()), } } pub async fn import_pkg(ctx: &DalContext, pkg_file_path: impl AsRef<Path>) -> PkgResult<SiPkg> { println!("Importing package from {:?}", pkg_file_path.as_ref()); let pkg = SiPkg::load_from_file(&pkg_file_path).await?; import_pkg_from_pkg(ctx, &pkg, None).await?; Ok(pkg) } async fn create_func( ctx: &DalContext, func_spec: &SiPkgFunc<'_>, is_builtin: bool, ) -> PkgResult<Func> { let name = func_spec.name(); let func_spec_data = func_spec .data() .ok_or(PkgError::DataNotFound(name.into()))?; let func = if func_spec_data.is_transformation() { let func_id = FuncId::from_str(func_spec.unique_id())?; Func::upsert_with_id( ctx, func_id, name, func_spec_data .display_name() .map(|display_name| display_name.to_owned()), func_spec_data.description().map(|desc| desc.to_owned()), func_spec_data.link().map(|l| l.to_string()), func_spec_data.hidden(), is_builtin, func_spec_data.backend_kind().into(), func_spec_data.response_type().into(), Some(func_spec_data.handler().to_owned()), Some(func_spec_data.code_base64().to_owned()), func_spec_data.is_transformation(), func_spec_data.last_updated_at(), ) .await? } else { Func::new( ctx, name, func_spec_data .display_name() .map(|display_name| display_name.to_owned()), func_spec_data.description().map(|desc| desc.to_owned()), func_spec_data.link().map(|l| l.to_string()), func_spec_data.hidden(), is_builtin, func_spec_data.backend_kind().into(), func_spec_data.response_type().into(), Some(func_spec_data.handler().to_owned()), Some(func_spec_data.code_base64().to_owned()), func_spec_data.is_transformation(), ) .await? }; Ok(func) } #[allow(unused)] async fn upsert_func( ctx: &DalContext, func_spec: &SiPkgFunc<'_>, is_builtin: bool, ) -> PkgResult<Func> { let name = func_spec.name(); let func_spec_data = func_spec .data() .ok_or(PkgError::DataNotFound(name.into()))?; let func_id = FuncId::from_str(func_spec.unique_id())?; let func = Func::upsert_with_id( ctx, func_id, name, func_spec_data .display_name() .map(|display_name| display_name.to_owned()), func_spec_data.description().map(|desc| desc.to_owned()), func_spec_data.link().map(|l| l.to_string()), func_spec_data.hidden(), is_builtin, func_spec_data.backend_kind().into(), func_spec_data.response_type().into(), Some(func_spec_data.handler().to_owned()), Some(func_spec_data.code_base64().to_owned()), func_spec_data.is_transformation(), func_spec_data.last_updated_at(), ) .await?; // Update values that depend on the function let attribute_prototypes = AttributePrototype::list_ids_for_func_id(ctx, func_id).await?; for attribute_prototype_id in attribute_prototypes { AttributeBinding::enqueue_dvu_for_impacted_values(ctx, attribute_prototype_id).await?; } Ok(func) } pub async fn import_func( ctx: &DalContext, func_spec: &SiPkgFunc<'_>, installed_module: Option<Module>, thing_map: &mut ThingMap, create_unlocked: bool, update_mode: super::UpdateMode, ) -> PkgResult<Func> { let existing_func = if let Some(ref module) = installed_module { let associated_funcs = module.list_associated_funcs(ctx).await?; associated_funcs .into_iter() .find(|f| f.name == func_spec.name()) } else { None }; let func = match (existing_func, update_mode) { // SkipExisting, just return the func (Some(func), UpdateMode::SkipExisting) => func, // Update existing, do that (Some(existing), UpdateMode::UpdateExisting) => { let func_spec_data = func_spec .data() .ok_or(PkgError::DataNotFound(func_spec.name().into()))?; Func::upsert_with_id( ctx, existing.id, // Use existing func's ID func_spec.name(), func_spec_data.display_name().map(|d| d.to_owned()), func_spec_data.description().map(|d| d.to_owned()), func_spec_data.link().map(|l| l.to_string()), func_spec_data.hidden(), false, // is_builtin func_spec_data.backend_kind().into(), func_spec_data.response_type().into(), Some(func_spec_data.handler().to_owned()), Some(func_spec_data.code_base64().to_owned()), func_spec_data.is_transformation(), func_spec_data.last_updated_at(), ) .await? } // No func, need a new one (None, _) => { let func = create_func(ctx, func_spec, false).await?; if !create_unlocked { func.lock(ctx).await? } else { func } } }; if let Some(installed_pkg) = installed_module { installed_pkg .create_association(ctx, func.id.into()) .await?; } thing_map.insert( func_spec.unique_id().to_owned(), Thing::Func(func.to_owned()), ); Ok(func) } /// Import funcs from a package for module update/upgrade. pub async fn import_funcs_for_module_update( ctx: &DalContext, funcs: Vec<SiPkgFunc<'_>>, ) -> PkgResult<ThingMap> { use crate::func::intrinsics::IntrinsicFunc; let mut thing_map = ThingMap::new(); for func_spec in funcs { // Intrinsic functions should already exist - just look them up and add to thing_map if IntrinsicFunc::maybe_from_str(func_spec.name()).is_some() { if let Some(func_id) = Func::find_id_by_name(ctx, func_spec.name()).await? { let func = Func::get_by_id(ctx, func_id).await?; thing_map.insert(func_spec.unique_id().to_owned(), Thing::Func(func)); continue; } } import_func( ctx, &func_spec, None, &mut thing_map, false, // create_locked super::UpdateMode::UpdateExisting, ) .await?; } Ok(thing_map) } async fn create_func_argument( ctx: &DalContext, func_id: FuncId, func_arg: &SiPkgFuncArgument<'_>, ) -> PkgResult<FuncArgument> { Ok(FuncArgument::new( ctx, func_arg.name(), func_arg.kind().into(), func_arg.element_kind().to_owned().map(|&kind| kind.into()), func_id, ) .await?) } async fn import_func_arguments( ctx: &DalContext, func_id: FuncId, func_arguments: &[SiPkgFuncArgument<'_>], ) -> PkgResult<()> { for arg in func_arguments { create_func_argument(ctx, func_id, arg).await?; } Ok(()) } async fn create_schema( ctx: &DalContext, maybe_existing_schema_id: Option<Ulid>, schema_spec_data: &SiPkgSchemaData, ) -> PkgResult<Schema> { let schema = match maybe_existing_schema_id { Some(id) => Schema::new_with_id(ctx, id.into(), schema_spec_data.name()).await?, None => Schema::new(ctx, schema_spec_data.name()).await?, } .modify(ctx, |schema| { schema.ui_hidden = schema_spec_data.ui_hidden(); Ok(()) }) .await?; Ok(schema) } async fn import_schema( ctx: &DalContext, schema_spec: &SiPkgSchema<'_>, installed_module: Option<Module>, thing_map: &mut ThingMap, create_unlocked: bool, past_hashes: Option<Vec<String>>, ) -> PkgResult<Vec<SchemaVariantId>> { let mut existing_schema: Option<Schema> = None; let mut existing_schema_id = None; if let Some(installed_module) = installed_module.as_ref() { existing_schema_id = installed_module.schema_id(); // loop through past hashes to find matching schema if let Some(maybe_past_hashes) = past_hashes { for past_hash in maybe_past_hashes { // find if there's an existing module // if there is, find the asssociated schemas if let Some(found) = Module::find_by_root_hash(ctx, past_hash).await? { match found.list_associated_schemas(ctx).await?.into_iter().next() { Some(existing) => { existing_schema = Some(existing); break; } None => continue, } } } } } let data = schema_spec .data() .ok_or(PkgError::DataNotFound("schema".into()))?; let schema_already_existed = existing_schema.is_some(); let schema = match existing_schema { None => create_schema(ctx, existing_schema_id, data).await?, Some(installed_schema_record) => installed_schema_record, }; // Even if the asset is already installed, we write a record of the asset installation so that // we can track the installed packages that share schemas. if let Some(module) = installed_module.clone() { module.create_association(ctx, schema.id().into()).await?; } import_schema_variants_for_imported_schema( ctx, schema_spec, installed_module, thing_map, create_unlocked, schema, schema_already_existed, ) .await } async fn import_schema_variants_for_imported_schema( ctx: &DalContext, schema_spec: &SiPkgSchema<'_>, installed_module: Option<Module>, thing_map: &mut ThingMap, create_unlocked: bool, schema: Schema, schema_already_existed: bool, ) -> PkgResult<Vec<SchemaVariantId>> { if let Some(unique_id) = schema_spec.unique_id() { thing_map.insert(unique_id.to_owned(), Thing::Schema(schema.to_owned())); } let data = schema_spec .data() .ok_or(PkgError::DataNotFound("schema".into()))?; let variant_specs_in_schema_spec = schema_spec.variants()?; // Okay, so this is a weird one. Before we even begin importing variants, we need to make sure // everyone agrees on which one is the default. Here's the problem: if the package knows its // default variant, but the first one in the iterator does not have a unique ID, we need to // keep looking through the variants until we find one that does. If none of them have unique // IDs, then the first variant will be considered the default variant in the package. That // sounds completely nuts, but hear me out: in the land before time, packages did not know // their default schema variants and variant specs did not have unique IDs. How did we know // which one was the default back then? We didn't. We just YOLO made the first one the default. // Yeehaw. How do we get around this in a world where they can be mixed and matched? You just // don't trust anything at all whatsoever. Essentially, we will look at all the variants just // to be sure that we have found the default variant or we will fallback to the first. let default_variant_spec_index = match determine_default_variant_spec_index(data, &variant_specs_in_schema_spec) { Some(found_default_variant_spec_index) => found_default_variant_spec_index, None => { // This is only possible if there are no variants in the spec. return Ok(Vec::new()); } }; let mut installed_schema_variant_ids = Vec::new(); for (index, variant_spec) in variant_specs_in_schema_spec.iter().enumerate() { let variant = import_schema_variant( ctx, &schema, schema_spec.clone(), variant_spec, installed_module.clone(), thing_map, None, ) .await?; let variant_id = variant.id(); installed_schema_variant_ids.push(variant_id); // If we are working with the package's default schema variant, then we need to figure out // how to handle it. // // If the schema already existed, then we must leave the package's default variant unlocked // (again, the one we are currently working with). This is because we need to ensure that // the user's local changes to the assets are not automatically clobbered when installing // updated assets from the module index. Not only that, but its corresponding asset func // needs to be unlocked so that the user can regenerate and make changes at will, so we // will do that too. // // If the schema did not already exist, we can safely set the default schema variant and we // don't care about whether or not it becomes locked. let mut can_lock = true; if index == default_variant_spec_index { if schema_already_existed { can_lock = false; variant.unlock_asset_func_without_copy(ctx).await?; } else { schema.set_default_variant_id(ctx, variant_id).await?; } } // We will only block locking the variant if we are currently working with the default // variant and the schema already existed locally. if can_lock && !create_unlocked { variant.lock(ctx).await?; } } Ok(installed_schema_variant_ids) } fn determine_default_variant_spec_index( data: &SiPkgSchemaData, variant_specs_in_schema_spec: &[SiPkgSchemaVariant], ) -> Option<usize> { let mut default_variant_spec_index = None; for (index, variant_spec) in variant_specs_in_schema_spec.iter().enumerate() { match (data.default_schema_variant(), variant_spec.unique_id()) { (Some(default_schema_variant_for_package), Some(variant_spec_unique_id)) => { // This is the ideal case: the package knows its default variant and the // variant spec has a unique ID that matches it. if default_schema_variant_for_package == variant_spec_unique_id { return Some(index); } } (Some(_), None) => { // If the package knows its default variant, then we have a chance to find a // variant spec who has a unique ID that matches it. For now, let's just grab // the first variant spec we see just in case that never happens. if default_variant_spec_index.is_none() { default_variant_spec_index = Some(index); } } (None, _) => { // If the package doesn't know its default variant, then just choose the first // one and return early. This is how importing older packages has worked in // the past. return Some(index); } } } // If this is "None", then there were no variants to import. default_variant_spec_index } #[allow(dead_code)] #[derive(Clone, Debug)] struct AttrFuncInfo { func_unique_id: String, prop_id: PropId, inputs: Vec<SiPkgAttrFuncInputView>, } #[allow(dead_code)] #[remain::sorted] #[derive(Clone, Debug)] enum DefaultValueInfo { Boolean { prop_id: PropId, default_value: bool, }, Number { prop_id: PropId, default_value: i64, }, String { prop_id: PropId, default_value: String, }, } struct PropVisitContext<'a> { pub ctx: &'a DalContext, pub schema_variant_id: SchemaVariantId, pub attr_funcs: Mutex<Vec<AttrFuncInfo>>, pub default_values: Mutex<Vec<DefaultValueInfo>>, pub map_key_funcs: Mutex<Vec<(String, AttrFuncInfo)>>, } async fn import_leaf_function( ctx: &DalContext, leaf_func: SiPkgLeafFunction<'_>, schema_variant_id: SchemaVariantId, thing_map: &mut ThingMap, ) -> PkgResult<()> { let inputs: Vec<LeafInputLocation> = leaf_func .inputs() .iter() .map(|input| input.into()) .collect(); let kind: LeafKind = leaf_func.leaf_kind().into(); match thing_map.get(&leaf_func.func_unique_id().to_owned()) { Some(Thing::Func(func)) => { SchemaVariant::upsert_leaf_function(ctx, schema_variant_id, kind, &inputs, func) .await?; } _ => { return Err(PkgError::MissingFuncUniqueId( leaf_func.func_unique_id().to_string(), "error found while importing leaf function", )); } } Ok(()) } async fn get_identity_func(ctx: &DalContext) -> PkgResult<FuncId> { Ok(Func::find_intrinsic(ctx, IntrinsicFunc::Identity).await?) } async fn create_socket( ctx: &DalContext, data: &SiPkgSocketData, schema_variant_id: SchemaVariantId, ) -> PkgResult<(Option<InputSocket>, Option<OutputSocket>)> { let identity_func_id = get_identity_func(ctx).await?; let connection_annotations: Vec<ConnectionAnnotation> = if data.connection_annotations().contains("tokens") { // This is the new format of connection annotations and will be // in the format [{\"tokens\":[\"region\"]}] // So we can deserialize this directly to Vec<ConnectionAnnotations> let mut stash = vec![]; let raw_cas_values = serde_json::from_str::<Vec<ConnectionAnnotation>>(data.connection_annotations())?; for raw_ca in raw_cas_values { stash.push(raw_ca); } stash } else { // This is now the old format and we need to change how we // deserialize // The old format is a Vec of strings // "[\"text area\"]" let mut stash = vec![]; let raw_cas_values = serde_json::from_str::<Vec<String>>(data.connection_annotations())?; for raw_cas_value in raw_cas_values { let cas_value = ConnectionAnnotation::from_tokens_array(vec![raw_cas_value]); stash.push(cas_value); } stash }; let (input_socket, output_socket) = match data.kind() { SocketSpecKind::Input => { let input_socket = InputSocket::new( ctx, schema_variant_id, data.name(), identity_func_id, data.arity().into(), SocketKind::Standard, Some(connection_annotations), ) .await?; (Some(input_socket), None) } SocketSpecKind::Output => { let output_socket = OutputSocket::new( ctx, schema_variant_id, data.name(), None, identity_func_id, data.arity().into(), SocketKind::Standard, Some(connection_annotations), ) .await?; (None, Some(output_socket)) } }; // TODO: add modify_by_id to socket, ui hide frames // socket.set_ui_hidden(ctx, data.ui_hidden()).await?; Ok((input_socket, output_socket)) } async fn import_socket( ctx: &DalContext, socket_spec: SiPkgSocket<'_>, schema_variant_id: SchemaVariantId, thing_map: &mut ThingMap, ) -> PkgResult<()> { let (input_socket, output_socket) = { let data = socket_spec .data() .ok_or(PkgError::DataNotFound(socket_spec.name().into()))?; create_socket(ctx, data, schema_variant_id).await? }; if let Some(unique_id) = socket_spec.unique_id() { thing_map.insert( unique_id.to_owned(), Thing::Socket(Box::new(( input_socket.to_owned(), output_socket.to_owned(), ))), ); } match ( socket_spec.data().and_then(|data| data.func_unique_id()), output_socket, input_socket, ) { (Some(func_unique_id), Some(output_socket), None) => { import_attr_func_for_output_socket( ctx, schema_variant_id, output_socket.id(), func_unique_id, socket_spec.inputs()?.drain(..).map(Into::into).collect(), thing_map, ) .await?; } (Some(_), _, Some(_)) => {} _ => {} } Ok(()) } async fn create_action_prototype( ctx: &DalContext, action_func_spec: &SiPkgActionFunc<'_>, func_id: FuncId, schema_variant_id: SchemaVariantId, ) -> PkgResult<ActionPrototype> { let kind: crate::action::prototype::ActionKind = action_func_spec.kind().into(); let name = action_func_spec .name() .map_or_else(|| kind.to_string(), |n| n.to_owned()); let proto = ActionPrototype::new(ctx, kind, name, None, schema_variant_id, func_id).await?; Ok(proto) } async fn import_management_func( ctx: &DalContext, management_func_spec: &SiPkgManagementFunc<'_>, schema_variant_id: SchemaVariantId, thing_map: &ThingMap, ) -> PkgResult<ManagementPrototype> { let Some(Thing::Func(func)) = thing_map.get(&management_func_spec.func_unique_id().to_owned()) else { return Err(PkgError::MissingFuncUniqueId( management_func_spec.func_unique_id().into(), "error found while importing management func", )); }; let func_id = func.id; let prototype = ManagementPrototype::new( ctx, management_func_spec.name().to_string(), management_func_spec.description().map(Into::into), func_id, schema_variant_id, ) .await?; Ok(prototype) } async fn import_action_func( ctx: &DalContext, action_func_spec: &SiPkgActionFunc<'_>, schema_variant_id: SchemaVariantId, thing_map: &ThingMap, ) -> PkgResult<Option<ActionPrototype>> { let prototype = match thing_map.get(&action_func_spec.func_unique_id().to_owned()) { Some(Thing::Func(func)) => { let func_id = func.id; if let Some(unique_id) = action_func_spec.unique_id() { match thing_map.get(&unique_id.to_owned()) { Some(Thing::ActionPrototype(_prototype)) => { return Err(PkgError::WorkspaceExportNotSupported()); } _ => { if action_func_spec.deleted() { None } else { let action_prototype = create_action_prototype( ctx, action_func_spec, func_id, schema_variant_id, ) .await?; Some(action_prototype) } } } } else { let action_prototype = create_action_prototype(ctx, action_func_spec, func_id, schema_variant_id) .await?; Some(action_prototype) } } _ => { return Err(PkgError::MissingFuncUniqueId( action_func_spec.func_unique_id().into(), "error found while importing action func", )); } }; Ok(prototype) } async fn import_auth_func( ctx: &DalContext, func_spec: &SiPkgAuthFunc<'_>, schema_variant_id: SchemaVariantId, thing_map: &ThingMap, ) -> PkgResult<Option<AuthenticationPrototype>> { let prototype = match thing_map.get(&func_spec.func_unique_id().to_owned()) { Some(Thing::Func(func)) => { let func_id = func.id; if let Some(unique_id) = func_spec.unique_id() { match thing_map.get(&unique_id.to_owned()) { Some(Thing::AuthPrototype(_prototype)) => { return Err(PkgError::WorkspaceExportNotSupported()); } _ => { if func_spec.deleted() { None } else { SchemaVariant::new_authentication_prototype( ctx, func_id, schema_variant_id, ) .await?; Some(AuthenticationPrototype { id: AuthenticationPrototypeId::generate(), func_id, schema_variant_id, }) } } } } else { SchemaVariant::new_authentication_prototype(ctx, func_id, schema_variant_id) .await?; Some(AuthenticationPrototype { id: AuthenticationPrototypeId::generate(), func_id, schema_variant_id, }) } } _ => { return Err(PkgError::MissingFuncUniqueId( func_spec.func_unique_id().into(), "error found while importing auth func", )); } }; Ok(prototype) } #[derive(Default, Clone, Debug)] struct CreatePropsSideEffects { attr_funcs: Vec<AttrFuncInfo>, default_values: Vec<DefaultValueInfo>, map_key_funcs: Vec<(String, AttrFuncInfo)>, } impl IntoIterator for CreatePropsSideEffects { type Item = CreatePropsSideEffects; type IntoIter = std::vec::IntoIter<Self::Item>; fn into_iter(self) -> Self::IntoIter { vec![self].into_iter() } } impl Extend<CreatePropsSideEffects> for CreatePropsSideEffects { fn extend<T: IntoIterator<Item = CreatePropsSideEffects>>(&mut self, iter: T) { for element in iter { self.attr_funcs.extend(element.attr_funcs); self.default_values.extend(element.default_values); self.map_key_funcs.extend(element.map_key_funcs); } } } async fn create_props( ctx: &DalContext, variant_spec: &SiPkgSchemaVariant<'_>, prop_root: SchemaVariantSpecPropRoot, prop_root_prop_id: PropId, schema_variant_id: SchemaVariantId, ) -> PkgResult<CreatePropsSideEffects> { let context = PropVisitContext { ctx, schema_variant_id, attr_funcs: Mutex::new(vec![]), default_values: Mutex::new(vec![]), map_key_funcs: Mutex::new(vec![]), }; let parent_info = ParentPropInfo { prop_id: prop_root_prop_id, }; variant_spec .visit_prop_tree(prop_root, create_prop, Some(parent_info), &context) .await?; Ok(CreatePropsSideEffects { attr_funcs: context.attr_funcs.into_inner(), default_values: context.default_values.into_inner(), map_key_funcs: context.map_key_funcs.into_inner(), }) } /// Import only new [`Funcs`](Func) and return a [`ThingMap`] with all [`Funcs`](Func) included. This is used for /// importing a standalone [`SchemaVariant`]. pub async fn import_only_new_funcs( ctx: &DalContext, funcs: Vec<SiPkgFunc<'_>>, ) -> PkgResult<ThingMap> { let mut thing_map = ThingMap::new(); // Cache the intrinsic funcs pkg in case we need it. let unsafe_to_install_intrinsic_funcs_pkg = SiPkg::load_from_spec(IntrinsicFunc::pkg_spec()?)?; // Iterate through all func specs. If the func is an intrinsic, we need to handle it // separately. If it is any other kind of func, we find or create the func and find or create // its arguments. for func_spec in funcs { if let Some(intrinsic) = IntrinsicFunc::maybe_from_str(func_spec.name()) { if intrinsic == IntrinsicFunc::ResourcePayloadToValue || intrinsic == IntrinsicFunc::NormalizeToArray { if let Some(func_id) = Func::find_id_by_name_and_kind(ctx, func_spec.name(), FuncKind::Intrinsic) .await? { let func = Func::get_by_id(ctx, func_id).await?; thing_map.insert(func_spec.unique_id().into(), Thing::Func(func)); } else { let mut override_intrinsic_func_specs = unsafe_to_install_intrinsic_funcs_pkg.funcs_for_name(intrinsic.name())?; let override_intrinsic_func_spec = override_intrinsic_func_specs.pop().ok_or( PkgError::IntrinsicFuncSpecsNoneForName(intrinsic.name().to_owned()), )?; if !override_intrinsic_func_specs.is_empty() { return Err(PkgError::IntrinsicFuncSpecsMultipleForName( intrinsic.name().to_owned(), )); } // Use the override func spec to create the func. let func = create_func(ctx, &override_intrinsic_func_spec, false).await?; // Find or create the func arguments for the provided spec. for argument in func_spec.arguments()? { if FuncArgument::find_by_name_for_func(ctx, argument.name(), func.id) .await? .is_none() { create_func_argument(ctx, func.id, &argument).await?; } } // However, use the _provided_ func spec when inserting into the thing map. Why // not use the override? Because we are initializing the thing map here and the // users of the thing map don't need the "overrides" re-direction: they just // have the func they need here. thing_map.insert(func_spec.unique_id().into(), Thing::Func(func)); } } else { let func_id = Func::find_id_by_name(ctx, func_spec.name()) .await? .ok_or(PkgError::MissingIntrinsicFunc(func_spec.name().to_owned()))?; let func = Func::get_by_id(ctx, func_id).await?; thing_map.insert(func_spec.unique_id().into(), Thing::Func(func)); } } else { // Find or create the func for the provided spec. let func = if let Some(func) = Func::find_by_id_or_name(ctx, func_spec.unique_id(), func_spec.name()).await? { func } else { create_func(ctx, &func_spec, false).await? }; // Find or create the func arguments for the provided spec. for argument in func_spec.arguments()? { if FuncArgument::find_by_name_for_func(ctx, argument.name(), func.id) .await? .is_none() { create_func_argument(ctx, func.id, &argument).await?; } } thing_map.insert(func_spec.unique_id().into(), Thing::Func(func)); } } Ok(thing_map) } #[allow(clippy::too_many_arguments)] pub async fn import_schema_variant( ctx: &DalContext, schema: &Schema, schema_spec: SiPkgSchema<'_>, variant_spec: &SiPkgSchemaVariant<'_>, installed_module: Option<Module>, thing_map: &mut ThingMap, installed_schema_variant: Option<SchemaVariant>, ) -> PkgResult<SchemaVariant> { let mut existing_schema_variant: Option<SchemaVariant> = None; if let Some(installed_pkg) = installed_module.clone() { let associated_schema_variants = installed_pkg.list_associated_schema_variants(ctx).await?; let mut maybe_matching_schema_variant: Vec<SchemaVariant> = associated_schema_variants .into_iter() .filter(|s| s.version() == schema_spec.name()) .collect(); if let Some(matching_schema_variant) = maybe_matching_schema_variant.pop() { existing_schema_variant = Some(matching_schema_variant); } } if let Some(existing_sv) = installed_schema_variant { existing_schema_variant = Some(existing_sv); } let schema_variant = if let Some(variant) = existing_schema_variant { variant } else { let spec = schema_spec.to_spec().await?; let metadata = SchemaVariantJson::metadata_from_spec(spec)?; let mut asset_func_id: Option<FuncId> = None; if let Some(variant_spec_data) = variant_spec.data() { let func_unique_id = variant_spec_data.func_unique_id().to_owned(); if let Thing::Func(asset_func) = thing_map .get(&func_unique_id) .ok_or(PkgError::MissingFuncUniqueId( func_unique_id.to_string(), "error found while importing schema variant", ))? { asset_func_id = Some(asset_func.id) } } let old_versions = ["v0", "v1", "v2"]; let version_date = if old_versions.contains(&variant_spec.version()) { let date = DateTime::UNIX_EPOCH; format!("{}", date.format("%Y%m%d%H%M%S")) } else { variant_spec.version().to_owned() }; SchemaVariant::new( ctx, schema.id(), version_date, metadata.display_name, metadata.category, metadata.color, metadata.component_type, metadata.link, metadata.description, asset_func_id, variant_spec.is_builtin(), ) .await? .0 }; if let Some(module) = installed_module.clone() { module .create_association(ctx, schema_variant.id().into()) .await?; } if let Some(unique_id) = variant_spec.unique_id() { thing_map.insert( unique_id.to_owned(), Thing::SchemaVariant(schema_variant.to_owned()), ); } if let Some(data) = variant_spec.data() { if let Some(color) = data.color() { let current_color = schema_variant.get_color(ctx).await?; if current_color != color { schema_variant.set_color(ctx, color).await? } } schema_variant .set_type(ctx, data.component_type().to_string()) .await?; } let mut side_effects = CreatePropsSideEffects::default(); let domain_prop_id = Prop::find_prop_id_by_path(ctx, schema_variant.id(), &PropPath::new(["root", "domain"])) .await?; side_effects.extend( create_props( ctx, variant_spec, SchemaVariantSpecPropRoot::Domain, domain_prop_id, schema_variant.id(), ) .await?, ); let resource_value_prop_id = Prop::find_prop_id_by_path( ctx, schema_variant.id(), &PropPath::new(["root", "resource_value"]), ) .await?; side_effects.extend( create_props( ctx, variant_spec, SchemaVariantSpecPropRoot::ResourceValue, resource_value_prop_id, schema_variant.id(), ) .await?, ); let secrets_prop_id = Prop::find_prop_id_by_path( ctx, schema_variant.id(), &PropPath::new(["root", "secrets"]), ) .await?; side_effects.extend( create_props( ctx, variant_spec, SchemaVariantSpecPropRoot::Secrets, secrets_prop_id, schema_variant.id(), ) .await?, ); if !variant_spec.secret_definitions()?.is_empty() { let root_prop_id = Prop::find_prop_id_by_path(ctx, schema_variant.id(), &PropPath::new(["root"])).await?; let secret_definition_prop = Prop::new_without_ui_optionals( ctx, "secret_definition", PropKind::Object, root_prop_id, ) .await?; let secret_definition_prop_id = secret_definition_prop.id(); side_effects.extend( create_props( ctx, variant_spec, SchemaVariantSpecPropRoot::SecretDefinition, secret_definition_prop_id, schema_variant.id(), ) .await?, ); } SchemaVariant::finalize(ctx, schema_variant.id()).await?; for socket in variant_spec.sockets()? { import_socket(ctx, socket, schema_variant.id(), thing_map).await?; } for action_func in &variant_spec.action_funcs()? { let prototype = import_action_func(ctx, action_func, schema_variant.id(), thing_map).await?; if let (Some(prototype), Some(unique_id)) = (prototype, action_func.unique_id()) { thing_map.insert(unique_id.to_owned(), Thing::ActionPrototype(prototype)); } } for auth_func in &variant_spec.auth_funcs()? { let prototype = import_auth_func(ctx, auth_func, schema_variant.id(), thing_map).await?; if let (Some(prototype), Some(unique_id)) = (prototype, auth_func.unique_id()) { thing_map.insert(unique_id.to_owned(), Thing::AuthPrototype(prototype)); } } for leaf_func in variant_spec.leaf_functions()? { import_leaf_function(ctx, leaf_func, schema_variant.id(), thing_map).await?; } for management_func in variant_spec.management_funcs()? { import_management_func(ctx, &management_func, schema_variant.id(), thing_map).await?; } // Default values must be set before attribute functions are configured so they don't // override the prototypes set there for default_value_info in side_effects.default_values { set_default_value(ctx, default_value_info).await?; } // Set a default name value for all name props, this ensures region has a name before // the function is executed { let name_prop_id = Prop::find_prop_id_by_path( ctx, schema_variant.id(), &PropPath::new(["root", "si", "name"]), ) .await?; let name_default_value_info = DefaultValueInfo::String { prop_id: name_prop_id, default_value: schema.name.to_owned().to_lowercase(), }; set_default_value(ctx, name_default_value_info).await?; } for si_prop_func in variant_spec.si_prop_funcs()? { let prop_id = Prop::find_prop_id_by_path( ctx, schema_variant.id(), &PropPath::new(si_prop_func.kind().prop_path()), ) .await?; import_attr_func_for_prop( ctx, schema_variant.id(), AttrFuncInfo { func_unique_id: si_prop_func.func_unique_id().to_owned(), prop_id, inputs: si_prop_func .inputs()? .iter() .map(|input| input.to_owned().into()) .collect(), }, None, thing_map, ) .await?; } let mut has_resource_value_func = false; for root_prop_func in variant_spec.root_prop_funcs()? { if root_prop_func.prop() == SchemaVariantSpecPropRoot::ResourceValue { has_resource_value_func = true; } let prop_id = Prop::find_prop_id_by_path( ctx, schema_variant.id(), &PropPath::new(root_prop_func.prop().path_parts()), ) .await?; import_attr_func_for_prop( ctx, schema_variant.id(), AttrFuncInfo { func_unique_id: root_prop_func.func_unique_id().to_owned(), prop_id, inputs: root_prop_func .inputs()? .iter() .map(|input| input.to_owned().into()) .collect(), }, None, thing_map, ) .await?; } if !has_resource_value_func { attach_resource_payload_to_value(ctx, schema_variant.id()).await?; } for attr_func in side_effects.attr_funcs { import_attr_func_for_prop(ctx, schema_variant.id(), attr_func, None, thing_map).await?; } for (key, map_key_func) in side_effects.map_key_funcs { import_attr_func_for_prop(ctx, schema_variant.id(), map_key_func, Some(key), thing_map) .await?; } Ok(schema_variant) } async fn set_default_value( ctx: &DalContext, default_value_info: DefaultValueInfo, ) -> PkgResult<()> { let prop_id = match &default_value_info { DefaultValueInfo::Number { prop_id, .. } | DefaultValueInfo::String { prop_id, .. } | DefaultValueInfo::Boolean { prop_id, .. } => *prop_id, }; match default_value_info { DefaultValueInfo::Boolean { default_value, .. } => { Prop::set_default_value(ctx, prop_id, default_value).await? } DefaultValueInfo::Number { default_value, .. } => { Prop::set_default_value(ctx, prop_id, default_value).await? } DefaultValueInfo::String { default_value, .. } => { Prop::set_default_value(ctx, prop_id, default_value).await? } } Ok(()) } async fn import_attr_func_for_prop( ctx: &DalContext, schema_variant_id: SchemaVariantId, AttrFuncInfo { func_unique_id, prop_id, inputs, }: AttrFuncInfo, key: Option<String>, thing_map: &mut ThingMap, ) -> PkgResult<()> { match thing_map.get(&func_unique_id.to_owned()) { Some(Thing::Func(func)) => { import_attr_func( ctx, AttrFuncContext::Prop(prop_id), key, schema_variant_id, func.id, inputs, thing_map, ) .await?; } _ => { return Err(PkgError::MissingFuncUniqueId( func_unique_id.to_string(), "error found while importing attribute func for prop", )); } } Ok(()) } async fn import_attr_func_for_output_socket( ctx: &DalContext, schema_variant_id: SchemaVariantId, output_socket_id: OutputSocketId, func_unique_id: &str, inputs: Vec<SiPkgAttrFuncInputView>, thing_map: &mut ThingMap, ) -> PkgResult<()> { match thing_map.get(&func_unique_id.to_owned()) { Some(Thing::Func(func)) => { import_attr_func( ctx, AttrFuncContext::OutputSocket(output_socket_id), None, schema_variant_id, func.id, inputs, thing_map, ) .await?; } _ => { return Err(PkgError::MissingFuncUniqueId( func_unique_id.to_string(), "import attribute func for output socket", )); } } Ok(()) } async fn get_prototype_for_context( ctx: &DalContext, context: AttrFuncContext, key: Option<String>, ) -> PkgResult<AttributePrototypeId> { if key.is_some() { #[allow(clippy::infallible_destructuring_match)] let map_prop_id = match context { AttrFuncContext::Prop(prop_id) => prop_id, _ => Err(PkgError::AttributeFuncForKeyMissingProp( context, key.to_owned().expect("check above ensures this is some"), ))?, }; let map_prop = Prop::get_by_id(ctx, map_prop_id).await?; if map_prop.kind != PropKind::Map { return Err(PkgError::AttributeFuncForKeySetOnWrongKind( map_prop_id, key.to_owned().expect("check above ensures this is some"), map_prop.kind, )); } let element_prop_id = Prop::element_prop_id(ctx, map_prop.id).await?; Ok( match AttributePrototype::find_for_prop(ctx, element_prop_id, &key).await? { None => { let unset_func_id = Func::find_intrinsic(ctx, IntrinsicFunc::Unset).await?; let prototype_id = AttributePrototype::new(ctx, unset_func_id).await?.id(); Prop::add_edge_to_attribute_prototype( ctx, element_prop_id, prototype_id, EdgeWeightKind::Prototype(key), ) .await?; prototype_id } Some(prototype_id) => prototype_id, }, ) } else { Ok(match context { AttrFuncContext::Prop(prop_id) => { AttributePrototype::find_for_prop(ctx, prop_id, &None) .await? .ok_or(PkgError::PropMissingPrototype(prop_id))? } AttrFuncContext::OutputSocket(output_socket_id) => { AttributePrototype::find_for_output_socket(ctx, output_socket_id) .await? .ok_or(PkgError::OutputSocketMissingPrototype(output_socket_id))? } }) } } async fn create_attr_proto_arg( ctx: &DalContext, prototype_id: AttributePrototypeId, input: &SiPkgAttrFuncInputView, func_id: FuncId, schema_variant_id: SchemaVariantId, ) -> PkgResult<AttributePrototypeArgumentId> { let arg = match &input { SiPkgAttrFuncInputView::Prop { name, .. } | SiPkgAttrFuncInputView::InputSocket { name, .. } | SiPkgAttrFuncInputView::OutputSocket { name, .. } => { FuncArgument::find_by_name_for_func(ctx, name, func_id) .await? .ok_or(PkgError::MissingFuncArgument(name.to_owned(), func_id))? } }; Ok(match input { SiPkgAttrFuncInputView::Prop { prop_path, .. } => { let prop_id = Prop::find_prop_id_by_path(ctx, schema_variant_id, &prop_path.into()).await?; AttributePrototypeArgument::new(ctx, prototype_id, arg.id, prop_id).await? } SiPkgAttrFuncInputView::InputSocket { socket_name, .. } => { let input_socket = InputSocket::find_with_name(ctx, socket_name, schema_variant_id) .await? .ok_or(PkgError::MissingInputSocketName(socket_name.to_owned()))?; AttributePrototypeArgument::new(ctx, prototype_id, arg.id, input_socket.id()).await? } SiPkgAttrFuncInputView::OutputSocket { name, socket_name, .. } => { return Err(PkgError::TakingOutputSocketAsInputForPropUnsupported( name.to_owned(), socket_name.to_owned(), )); } } .id()) } #[derive(Debug, Clone)] pub enum AttrFuncContext { Prop(PropId), OutputSocket(OutputSocketId), } #[allow(clippy::too_many_arguments)] async fn import_attr_func( ctx: &DalContext, context: AttrFuncContext, key: Option<String>, schema_variant_id: SchemaVariantId, func_id: FuncId, inputs: Vec<SiPkgAttrFuncInputView>, _thing_map: &mut ThingMap, ) -> PkgResult<()> { let prototype_id = get_prototype_for_context(ctx, context, key).await?; let prototype_func_id = AttributePrototype::func_id(ctx, prototype_id).await?; if prototype_func_id != func_id { AttributePrototype::update_func_by_id(ctx, prototype_id, func_id).await?; } for input in &inputs { create_attr_proto_arg(ctx, prototype_id, input, func_id, schema_variant_id).await?; } Ok(()) } fn prop_kind_for_pkg_prop(pkg_prop: &SiPkgProp<'_>) -> PropKind { match pkg_prop { SiPkgProp::Array { .. } => PropKind::Array, SiPkgProp::Boolean { .. } => PropKind::Boolean, SiPkgProp::Json { .. } => PropKind::Json, SiPkgProp::Map { .. } => PropKind::Map, SiPkgProp::Number { .. } => PropKind::Integer, SiPkgProp::Float { .. } => PropKind::Float, SiPkgProp::Object { .. } => PropKind::Object, SiPkgProp::String { .. } => PropKind::String, } } async fn create_dal_prop( ctx: &DalContext, SiPkgPropData { name, default_value: _, // unused for some reason? func_unique_id: _, // unused for some reason? widget_kind, widget_options, doc_link, hidden, documentation, validation_format, ui_optionals, }: &SiPkgPropData, kind: PropKind, schema_variant_id: SchemaVariantId, parent_prop_info: Option<ParentPropInfo>, ) -> PkgResult<Prop> { let ui_optionals = ui_optionals .iter() .map(|(key, value)| (key.clone(), value.clone().into())) .collect(); let prop = match parent_prop_info { Some(parent_info) => Prop::new( ctx, name.as_str(), kind, *hidden, doc_link.as_ref().map(|l| l.to_string()), documentation.clone(), Some((widget_kind.into(), widget_options.to_owned())), validation_format.clone(), ui_optionals, parent_info.prop_id, ) .await .map_err(SiPkgError::visit_prop)?, None => Prop::new_root( ctx, name.as_str(), kind, *hidden, doc_link.as_ref().map(|l| l.to_string()), documentation.clone(), Some((widget_kind.into(), widget_options.to_owned())), validation_format.clone(), ui_optionals, schema_variant_id, ) .await .map_err(SiPkgError::visit_prop)?, }; Ok(prop) } #[derive(Debug, Clone)] struct ParentPropInfo { prop_id: PropId, } async fn create_prop( spec: SiPkgProp<'_>, parent_prop_info: Option<ParentPropInfo>, ctx: &PropVisitContext<'_>, ) -> PkgResult<Option<ParentPropInfo>> { let prop = { let data = spec.data().ok_or(PkgError::DataNotFound("prop".into()))?; create_dal_prop( ctx.ctx, data, prop_kind_for_pkg_prop(&spec), ctx.schema_variant_id, parent_prop_info, ) .await? }; let prop_id = prop.id(); // Both attribute functions and default values have to be set *after* the schema variant is // "finalized", so we can't do until we construct the *entire* prop tree. Hence we push work // queues up to the outer context via the PropVisitContext, which uses Mutexes for interior // mutability (maybe there's a better type for that here?) if let Some(data) = spec.data() { if let Some(default_value_info) = match &spec { SiPkgProp::String { .. } => { if let Some(serde_json::Value::String(default_value)) = &data.default_value { Some(DefaultValueInfo::String { prop_id, default_value: default_value.to_owned(), }) } else { // Raise error here for type mismatch None } } SiPkgProp::Number { .. } => { if let Some(serde_json::Value::Number(default_value_number)) = &data.default_value { if default_value_number.is_i64() { default_value_number .as_i64() .map(|dv_i64| DefaultValueInfo::Number { prop_id, default_value: dv_i64, }) } else { None } } else { None } } SiPkgProp::Boolean { .. } => { if let Some(serde_json::Value::Bool(default_value)) = &data.default_value { Some(DefaultValueInfo::Boolean { prop_id, default_value: *default_value, }) } else { None } } // Default values for complex types are not yet supported in packages _ => None, } { ctx.default_values.lock().await.push(default_value_info); } } if matches!(&spec, SiPkgProp::Map { .. }) { for map_key_func in spec.map_key_funcs()? { let key = map_key_func.key(); let mut inputs = map_key_func.inputs()?; let func_unique_id = map_key_func.func_unique_id(); ctx.map_key_funcs.lock().await.push(( key.to_owned(), AttrFuncInfo { func_unique_id: func_unique_id.to_owned(), prop_id, inputs: inputs.drain(..).map(Into::into).collect(), }, )); } } if let Some(func_unique_id) = spec.data().and_then(|data| data.func_unique_id.to_owned()) { let mut inputs = spec.inputs()?; ctx.attr_funcs.lock().await.push(AttrFuncInfo { func_unique_id, prop_id, inputs: inputs.drain(..).map(Into::into).collect(), }); } Ok(Some(ParentPropInfo { prop_id: prop.id() })) } pub async fn attach_resource_payload_to_value( ctx: &DalContext, schema_variant_id: SchemaVariantId, ) -> PkgResult<()> { let name = "si:resourcePayloadToValue"; let func_id = if let Some(func_id) = Func::find_id_by_name_and_kind(ctx, name, FuncKind::Intrinsic).await? { func_id } else { trace!( "installing the intrinsic version of 'si:resourcePayloadToValue' (neither found nor was specified in the package spec)" ); // If we did not find it by this point, the package did not specify it. let unsafe_to_install_intrinsic_funcs_pkg = SiPkg::load_from_spec(IntrinsicFunc::pkg_spec()?)?; let mut intrinsic_func_specs = unsafe_to_install_intrinsic_funcs_pkg.funcs_for_name(name)?; let intrinsic_func_spec = intrinsic_func_specs .pop() .ok_or(PkgError::IntrinsicFuncSpecsNoneForName(name.to_owned()))?; if !intrinsic_func_specs.is_empty() { return Err(PkgError::IntrinsicFuncSpecsMultipleForName(name.to_owned())); } // Create the func and arguments without interacting with the thing map and import flow. We // do that because the package did not specify it. let func = create_func(ctx, &intrinsic_func_spec, false).await?; for argument in intrinsic_func_spec.arguments()? { if FuncArgument::find_by_name_for_func(ctx, argument.name(), func.id) .await? .is_none() { create_func_argument(ctx, func.id, &argument).await?; } } func.id }; let func_argument_id = FuncArgument::find_by_name_for_func(ctx, "payload", func_id) .await? .ok_or(PkgError::FuncArgumentNotFoundByName( func_id, "payload".into(), ))? .id; let source_prop_id = Prop::find_prop_id_by_path( ctx, schema_variant_id, &PropPath::new(["root", "resource", "payload"]), ) .await?; let target_id = { let resource_value_prop_id = Prop::find_prop_id_by_path( ctx, schema_variant_id, &PropPath::new(["root", "resource_value"]), ) .await?; let prototype_id = get_prototype_for_context(ctx, AttrFuncContext::Prop(resource_value_prop_id), None) .await?; AttributePrototype::update_func_by_id(ctx, prototype_id, func_id).await?; prototype_id }; let mut rv_input_apa_id = None; for apa_id in AttributePrototypeArgument::list_ids_for_prototype(ctx, target_id).await? { if func_argument_id == AttributePrototypeArgument::func_argument_id(ctx, apa_id).await? { rv_input_apa_id = Some(apa_id); break; } } match rv_input_apa_id { Some(apa_id) => { if !{ if let Some(ValueSource::Prop(prop_id)) = AttributePrototypeArgument::value_source_opt(ctx, apa_id).await? { prop_id == source_prop_id } else { false } } { AttributePrototypeArgument::set_value_source(ctx, apa_id, source_prop_id).await?; } } None => { AttributePrototypeArgument::new(ctx, target_id, func_argument_id, source_prop_id) .await?; } } Ok(()) }

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