Skip to main content
Glama
authoring.rs36 kB
use std::{ collections::HashMap, fmt::Debug, }; use base64::{ Engine, engine::general_purpose, }; use chrono::Utc; use convert_case::{ Case, Casing, }; use pkg::import::import_schema_variant; use serde::{ Deserialize, Serialize, }; use serde_json::error::Category; use si_events::{ FuncRunId, FuncRunState, ulid::Ulid, }; use si_layer_cache::LayerDbError; use si_pkg::{ FuncSpec, FuncSpecBackendKind, FuncSpecBackendResponseType, FuncSpecData, MergeSkip, PkgSpec, SchemaVariantSpec, SiPkg, SiPkgError, SpecError, }; use telemetry::prelude::*; use thiserror::Error; use crate::{ Component, ComponentError, ComponentType, DalContext, Func, FuncBackendKind, FuncBackendResponseType, FuncError, FuncId, Schema, SchemaError, SchemaId, SchemaVariant, SchemaVariantError, SchemaVariantId, WorkspaceSnapshotError, action::prototype::ActionPrototypeError, attribute::prototype::{ AttributePrototypeError, argument::AttributePrototypeArgumentError, }, func::{ authoring::FuncAuthoringError, intrinsics::IntrinsicFunc, runner::{ FuncRunner, FuncRunnerError, }, }, pkg::{ self, ImportOptions, PkgError, export::PkgExporter, import::import_only_new_funcs, import_pkg_from_pkg, }, prop::PropError, schema::variant::{ SchemaVariantJson, SchemaVariantMetadataJson, }, socket::{ input::InputSocketError, output::OutputSocketError, }, }; #[allow(missing_docs)] #[remain::sorted] #[derive(Error, Debug)] pub enum VariantAuthoringError { #[error("action prototype error: {0}")] ActionPrototype(#[from] Box<ActionPrototypeError>), #[error( "found unexpected return type: expected type 'Asset' to be returned for asset func (FuncId {0}): raw error: {1})" )] AssetTypeNotReturnedForAssetFunc(FuncId, String), #[error("attribute prototype error: {0}")] AttributePrototype(#[from] Box<AttributePrototypeError>), #[error("attribute prototype error: {0}")] AttributePrototypeArgument(#[from] Box<AttributePrototypeArgumentError>), #[error("component error: {0}")] Component(#[from] Box<ComponentError>), #[error("there already exists a Schema with the name {0}")] DuplicatedSchemaName(String), #[error("empty value within func run value (FuncId {0} and FuncRunId {1})")] EmptyValueWithinFuncRunValue(FuncId, FuncRunId), #[error("func error: {0}")] Func(#[from] Box<FuncError>), #[error("func authoring error: {0}")] FuncAuthoring(#[from] Box<FuncAuthoringError>), #[error("func execution failure error: {0}")] FuncExecutionFailure(String), #[error("func run error: {0}")] FuncRun(#[from] Box<FuncRunnerError>), #[error("func run value sender has terminated without sending")] FuncRunGone, #[error("input socket error: {0}")] InputSocket(#[from] Box<InputSocketError>), #[error("layer db error: {0}")] LayerDb(#[from] LayerDbError), #[error("trying to modify locked variant: {0}")] LockedVariant(SchemaVariantId), #[error("no new asset was created")] NoAssetCreated, #[error("output socket error: {0}")] OutputSocket(#[from] Box<OutputSocketError>), #[error("pkg error: {0}")] Pkg(#[from] Box<PkgError>), #[error("constructed package has no schema node")] PkgMissingSchema, #[error("constructed package has no schema variant node")] PkgMissingSchemaVariant, #[error("prop error: {0}")] Prop(#[from] Box<PropError>), #[error("schema error: {0}")] Schema(#[from] Box<SchemaError>), #[error("schema {0} already has unlocked variant: {1}")] SchemaAlreadyUnlocked(SchemaId, SchemaVariantId), #[error("schema variant error: {0}")] SchemaVariant(#[from] Box<SchemaVariantError>), #[error("schema variant asset func not found: {0}")] SchemaVariantAssetNotFound(SchemaVariantId), #[error("schema variant not found: {0}")] SchemaVariantNotFound(SchemaVariantId), #[error("schema variant not updated: {0}")] SchemaVariantUpdatedFailed(SchemaVariantId), #[error("json serialization error: {0}")] SerdeJson(#[from] serde_json::Error), #[error("si db error: {0}")] SiDb(#[from] si_db::Error), #[error("si pkg error: {0}")] SiPkg(#[from] SiPkgError), #[error("spec error: {0}")] Spec(#[from] SpecError), #[error("workspace snapshot error: {0}")] WorkspaceSnapshot(#[from] WorkspaceSnapshotError), } impl From<ActionPrototypeError> for VariantAuthoringError { fn from(value: ActionPrototypeError) -> Self { Box::new(value).into() } } impl From<AttributePrototypeError> for VariantAuthoringError { fn from(value: AttributePrototypeError) -> Self { Box::new(value).into() } } impl From<AttributePrototypeArgumentError> for VariantAuthoringError { fn from(value: AttributePrototypeArgumentError) -> Self { Box::new(value).into() } } impl From<ComponentError> for VariantAuthoringError { fn from(value: ComponentError) -> Self { Box::new(value).into() } } impl From<FuncError> for VariantAuthoringError { fn from(value: FuncError) -> Self { Box::new(value).into() } } impl From<FuncAuthoringError> for VariantAuthoringError { fn from(value: FuncAuthoringError) -> Self { Box::new(value).into() } } impl From<FuncRunnerError> for VariantAuthoringError { fn from(value: FuncRunnerError) -> Self { Box::new(value).into() } } impl From<InputSocketError> for VariantAuthoringError { fn from(value: InputSocketError) -> Self { Box::new(value).into() } } impl From<OutputSocketError> for VariantAuthoringError { fn from(value: OutputSocketError) -> Self { Box::new(value).into() } } impl From<PkgError> for VariantAuthoringError { fn from(value: PkgError) -> Self { Box::new(value).into() } } impl From<PropError> for VariantAuthoringError { fn from(value: PropError) -> Self { Box::new(value).into() } } impl From<SchemaError> for VariantAuthoringError { fn from(value: SchemaError) -> Self { Box::new(value).into() } } impl From<SchemaVariantError> for VariantAuthoringError { fn from(value: SchemaVariantError) -> Self { Box::new(value).into() } } type VariantAuthoringResult<T> = Result<T, VariantAuthoringError>; const DEFAULT_ASSET_CODE: &str = r#"function main() { const asset = new AssetBuilder(); return asset.build(); }"#; #[derive(Debug, Deserialize, Serialize)] struct SchemaVariantJsonWrapper { definition: Option<SchemaVariantJson>, error: Option<String>, } #[derive(Debug)] pub struct VariantAuthoringClient; impl VariantAuthoringClient { /// Creates a [`SchemaVariant`] and returns the [result](SchemaVariant). #[instrument(name = "variant.authoring.create_variant", level = "info", skip_all)] #[allow(clippy::too_many_arguments)] pub async fn create_schema_and_variant_from_code( ctx: &DalContext, name: impl Into<String>, description: Option<String>, link: Option<String>, category: impl Into<String>, color: impl Into<String>, code: impl AsRef<str>, ) -> VariantAuthoringResult<SchemaVariant> { let name = name.into(); if Schema::is_name_taken(ctx, &name).await? { return Err(VariantAuthoringError::DuplicatedSchemaName(name)); }; let variant_version = SchemaVariant::generate_version_string(); let code_base64 = general_purpose::STANDARD_NO_PAD.encode(code.as_ref()); let asset_func = Func::new( ctx, generate_scaffold_func_name(&name), Some(name.clone()), description.clone(), link.clone(), false, false, FuncBackendKind::JsSchemaVariantDefinition, FuncBackendResponseType::SchemaVariantDefinition, Some("main"), Some(code_base64), false, ) .await?; let asset_func_spec = build_asset_func_spec(&asset_func)?; let definition = Self::execute_asset_func(ctx, &asset_func).await?; let metadata = SchemaVariantMetadataJson { schema_name: name.clone(), version: variant_version.clone(), display_name: name.clone(), category: category.into(), color: color.into(), component_type: ComponentType::Component, link: link.clone(), description: description.clone(), }; let email = ctx.history_actor().email(ctx).await?; let pkg_spec = build_pkg_spec_for_variant(&name, definition, &asset_func_spec, &metadata, &email)?; let pkg = SiPkg::load_from_spec(pkg_spec.clone())?; let (_, schema_variant_ids, _) = import_pkg_from_pkg( ctx, &pkg, Some(ImportOptions { skip_import_funcs: Some(HashMap::from_iter([( asset_func_spec.unique_id.to_owned(), asset_func.clone(), )])), create_unlocked: true, schema_id: Some(Ulid::new()), ..Default::default() }), ) .await?; let schema_variant_id = schema_variant_ids .first() .copied() .ok_or(VariantAuthoringError::NoAssetCreated)?; ctx.workspace_snapshot()? .clear_prop_suggestions_cache() .await; Ok(SchemaVariant::get_by_id(ctx, schema_variant_id).await?) } pub async fn create_schema_and_variant( ctx: &DalContext, name: impl Into<String>, description: Option<String>, link: Option<String>, category: impl Into<String>, color: impl Into<String>, ) -> VariantAuthoringResult<SchemaVariant> { Self::create_schema_and_variant_from_code( ctx, name, description, link, category, color, DEFAULT_ASSET_CODE, ) .await } #[instrument( name = "variant.authoring.new_schema_with_cloned_variant", level = "info", skip_all )] pub async fn new_schema_with_cloned_variant( ctx: &DalContext, schema_variant_id: SchemaVariantId, schema_name: String, ) -> VariantAuthoringResult<(SchemaVariant, Schema)> { if Schema::is_name_taken(ctx, &schema_name).await? { return Err(VariantAuthoringError::DuplicatedSchemaName(schema_name)); }; let variant = SchemaVariant::get_by_id(ctx, schema_variant_id).await?; let schema = variant.schema(ctx).await?; if let Some(asset_func_id) = variant.asset_func_id() { let old_func = Func::get_by_id(ctx, asset_func_id).await?; let cloned_func = old_func .clone_func_with_new_name(ctx, schema_name.clone()) .await?; let cloned_func_spec = build_asset_func_spec(&cloned_func)?; let definition = Self::execute_asset_func(ctx, &cloned_func).await?; let display_name = format!("{}-Clone", variant.display_name()); let metadata = SchemaVariantMetadataJson { schema_name: schema_name.clone(), version: SchemaVariant::generate_version_string(), display_name, category: variant.category().to_string(), color: variant.get_color(ctx).await?, component_type: variant.component_type(), link: variant.link().clone(), description: variant.description().clone(), }; let email = ctx.history_actor().email(ctx).await?; let pkg_spec = build_pkg_spec_for_variant( &schema.name, definition, &cloned_func_spec, &metadata, &email, )?; let pkg = SiPkg::load_from_spec(pkg_spec.clone())?; let (_, schema_variant_ids, _) = import_pkg_from_pkg( ctx, &pkg, Some(ImportOptions { skip_import_funcs: Some(HashMap::from_iter([( cloned_func_spec.unique_id.to_owned(), cloned_func.clone(), )])), create_unlocked: true, schema_id: Some(Ulid::new()), ..Default::default() }), ) .await?; let new_schema_variant_id = schema_variant_ids .first() .copied() .ok_or(VariantAuthoringError::NoAssetCreated)?; ctx.workspace_snapshot()? .clear_prop_suggestions_cache() .await; Ok(( SchemaVariant::get_by_id(ctx, new_schema_variant_id).await?, schema, )) } else { return Err(VariantAuthoringError::SchemaVariantAssetNotFound( schema_variant_id, )); } } #[instrument( name = "variant.authoring.regenerate_variant", level = "info", skip(ctx) )] pub async fn regenerate_variant( ctx: &DalContext, schema_variant_id: SchemaVariantId, ) -> VariantAuthoringResult<SchemaVariantId> { let schema_variant = SchemaVariant::get_by_id(ctx, schema_variant_id).await?; if schema_variant.is_locked { return Err(VariantAuthoringError::LockedVariant(schema_variant_id)); }; let schema = schema_variant.schema(ctx).await?; let components_in_use = SchemaVariant::list_component_ids(ctx, schema_variant_id).await?; if components_in_use.is_empty() { Self::update_existing_variant_and_regenerate( ctx, schema_variant_id, schema.name, schema_variant.display_name, schema_variant.category, schema_variant.color, schema_variant.link, schema_variant.description, schema_variant.component_type, ) .await?; Ok(schema_variant_id) } else { let original_is_default = schema_variant.is_default(ctx).await?; let new_variant = Self::generate_variant_with_updates( ctx, schema_variant_id, &schema.name, schema_variant.display_name, schema_variant.category, schema_variant.color, schema_variant.link, schema_variant.description, schema_variant.component_type, ) .await?; for component_id in components_in_use { Component::upgrade_to_new_variant(ctx, component_id, new_variant.id).await?; } if original_is_default { schema.set_default_variant_id(ctx, new_variant.id).await?; } // When we get here, "schema_variant_id" points to an unlocked variant with no components // and no way to create new components from it. No other changesets point to it, // since it's been created on the changeset (otherwise it would not be unlocked) // So we should clean it up SchemaVariant::cleanup_unlocked_variant(ctx, schema_variant_id).await?; ctx.workspace_snapshot()? .clear_prop_suggestions_cache() .await; Ok(new_variant.id) } } #[allow(clippy::too_many_arguments)] #[instrument( name = "variant.authoring.update_existing_variant_and_regenerate", level = "info", skip_all )] async fn update_existing_variant_and_regenerate( ctx: &DalContext, current_schema_variant_id: SchemaVariantId, schema_name: impl Into<String>, display_name: impl Into<String>, category: impl Into<String>, color: impl Into<String>, link: Option<String>, description: Option<String>, component_type: ComponentType, ) -> VariantAuthoringResult<()> { // Ok we need to delete the first level of outgoing children for the schema variant let current_schema_variant = SchemaVariant::get_by_id(ctx, current_schema_variant_id).await?; // then we can build the package and reimport ALL but the schema variant itself let asset_func = current_schema_variant.get_asset_func(ctx).await?; let display_name = display_name.into(); let category = category.into(); let color = color.into(); let schema_name = schema_name.into(); let asset_func_spec = build_asset_func_spec(&asset_func)?; let definition = Self::execute_asset_func(ctx, &asset_func).await?; let metadata = SchemaVariantMetadataJson { schema_name: schema_name.clone(), version: SchemaVariant::generate_version_string(), display_name: display_name.clone(), category: category.clone(), color: color.clone(), component_type, link: link.clone(), description: description.clone(), }; let (new_variant_spec, _skips, variant_funcs) = build_variant_spec_based_on_existing_variant( ctx, definition, &asset_func_spec, &metadata, current_schema_variant_id, ) .await?; let schema_spec = metadata.to_schema_spec(new_variant_spec)?; let email = ctx.history_actor().email(ctx).await?; let pkg_spec = PkgSpec::builder() .name(schema_name.clone()) .created_by(&email) .funcs(variant_funcs.clone()) .func(asset_func_spec) .schema(schema_spec) .version("0") .build()?; let pkg = SiPkg::load_from_spec(pkg_spec)?; let pkg_schemas = pkg.schemas()?; let pkg_variants = pkg_schemas .first() .ok_or(VariantAuthoringError::PkgMissingSchema)? .variants()?; let schema_spec = pkg_schemas .first() .ok_or(VariantAuthoringError::PkgMissingSchema)?; let variant_pkg_spec = pkg_variants .first() .ok_or(VariantAuthoringError::PkgMissingSchemaVariant)?; let schema = SchemaVariant::get_by_id(ctx, current_schema_variant_id) .await? .schema(ctx) .await?; schema .clone() .modify(ctx, |s| { s.name.clone_from(&schema_name.clone()); Ok(()) }) .await?; // We need to clean up the old graph before we re-import the new parts! current_schema_variant .remove_external_connections(ctx) .await?; current_schema_variant .rebuild_variant_root_prop(ctx) .await?; // Now we can reimport all parts of the schema variant in place! let mut thing_map = import_only_new_funcs(ctx, pkg.funcs()?).await?; let new_schema_variant = import_schema_variant( ctx, &schema, schema_spec.clone(), variant_pkg_spec, None, &mut thing_map, Some(current_schema_variant), ) .await?; if new_schema_variant.id != current_schema_variant_id { return Err(VariantAuthoringError::SchemaVariantUpdatedFailed( current_schema_variant_id, )); } // Let's update the SV struct now to reflect any changes new_schema_variant .clone() .modify(ctx, |sv| { sv.description = description; sv.link = link; sv.category.clone_from(&category); sv.component_type = component_type; sv.color.clone_from(&color); sv.display_name = display_name; Ok(()) }) .await?; Ok(()) } #[allow(clippy::too_many_arguments)] #[instrument( name = "variant.authoring.update_and_generate_variant_with_new_version", level = "info", skip_all )] async fn generate_variant_with_updates( ctx: &DalContext, current_sv_id: SchemaVariantId, schema_name: impl Into<String>, display_name: String, category: impl Into<String>, color: impl Into<String>, link: Option<String>, description: Option<String>, component_type: ComponentType, ) -> VariantAuthoringResult<SchemaVariant> { let schema_name = schema_name.into(); let old_sv = SchemaVariant::get_by_id(ctx, current_sv_id).await?; let old_asset_func = old_sv.get_asset_func(ctx).await?; let new_asset_func = old_asset_func .clone_func_with_new_name(ctx, generate_scaffold_func_name(&schema_name)) .await?; let asset_func_spec = build_asset_func_spec(&new_asset_func.clone())?; let definition = Self::execute_asset_func(ctx, &new_asset_func).await?; let metadata = SchemaVariantMetadataJson { schema_name: schema_name.clone(), version: SchemaVariant::generate_version_string(), display_name: display_name.clone(), category: category.into(), color: color.into(), component_type, link: link.clone(), description: description.clone(), }; let (new_variant_spec, _skips, variant_funcs) = build_variant_spec_based_on_existing_variant( ctx, definition, &asset_func_spec, &metadata, current_sv_id, ) .await?; let schema_spec = metadata.to_schema_spec(new_variant_spec)?; let email = ctx.history_actor().email(ctx).await?; let pkg_spec = PkgSpec::builder() .name(schema_name.clone()) .created_by(&email) .funcs(variant_funcs.clone()) .func(asset_func_spec) .schema(schema_spec) .version("0") .build()?; let pkg = SiPkg::load_from_spec(pkg_spec)?; let pkg_schemas = pkg.schemas()?; let pkg_variants = pkg_schemas .first() .ok_or(VariantAuthoringError::PkgMissingSchema)? .variants()?; let schema_spec = pkg_schemas .first() .ok_or(VariantAuthoringError::PkgMissingSchema)?; let variant_pkg_spec = pkg_variants .first() .ok_or(VariantAuthoringError::PkgMissingSchemaVariant)?; let schema = SchemaVariant::get_by_id(ctx, current_sv_id) .await? .schema(ctx) .await?; schema .clone() .modify(ctx, |s| { s.name = schema_name; Ok(()) }) .await?; let mut thing_map = import_only_new_funcs(ctx, pkg.funcs()?).await?; Ok(import_schema_variant( ctx, &schema, schema_spec.clone(), variant_pkg_spec, None, &mut thing_map, None, ) .await?) } // Note(victor): This is very similar to the logic in update_and_generate_variant_with_new_version, with a few differences: // 1. it makes an exact copy of the schema variant - in the future there'll be no updates on unlocked copies // 2. it does not update the default schema variant #[instrument( name = "variant.authoring.create_unlocked_variant_copy", level = "info", skip_all )] pub async fn create_unlocked_variant_copy( ctx: &DalContext, locked_variant_id: SchemaVariantId, ) -> VariantAuthoringResult<SchemaVariant> { let locked_variant = SchemaVariant::get_by_id(ctx, locked_variant_id).await?; let schema = locked_variant.schema(ctx).await?; if let Some(variant) = SchemaVariant::get_unlocked_for_schema(ctx, schema.id).await? { return Err(VariantAuthoringError::SchemaAlreadyUnlocked( schema.id, variant.id, )); } // Create copy of asset func let locked_variant_asset_func_id = locked_variant.get_asset_func(ctx).await?; let unlocked_variant_asset_func = locked_variant_asset_func_id .clone_func_with_new_name(ctx, generate_scaffold_func_name(&schema.name)) .await?; let (existing_variant_spec, variant_funcs) = PkgExporter::export_variant_standalone( ctx, &locked_variant, schema.name(), Some(unlocked_variant_asset_func.id), ) .await?; let unlocked_asset_spec = build_asset_func_spec(&unlocked_variant_asset_func)?; let metadata = SchemaVariantMetadataJson { schema_name: schema.name.clone(), version: SchemaVariant::generate_version_string(), display_name: locked_variant.display_name().to_string(), category: locked_variant.category().to_string(), color: locked_variant.color().to_string(), component_type: locked_variant.component_type(), link: locked_variant.link(), description: locked_variant.description(), }; let schema_spec = metadata.to_schema_spec(existing_variant_spec)?; let creator_email = ctx.history_actor().email(ctx).await?; let pkg_spec = PkgSpec::builder() .name(schema.name()) .created_by(creator_email) .funcs(variant_funcs.clone()) .func(unlocked_asset_spec) .schema(schema_spec) .version("0") .build()?; let pkg: SiPkg = SiPkg::load_from_spec(pkg_spec)?; let pkg_schemas = pkg.schemas()?; let schema_spec = pkg_schemas .first() .ok_or(VariantAuthoringError::PkgMissingSchema)?; let pkg_variants = schema_spec.variants()?; let variant_pkg_spec = pkg_variants .first() .ok_or(VariantAuthoringError::PkgMissingSchemaVariant)?; let mut thing_map = import_only_new_funcs(ctx, pkg.funcs()?).await?; let new_schema_variant = import_schema_variant( ctx, &schema, schema_spec.clone(), variant_pkg_spec, None, &mut thing_map, None, ) .await?; // need to manually modify the variant to get the new version // as the pkg spec used to generate this new version only has the // old one. Ok(new_schema_variant .modify(ctx, |sv| { sv.version = metadata.version; Ok(()) }) .await?) } #[allow(clippy::too_many_arguments)] #[instrument( name = "variant.authoring.save_variant_content", level = "info", skip_all )] pub async fn save_variant_content( ctx: &DalContext, schema_variant_id: SchemaVariantId, schema_name: impl Into<String>, display_name: impl Into<String>, category: impl Into<String>, description: Option<String>, link: Option<String>, color: impl Into<String>, component_type: ComponentType, code: Option<impl Into<String>>, ) -> VariantAuthoringResult<()> { let schema_variant = SchemaVariant::get_by_id(ctx, schema_variant_id).await?; if schema_variant.is_locked { return Err(VariantAuthoringError::LockedVariant(schema_variant_id)); }; let schema = schema_variant.schema(ctx).await?; let asset_func_id = schema_variant.asset_func_id.ok_or( VariantAuthoringError::SchemaVariantAssetNotFound(schema_variant_id), )?; let schema_name = &schema_name.into(); schema .modify(ctx, |s| { s.name.clone_from(schema_name); Ok(()) }) .await?; let variant_description = description.clone(); let variant_link = link.clone(); let display_name = &display_name.into(); let color = &color.into(); // cache default values to compare and update let original_color = schema_variant.color.clone(); let original_type = schema_variant.component_type; let schema_variant = schema_variant .modify(ctx, |sv| { sv.description = variant_description; sv.link = variant_link; sv.category.clone_from(&category.into()); sv.component_type = component_type; sv.color.clone_from(color); sv.display_name.clone_from(display_name); Ok(()) }) .await?; // now need to update the default values for the schema variant so newly created components get the latest values if original_color != *color { schema_variant.set_color(ctx, color).await?; } if original_type != component_type { schema_variant .set_type(ctx, component_type.to_string()) .await?; } let code_base64 = code.map(|c| general_purpose::STANDARD_NO_PAD.encode(c.into())); let current_func = Func::get_by_id(ctx, asset_func_id).await?; current_func .modify(ctx, |func| { func.name = generate_scaffold_func_name(schema_name); func.backend_kind = FuncBackendKind::JsSchemaVariantDefinition; func.backend_response_type = FuncBackendResponseType::SchemaVariantDefinition; func.display_name = Some(display_name.clone()); if let Some(code_base64) = code_base64 { func.code_base64 = Some(code_base64); } func.description.clone_from(&description); func.handler = Some("main".to_string()); func.hidden = false; func.link.clone_from(&link); Ok(()) }) .await?; Ok(()) } #[instrument( name = "variant.authoring.execute_asset_func", level = "info", skip_all )] pub async fn execute_asset_func( ctx: &DalContext, asset_func: &Func, ) -> VariantAuthoringResult<SchemaVariantJson> { let result_channel = FuncRunner::run_asset_definition_func(ctx, asset_func).await?; let func_run_value = result_channel .await .map_err(|_| VariantAuthoringError::FuncRunGone)??; let value = func_run_value .value() .ok_or(VariantAuthoringError::EmptyValueWithinFuncRunValue( asset_func.id, func_run_value.func_run_id(), ))? .to_owned(); let wrapper: SchemaVariantJsonWrapper = match serde_json::from_value(value) { Ok(wrapper) => wrapper, Err(err) if err.classify() == Category::Data && err.to_string().contains("unknown field") => { return Err(VariantAuthoringError::AssetTypeNotReturnedForAssetFunc( asset_func.id, err.to_string(), )); } Err(err) => return Err(err.into()), }; if let Some(error) = &wrapper.error { return Err(VariantAuthoringError::FuncExecutionFailure( error.to_owned(), )); } let Some(definition) = wrapper.definition else { return Err(VariantAuthoringError::FuncExecutionFailure( "definition cannot be undefined or null".to_string(), )); }; FuncRunner::update_run(ctx, func_run_value.func_run_id(), |func_run| { func_run.set_state(FuncRunState::Success) }) .await?; Ok(definition) } } async fn build_variant_spec_based_on_existing_variant( ctx: &DalContext, definition: SchemaVariantJson, asset_func_spec: &FuncSpec, metadata: &SchemaVariantMetadataJson, existing_variant_id: SchemaVariantId, ) -> VariantAuthoringResult<(SchemaVariantSpec, Vec<MergeSkip>, Vec<FuncSpec>)> { let identity_func_spec = IntrinsicFunc::Identity.to_spec()?; let existing_variant = SchemaVariant::get_by_id(ctx, existing_variant_id).await?; let schema = existing_variant.schema(ctx).await?; let variant_spec = definition.to_spec( metadata.clone(), &identity_func_spec.unique_id, &asset_func_spec.unique_id, )?; let (existing_variant_spec, variant_funcs) = PkgExporter::export_variant_standalone(ctx, &existing_variant, schema.name(), None).await?; let (merged_variant, skips) = variant_spec.merge_prototypes_from(&existing_variant_spec); Ok((merged_variant, skips, variant_funcs)) } #[allow(clippy::result_large_err)] fn build_asset_func_spec(asset_func: &Func) -> VariantAuthoringResult<FuncSpec> { let mut schema_variant_func_spec = FuncSpec::builder(); schema_variant_func_spec.name(asset_func.name.clone()); schema_variant_func_spec.unique_id(asset_func.id.to_string()); let mut func_spec_data_builder = FuncSpecData::builder(); func_spec_data_builder .name(asset_func.name.clone()) .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) .hidden(asset_func.hidden); if let Some(code) = asset_func.code_plaintext()? { func_spec_data_builder.code_plaintext(code); } if let Some(handler) = asset_func.handler.clone() { func_spec_data_builder.handler(handler.to_string()); } if let Some(description) = asset_func.description.clone() { func_spec_data_builder.description(description.to_string()); } if let Some(display_name) = asset_func.display_name.clone() { func_spec_data_builder.display_name(display_name.to_string()); } Ok(schema_variant_func_spec .data(func_spec_data_builder.build()?) .build()?) } #[allow(clippy::result_large_err)] fn build_pkg_spec_for_variant( schema_name: &str, definition: SchemaVariantJson, asset_func_spec: &FuncSpec, metadata: &SchemaVariantMetadataJson, user_email: &str, ) -> VariantAuthoringResult<PkgSpec> { // we need to change this to use the PkgImport let identity_func_spec = IntrinsicFunc::Identity.to_spec()?; let variant_spec = definition.to_spec( metadata.clone(), &identity_func_spec.unique_id, &asset_func_spec.unique_id, )?; let schema_spec = metadata.to_schema_spec(variant_spec)?; Ok(PkgSpec::builder() .name(schema_name) .created_by(user_email) .func(identity_func_spec) .func(asset_func_spec.clone()) .schema(schema_spec) .version("0.0.1") .build()?) } fn generate_scaffold_func_name(name: impl AsRef<str>) -> String { let version = Utc::now().format("%Y%m%d%H%M%S%f").to_string(); let generated_name = format!("{}Scaffold_{}", name.as_ref().to_case(Case::Camel), version); generated_name }

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