diff --git a/lib/dal/examples/dal-pkg-export/main.rs b/lib/dal/examples/dal-pkg-export/main.rs index e4d2abbdaf..119f54253e 100644 --- a/lib/dal/examples/dal-pkg-export/main.rs +++ b/lib/dal/examples/dal-pkg-export/main.rs @@ -3,8 +3,8 @@ use std::{env, path::Path, sync::Arc}; use tokio::fs; use dal::{ - pkg::export_pkg_as_bytes, DalContext, JobQueueProcessor, NatsProcessor, Schema, - ServicesContext, Tenancy, Workspace, + pkg::PkgExporter, DalContext, JobQueueProcessor, NatsProcessor, Schema, ServicesContext, + StandardModel, Tenancy, Workspace, }; use si_data_nats::{NatsClient, NatsConfig}; use si_data_pg::{PgPool, PgPoolConfig}; @@ -31,25 +31,16 @@ async fn main() -> Result<()> { let workspace = Workspace::builtin(&ctx).await?; ctx.update_tenancy(Tenancy::new(*workspace.pk())); - let mut variant_ids = Vec::new(); + let mut schema_ids = Vec::new(); for schema_name in schema_names { - variant_ids.push(Schema::default_schema_variant_id_for_name(&ctx, schema_name).await?); + schema_ids.push(*Schema::find_by_name(&ctx, schema_name.trim()).await?.id()); } println!("--- Exporting pkg: {tar_file}"); - fs::write( - &tar_file, - export_pkg_as_bytes( - &ctx, - name, - version, - Some(description), - created_by, - variant_ids, - ) - .await?, - ) - .await?; + let mut exporter = + PkgExporter::new_module_exporter(name, version, Some(description), created_by, schema_ids); + + fs::write(&tar_file, exporter.export_as_bytes(&ctx).await?).await?; println!("--- Committing database transaction"); ctx.commit().await?; diff --git a/lib/dal/src/attribute/prototype.rs b/lib/dal/src/attribute/prototype.rs index f556e0aa33..9e7c502dae 100644 --- a/lib/dal/src/attribute/prototype.rs +++ b/lib/dal/src/attribute/prototype.rs @@ -504,7 +504,7 @@ impl AttributePrototype { ctx: &DalContext, context: AttributeContext, backend_response_type: FuncBackendResponseType, - ) -> AttributePrototypeResult> { + ) -> AttributePrototypeResult> { let rows = ctx .txns() .await? @@ -521,7 +521,18 @@ impl AttributePrototype { ) .await?; - Ok(standard_model::objects_from_rows(rows)?) + let mut result = Vec::new(); + for row in rows.into_iter() { + let func_json: serde_json::Value = row.try_get("func_object")?; + let func: Func = serde_json::from_value(func_json)?; + + let ap_json: serde_json::Value = row.try_get("prototype_object")?; + let ap: Self = serde_json::from_value(ap_json)?; + + result.push((ap, func)); + } + + Ok(result) } pub async fn list_for_schema_variant( diff --git a/lib/dal/src/builtins/schema.rs b/lib/dal/src/builtins/schema.rs index e86f1a1bfa..d1d5e91a6f 100644 --- a/lib/dal/src/builtins/schema.rs +++ b/lib/dal/src/builtins/schema.rs @@ -102,11 +102,15 @@ pub async fn migrate_for_tests( migrate_pkg(ctx, super::SI_AWS_EC2_PKG, None).await?; migrate_pkg(ctx, super::SI_COREOS_PKG, None).await?; migrate_pkg(ctx, super::SI_DOCKER_IMAGE_PKG, None).await?; + migrate_pkg(ctx, super::SI_GENERIC_FRAME_PKG, None).await?; for test_schema in [BuiltinSchema::Starfield, BuiltinSchema::Fallout] { migrate_schema(ctx, test_schema, &driver).await?; ctx.blocking_commit().await?; } } else if migrate_test_exclusive { + // We migrate generic frame to get "si:resourceToPayloadValue" cheaply. This function + // should be converted to an intrinsic (or removed?) + migrate_pkg(ctx, super::SI_GENERIC_FRAME_PKG, None).await?; for test_schema in [BuiltinSchema::Starfield, BuiltinSchema::Fallout] { migrate_schema(ctx, test_schema, &driver).await?; ctx.blocking_commit().await?; @@ -120,6 +124,7 @@ pub async fn migrate_for_tests( migrate_pkg(ctx, super::SI_AWS_EC2_PKG, Some(schemas.to_owned())).await?; migrate_pkg(ctx, super::SI_COREOS_PKG, Some(schemas.to_owned())).await?; migrate_pkg(ctx, super::SI_DOCKER_IMAGE_PKG, Some(schemas.to_owned())).await?; + migrate_pkg(ctx, super::SI_GENERIC_FRAME_PKG, Some(schemas.to_owned())).await?; for test_schema in [BuiltinSchema::Starfield, BuiltinSchema::Fallout] { if specific_builtin_schemas.contains(test_schema.real_schema_name()) { migrate_schema(ctx, test_schema, &driver).await?; diff --git a/lib/dal/src/builtins/schema/test_exclusive_fallout.rs b/lib/dal/src/builtins/schema/test_exclusive_fallout.rs index 7bfe862292..5cb9a08ef5 100644 --- a/lib/dal/src/builtins/schema/test_exclusive_fallout.rs +++ b/lib/dal/src/builtins/schema/test_exclusive_fallout.rs @@ -1,8 +1,10 @@ use si_pkg::{ ActionFuncSpec, AttrFuncInputSpec, AttrFuncInputSpecKind, FuncArgumentSpec, FuncSpec, FuncSpecBackendKind, FuncSpecBackendResponseType, PkgSpec, PropSpec, SchemaSpec, - SchemaVariantSpec, SiPkg, SocketSpec, SocketSpecKind, ValidationSpec, ValidationSpecKind, + SchemaVariantSpec, SchemaVariantSpecData, SiPkg, SocketSpec, SocketSpecData, SocketSpecKind, + ValidationSpec, ValidationSpecKind, }; +use si_pkg::{FuncSpecData, SchemaSpecData}; use crate::func::argument::FuncArgumentKind; use crate::func::intrinsics::IntrinsicFunc; @@ -24,35 +26,56 @@ impl MigrationDriver { let fallout_create_action_code = "async function create() { return { payload: { \"poop\": true }, status: \"ok\" }; }"; + let fn_name = "test:createActionFallout"; let fallout_create_action_func = FuncSpec::builder() - .name("test:createActionFallout") - .code_plaintext(fallout_create_action_code) - .handler("create") - .backend_kind(FuncSpecBackendKind::JsAction) - .response_type(FuncSpecBackendResponseType::Action) + .name(fn_name) + .unique_id(fn_name) + .data( + FuncSpecData::builder() + .name(fn_name) + .code_plaintext(fallout_create_action_code) + .handler("create") + .backend_kind(FuncSpecBackendKind::JsAction) + .response_type(FuncSpecBackendResponseType::Action) + .build()?, + ) .build()?; let fallout_scaffold_func = "function createAsset() {\ return new AssetBuilder().build(); }"; + let fn_name = "test:scaffoldFalloutAsset"; let fallout_authoring_schema_func = FuncSpec::builder() - .name("test:scaffoldFalloutAsset") - .code_plaintext(fallout_scaffold_func) - .handler("createAsset") - .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) - .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) + .name(fn_name) + .unique_id(fn_name) + .data( + FuncSpecData::builder() + .name(fn_name) + .code_plaintext(fallout_scaffold_func) + .handler("createAsset") + .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) + .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) + .build()?, + ) .build()?; let fallout_resource_payload_to_value_func_code = "async function translate(arg: Input): Promise {\ return arg.payload ?? {}; }"; + let fn_name = "test:resourcePayloadToValue"; let fallout_resource_payload_to_value_func = FuncSpec::builder() - .name("test:resourcePayloadToValue") - .code_plaintext(fallout_resource_payload_to_value_func_code) - .handler("translate") - .backend_kind(FuncSpecBackendKind::JsAttribute) - .response_type(FuncSpecBackendResponseType::Json) + .name(fn_name) + .unique_id(fn_name) + .data( + FuncSpecData::builder() + .name(fn_name) + .code_plaintext(fallout_resource_payload_to_value_func_code) + .handler("translate") + .backend_kind(FuncSpecBackendKind::JsAttribute) + .response_type(FuncSpecBackendResponseType::Json) + .build()?, + ) .argument( FuncArgumentSpec::builder() .name("payload") @@ -63,18 +86,31 @@ impl MigrationDriver { let fallout_schema = SchemaSpec::builder() .name("fallout") - .category("test exclusive") - .category_name("fallout") + .data( + SchemaSpecData::builder() + .name("fallout") + .category("test exclusive") + .category_name("fallout") + .build() + .expect("build schema spec data"), + ) .variant( SchemaVariantSpec::builder() - .color("#ffffff") .name("v0") - .func_unique_id(fallout_authoring_schema_func.unique_id) + .unique_id("fallout_sv") + .data( + SchemaVariantSpecData::builder() + .name("v0") + .color("#ffffff") + .func_unique_id(&fallout_authoring_schema_func.unique_id) + .build() + .expect("fallout variant data"), + ) .domain_prop( PropSpec::builder() .name("name") .kind(PropKind::String) - .func_unique_id(identity_func_spec.unique_id) + .func_unique_id(&identity_func_spec.unique_id) .input( AttrFuncInputSpec::builder() .kind(AttrFuncInputSpecKind::Prop) @@ -118,8 +154,13 @@ impl MigrationDriver { .socket( SocketSpec::builder() .name("bethesda") - .kind(SocketSpecKind::Output) - .func_unique_id(identity_func_spec.unique_id) + .data( + SocketSpecData::builder() + .name("bethesda") + .kind(SocketSpecKind::Output) + .func_unique_id(&identity_func_spec.unique_id) + .build()?, + ) .input( AttrFuncInputSpec::builder() .name("identity") @@ -132,8 +173,13 @@ impl MigrationDriver { .socket( SocketSpec::builder() .name("fallout") - .kind(SocketSpecKind::Output) - .func_unique_id(identity_func_spec.unique_id) + .data( + SocketSpecData::builder() + .name("fallout") + .kind(SocketSpecKind::Output) + .func_unique_id(&identity_func_spec.unique_id) + .build()?, + ) .input( AttrFuncInputSpec::builder() .name("identity") @@ -146,7 +192,7 @@ impl MigrationDriver { .action_func( ActionFuncSpec::builder() .kind(&ActionKind::Create) - .func_unique_id(fallout_create_action_func.unique_id) + .func_unique_id(&fallout_create_action_func.unique_id) .build()?, ) .build()?, diff --git a/lib/dal/src/builtins/schema/test_exclusive_starfield.rs b/lib/dal/src/builtins/schema/test_exclusive_starfield.rs index e07bd9b168..d2d5ab5d4f 100644 --- a/lib/dal/src/builtins/schema/test_exclusive_starfield.rs +++ b/lib/dal/src/builtins/schema/test_exclusive_starfield.rs @@ -1,7 +1,8 @@ +use si_pkg::SchemaSpecData; use si_pkg::{ ActionFuncSpec, AttrFuncInputSpec, AttrFuncInputSpecKind, FuncArgumentSpec, FuncSpec, - FuncSpecBackendKind, FuncSpecBackendResponseType, PkgSpec, PropSpec, SchemaSpec, - SchemaVariantSpec, SiPkg, SocketSpec, SocketSpecKind, + FuncSpecBackendKind, FuncSpecBackendResponseType, FuncSpecData, PkgSpec, PropSpec, SchemaSpec, + SchemaVariantSpec, SchemaVariantSpecData, SiPkg, SocketSpec, SocketSpecData, SocketSpecKind, }; use crate::func::argument::FuncArgumentKind; @@ -26,12 +27,20 @@ impl MigrationDriver { let starfield_create_action_code = "async function create() { return { payload: { \"poop\": true }, status: \"ok\" }; }"; + + let fn_name = "test:createActionStarfield"; let starfield_create_action_func = FuncSpec::builder() - .name("test:createActionStarfield") - .code_plaintext(starfield_create_action_code) - .handler("create") - .backend_kind(FuncSpecBackendKind::JsAction) - .response_type(FuncSpecBackendResponseType::Action) + .name(fn_name) + .unique_id(fn_name) + .data( + FuncSpecData::builder() + .name(fn_name) + .code_plaintext(starfield_create_action_code) + .handler("create") + .backend_kind(FuncSpecBackendKind::JsAction) + .response_type(FuncSpecBackendResponseType::Action) + .build()?, + ) .build()?; let starfield_refresh_action_code = @@ -39,12 +48,19 @@ impl MigrationDriver { return { payload: { \"poop\": true }, status: \"ok\" }; }"; + let fn_name = "test:refreshActionStarfield"; let starfield_refresh_action_func = FuncSpec::builder() - .name("test:refreshActionStarfield") - .handler("refresh") - .code_plaintext(starfield_refresh_action_code) - .backend_kind(FuncSpecBackendKind::JsAction) - .response_type(FuncSpecBackendResponseType::Action) + .name(fn_name) + .unique_id(fn_name) + .data( + FuncSpecData::builder() + .name(fn_name) + .handler("refresh") + .code_plaintext(starfield_refresh_action_code) + .backend_kind(FuncSpecBackendKind::JsAction) + .response_type(FuncSpecBackendResponseType::Action) + .build()?, + ) .build()?; let fallout_entries_to_galaxies_transform_code = @@ -71,12 +87,19 @@ impl MigrationDriver { return galaxies; }"; + let fn_name = "test:falloutEntriesToGalaxies"; let fallout_entries_to_galaxies_transform_func = FuncSpec::builder() - .name("test:falloutEntriesToGalaxies") - .code_plaintext(fallout_entries_to_galaxies_transform_code) - .handler("falloutEntriesToGalaxies") - .backend_kind(FuncSpecBackendKind::JsAttribute) - .response_type(FuncSpecBackendResponseType::Array) + .name(fn_name) + .unique_id(fn_name) + .data( + FuncSpecData::builder() + .name(fn_name) + .code_plaintext(fallout_entries_to_galaxies_transform_code) + .handler("falloutEntriesToGalaxies") + .backend_kind(FuncSpecBackendKind::JsAttribute) + .response_type(FuncSpecBackendResponseType::Array) + .build()?, + ) .argument( FuncArgumentSpec::builder() .name("entries") @@ -89,24 +112,38 @@ impl MigrationDriver { let starfield_scaffold_func = "function createAsset() {\ return new AssetBuilder().build(); }"; + let fn_name = "test:scaffoldStarfieldAsset"; let starfield_authoring_schema_func = FuncSpec::builder() - .name("test:scaffoldStarfieldAsset") - .code_plaintext(starfield_scaffold_func) - .handler("createAsset") - .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) - .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) + .name(fn_name) + .unique_id(fn_name) + .data( + FuncSpecData::builder() + .name(fn_name) + .code_plaintext(starfield_scaffold_func) + .handler("createAsset") + .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) + .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) + .build()?, + ) .build()?; let starfield_resource_payload_to_value_func_code = "async function translate(arg: Input): Promise {\ return arg.payload ?? {}; }"; + let fn_name = "test:resourcePayloadToValue"; let starfield_resource_payload_to_value_func = FuncSpec::builder() - .name("test:resourcePayloadToValue") - .code_plaintext(starfield_resource_payload_to_value_func_code) - .handler("translate") - .backend_kind(FuncSpecBackendKind::JsAttribute) - .response_type(FuncSpecBackendResponseType::Json) + .name(fn_name) + .unique_id(fn_name) + .data( + FuncSpecData::builder() + .name(fn_name) + .code_plaintext(starfield_resource_payload_to_value_func_code) + .handler("translate") + .backend_kind(FuncSpecBackendKind::JsAttribute) + .response_type(FuncSpecBackendResponseType::Json) + .build()?, + ) .argument( FuncArgumentSpec::builder() .name("payload") @@ -117,18 +154,31 @@ impl MigrationDriver { let starfield_schema = SchemaSpec::builder() .name("starfield") - .category("test exclusive") - .category_name("starfield") + .data( + SchemaSpecData::builder() + .name("starfield") + .category("test exclusive") + .category_name("starfield") + .build() + .expect("schema spec data build"), + ) .variant( SchemaVariantSpec::builder() - .color("#ffffff") .name("v0") - .func_unique_id(starfield_authoring_schema_func.unique_id) + .unique_id("starfield_sv") + .data( + SchemaVariantSpecData::builder() + .name("v0") + .color("#ffffff") + .func_unique_id(&starfield_authoring_schema_func.unique_id) + .build() + .expect("build variant spec data"), + ) .domain_prop( PropSpec::builder() .name("name") .kind(PropKind::String) - .func_unique_id(identity_func_spec.unique_id) + .func_unique_id(&identity_func_spec.unique_id) .input( AttrFuncInputSpec::builder() .kind(AttrFuncInputSpecKind::Prop) @@ -155,7 +205,7 @@ impl MigrationDriver { PropSpec::builder() .name("attributes") .kind(PropKind::String) - .func_unique_id(identity_func_spec.unique_id) + .func_unique_id(&identity_func_spec.unique_id) .input( AttrFuncInputSpec::builder() .kind(AttrFuncInputSpecKind::InputSocket) @@ -174,7 +224,7 @@ impl MigrationDriver { .name("galaxies") .kind(PropKind::Array) .func_unique_id( - fallout_entries_to_galaxies_transform_func.unique_id, + &fallout_entries_to_galaxies_transform_func.unique_id, ) .input( AttrFuncInputSpec::builder() @@ -208,25 +258,35 @@ impl MigrationDriver { .socket( SocketSpec::builder() .name("bethesda") - .kind(SocketSpecKind::Input) + .data( + SocketSpecData::builder() + .name("bethesda") + .kind(SocketSpecKind::Input) + .build()?, + ) .build()?, ) .socket( SocketSpec::builder() .name("fallout") - .kind(SocketSpecKind::Input) + .data( + SocketSpecData::builder() + .name("fallout") + .kind(SocketSpecKind::Input) + .build()?, + ) .build()?, ) .action_func( ActionFuncSpec::builder() .kind(&ActionKind::Create) - .func_unique_id(starfield_create_action_func.unique_id) + .func_unique_id(&starfield_create_action_func.unique_id) .build()?, ) .action_func( ActionFuncSpec::builder() .kind(&ActionKind::Refresh) - .func_unique_id(starfield_refresh_action_func.unique_id) + .func_unique_id(&starfield_refresh_action_func.unique_id) .build()?, ) .build()?, diff --git a/lib/dal/src/func/intrinsics.rs b/lib/dal/src/func/intrinsics.rs index 432ed44cf8..fdb1d49267 100644 --- a/lib/dal/src/func/intrinsics.rs +++ b/lib/dal/src/func/intrinsics.rs @@ -1,6 +1,6 @@ use si_pkg::{ FuncArgumentKind, FuncArgumentSpec, FuncSpec, FuncSpecBackendKind, FuncSpecBackendResponseType, - PkgSpec, + FuncSpecData, PkgSpec, }; use super::{FuncError, FuncResult}; @@ -43,13 +43,21 @@ impl IntrinsicFunc { pub fn to_spec(&self) -> FuncResult { let mut builder = FuncSpec::builder(); builder.name(self.name()); - builder.handler(""); - builder.code_plaintext(""); + let mut data_builder = FuncSpecData::builder(); + data_builder + .name(self.name()) + .handler("") + .code_plaintext(""); + + // These magic unique ids are here to keep them consistent with the intrinsic ids in the + // existing builtin packages (chicken/egg problem here a bit) match self { Self::Identity => { - builder.backend_kind(FuncSpecBackendKind::Identity); - builder.response_type(FuncSpecBackendResponseType::Identity); + builder + .unique_id("c6938e12287ab65f8ba8234559178413f2e2c02c44ea08384ed6687a36ec4f50"); + data_builder.backend_kind(FuncSpecBackendKind::Identity); + data_builder.response_type(FuncSpecBackendResponseType::Identity); builder.argument( FuncArgumentSpec::builder() .name("identity") @@ -59,40 +67,61 @@ impl IntrinsicFunc { ); } Self::SetArray => { - builder.backend_kind(FuncSpecBackendKind::Array); - builder.response_type(FuncSpecBackendResponseType::Array); + builder + .unique_id("51049a590fb64860f159972012ac2657c629479a244d6bcc4b1b73ba4b29f87f"); + data_builder.backend_kind(FuncSpecBackendKind::Array); + data_builder.response_type(FuncSpecBackendResponseType::Array); } Self::SetBoolean => { - builder.backend_kind(FuncSpecBackendKind::Boolean); - builder.response_type(FuncSpecBackendResponseType::Boolean); + builder + .unique_id("577a7deea25cfad0d4b2dd1e1f3d96b86b8b1578605137b8c4128d644c86964b"); + data_builder.backend_kind(FuncSpecBackendKind::Boolean); + data_builder.response_type(FuncSpecBackendResponseType::Boolean); } Self::SetInteger => { - builder.backend_kind(FuncSpecBackendKind::Integer); - builder.response_type(FuncSpecBackendResponseType::Integer); + builder + .unique_id("7d384b237852f20b8dec2fbd2e644ffc6bde901d7dc937bd77f50a0d57e642a9"); + data_builder.backend_kind(FuncSpecBackendKind::Integer); + data_builder.response_type(FuncSpecBackendResponseType::Integer); } Self::SetMap => { - builder.backend_kind(FuncSpecBackendKind::Map); - builder.response_type(FuncSpecBackendResponseType::Map); + builder + .unique_id("dea5084fbf6e7fe8328ac725852b96f4b5869b14d0fe9dd63a285fa876772496"); + data_builder.backend_kind(FuncSpecBackendKind::Map); + data_builder.response_type(FuncSpecBackendResponseType::Map); } Self::SetObject => { - builder.backend_kind(FuncSpecBackendKind::Object); - builder.response_type(FuncSpecBackendResponseType::Object); + builder + .unique_id("cb9bf94739799f3a8b84bcb88495f93b27b47c31a341f8005a60ca39308909fd"); + data_builder.backend_kind(FuncSpecBackendKind::Object); + data_builder.response_type(FuncSpecBackendResponseType::Object); } Self::SetString => { - builder.backend_kind(FuncSpecBackendKind::String); - builder.response_type(FuncSpecBackendResponseType::String); + builder + .unique_id("bbe86d1a2b92c3e34b72a407cca424878d3466d29ca60e56a251a52a0840bfbd"); + data_builder.backend_kind(FuncSpecBackendKind::String); + data_builder.response_type(FuncSpecBackendResponseType::String); } Self::Unset => { - builder.backend_kind(FuncSpecBackendKind::Unset); - builder.response_type(FuncSpecBackendResponseType::Unset); + builder + .unique_id("8143ff98fbe8954bb3ab89ee521335d45ba9a42b7b79289eff53b503c4392c37"); + data_builder.backend_kind(FuncSpecBackendKind::Unset); + data_builder.response_type(FuncSpecBackendResponseType::Unset); } Self::Validation => { - builder.backend_kind(FuncSpecBackendKind::Validation); - builder.response_type(FuncSpecBackendResponseType::Validation); + builder + .unique_id("039ff70bc7922338978ab52a39156992b7d8e3390f0ef7e99d5b6ffd43141d8a"); + data_builder.backend_kind(FuncSpecBackendKind::Validation); + data_builder.response_type(FuncSpecBackendResponseType::Validation); } }; + let data = data_builder + .build() + .map_err(|e| FuncError::IntrinsicSpecCreation(e.to_string()))?; + builder + .data(data) .build() .map_err(|e| FuncError::IntrinsicSpecCreation(e.to_string())) } diff --git a/lib/dal/src/pkg.rs b/lib/dal/src/pkg.rs index 0f0231ab32..7136583e63 100644 --- a/lib/dal/src/pkg.rs +++ b/lib/dal/src/pkg.rs @@ -4,13 +4,11 @@ use url::ParseError; mod export; mod import; -pub use export::export_pkg_as_bytes; -pub use export::get_component_type; +pub use export::{get_component_type, PkgExporter}; pub use import::{import_pkg, import_pkg_from_pkg, ImportOptions}; use si_pkg::{FuncSpecBackendKind, FuncSpecBackendResponseType, SiPkgError, SpecError}; -use crate::schema::variant::definition::SchemaVariantDefinitionId; use crate::{ func::{ argument::{FuncArgumentError, FuncArgumentId}, @@ -18,14 +16,14 @@ use crate::{ }, installed_pkg::InstalledPkgError, prop_tree::PropTreeError, - schema::variant::definition::SchemaVariantDefinitionError, + schema::variant::definition::{SchemaVariantDefinitionError, SchemaVariantDefinitionId}, socket::SocketError, ActionPrototypeError, AttributeContextBuilderError, AttributePrototypeArgumentError, AttributePrototypeArgumentId, AttributePrototypeError, AttributePrototypeId, - AttributeReadContext, AttributeValueError, ExternalProviderError, ExternalProviderId, - FuncBackendKind, FuncBackendResponseType, FuncError, FuncId, InternalProviderError, - InternalProviderId, PropError, PropId, PropKind, SchemaError, SchemaId, SchemaVariantError, - SchemaVariantId, StandardModelError, ValidationPrototypeError, + AttributeReadContext, AttributeValueError, ChangeSetError, ChangeSetPk, ExternalProviderError, + ExternalProviderId, FuncBackendKind, FuncBackendResponseType, FuncError, FuncId, + InternalProviderError, InternalProviderId, PropError, PropId, PropKind, SchemaError, SchemaId, + SchemaVariantError, SchemaVariantId, StandardModelError, ValidationPrototypeError, }; #[remain::sorted] @@ -57,8 +55,14 @@ pub enum PkgError { ), #[error(transparent)] AttributeValue(#[from] AttributeValueError), + #[error(transparent)] + ChangeSet(#[from] ChangeSetError), + #[error("change set {0} not found")] + ChangeSetNotFound(ChangeSetPk), #[error("map item prop {0} has both custom key prototypes and custom prop only prototype")] ConflictingMapKeyPrototypes(PropId), + #[error("expected data on an SiPkg node, but none found: {0}")] + DataNotFound(String), #[error("Cannot find Socket for explicit InternalProvider {0}")] ExplicitInternalProviderMissingSocket(InternalProviderId), #[error(transparent)] @@ -93,6 +97,8 @@ pub enum PkgError { MissingAttributePrototypeForOutputSocket(AttributePrototypeId, ExternalProviderId), #[error("Missing Func {1} for AttributePrototype {0}")] MissingAttributePrototypeFunc(AttributePrototypeId, FuncId), + #[error("Missing a func map for changeset {0}")] + MissingChangeSetFuncMap(ChangeSetPk), #[error("Func {0} missing from exported funcs")] MissingExportedFunc(FuncId), #[error("Cannot find FuncArgument {0} for Func {1}")] diff --git a/lib/dal/src/pkg/export.rs b/lib/dal/src/pkg/export.rs index ade7b7baa3..405278e016 100644 --- a/lib/dal/src/pkg/export.rs +++ b/lib/dal/src/pkg/export.rs @@ -1,877 +1,1223 @@ +use chrono::Utc; use std::collections::{hash_map::Entry, HashMap}; use strum::IntoEnumIterator; use telemetry::prelude::*; use si_pkg::{ - ActionFuncSpec, AttrFuncInputSpec, AttrFuncInputSpecKind, FuncArgumentSpec, FuncSpec, - FuncUniqueId, LeafFunctionSpec, MapKeyFuncSpec, PkgSpec, PropSpec, PropSpecBuilder, - PropSpecKind, SchemaSpec, SchemaVariantSpec, SchemaVariantSpecBuilder, - SchemaVariantSpecComponentType, SchemaVariantSpecPropRoot, SiPkg, SiPropFuncSpec, - SiPropFuncSpecKind, SocketSpec, SocketSpecKind, SpecError, ValidationSpec, ValidationSpecKind, + ActionFuncSpec, AttrFuncInputSpec, AttrFuncInputSpecKind, ChangeSetSpec, FuncArgumentSpec, + FuncSpec, FuncSpecData, LeafFunctionSpec, MapKeyFuncSpec, PkgSpec, PropSpec, PropSpecBuilder, + PropSpecKind, SchemaSpec, SchemaSpecData, SchemaVariantSpec, SchemaVariantSpecBuilder, + SchemaVariantSpecComponentType, SchemaVariantSpecData, SchemaVariantSpecPropRoot, SiPkg, + SiPkgKind, SiPropFuncSpec, SiPropFuncSpecKind, SocketSpec, SocketSpecData, SocketSpecKind, + SpecError, ValidationSpec, ValidationSpecKind, }; use crate::schema::variant::definition::SchemaVariantDefinition; +use crate::ChangeSetPk; use crate::{ func::{argument::FuncArgument, backend::validation::FuncBackendValidationArgs}, prop_tree::{PropTree, PropTreeNode}, socket::SocketKind, validation::Validation, ActionPrototype, ActionPrototypeContext, AttributeContextBuilder, AttributePrototype, - AttributePrototypeArgument, AttributeReadContext, AttributeValue, ComponentType, DalContext, - ExternalProvider, ExternalProviderId, Func, FuncId, InternalProvider, InternalProviderId, - LeafInputLocation, LeafKind, Prop, PropId, PropKind, Schema, SchemaVariant, SchemaVariantError, - SchemaVariantId, Socket, StandardModel, StandardModelError, ValidationPrototype, + AttributePrototypeArgument, AttributeReadContext, AttributeValue, ChangeSet, ComponentType, + DalContext, ExternalProvider, ExternalProviderId, Func, FuncId, InternalProvider, + InternalProviderId, LeafInputLocation, LeafKind, Prop, PropId, PropKind, Schema, SchemaId, + SchemaVariant, SchemaVariantError, SchemaVariantId, Socket, StandardModel, ValidationPrototype, }; use super::{PkgError, PkgResult}; -type FuncSpecMap = HashMap; +type ChangeSetMap = HashMap; -pub async fn export_pkg_as_bytes( - ctx: &DalContext, - name: impl Into, - version: impl Into, - description: Option>, - created_by: impl Into, - variant_ids: Vec, -) -> PkgResult> { - info!("Building module package"); - let pkg = build_pkg(ctx, name, version, description, created_by, variant_ids).await?; - info!("Exporting as bytes"); - - Ok(pkg.write_to_bytes()?) -} +#[derive(Debug)] +struct FuncSpecMap(HashMap); -async fn build_pkg( - ctx: &DalContext, - name: impl Into, - version: impl Into, - description: Option>, - created_by: impl Into, - variant_ids: Vec, -) -> PkgResult { - let mut pkg_spec_builder = PkgSpec::builder(); - pkg_spec_builder - .name(name) - .version(version) - .created_by(created_by); - if let Some(description) = description { - pkg_spec_builder.description(description); +impl FuncSpecMap { + pub fn new() -> Self { + Self(HashMap::new()) } - let mut func_specs = FuncSpecMap::new(); - - for intrinsic in crate::func::intrinsics::IntrinsicFunc::iter() { - let intrinsic_name = intrinsic.name(); - // We need a unique id for intrinsic funcs to refer to them in custom bindings (for example - // mapping one prop to another via si:identity) - let intrinsic_func = Func::find_by_name(ctx, intrinsic_name) - .await? - .ok_or(PkgError::MissingIntrinsicFunc(intrinsic_name.to_string()))?; - let intrinsic_spec = intrinsic.to_spec()?; - func_specs.insert(*intrinsic_func.id(), intrinsic_spec.clone()); - pkg_spec_builder.func(intrinsic_spec); + #[allow(dead_code)] + pub fn get_in_changeset_only( + &self, + change_set_pk: Option, + func_id: FuncId, + ) -> Option<&FuncSpec> { + match self.0.get(&change_set_pk.unwrap_or(ChangeSetPk::NONE)) { + Some(change_set_map) => change_set_map.get(&func_id), + None => None, + } } - for variant_id in variant_ids { - let related_funcs = SchemaVariant::all_funcs(ctx, variant_id).await?; - for func in &related_funcs { - if !func_specs.contains_key(func.id()) { - let arguments = FuncArgument::list_for_func(ctx, *func.id()).await?; - let func_spec = build_func_spec(func, &arguments)?; - func_specs.insert(*func.id(), func_spec.clone()); - pkg_spec_builder.func(func_spec); - } + #[allow(dead_code)] + pub fn get(&self, change_set_pk: Option, func_id: FuncId) -> Option<&FuncSpec> { + match self.0.get(&change_set_pk.unwrap_or(ChangeSetPk::NONE)) { + Some(change_set_map) => change_set_map.get(&func_id).or_else(|| { + self.0 + .get(&ChangeSetPk::NONE) + .and_then(|funcs| funcs.get(&func_id)) + }), + None => self + .0 + .get(&ChangeSetPk::NONE) + .and_then(|funcs| funcs.get(&func_id)), } - let schema_spec = build_schema_spec(ctx, variant_id, &func_specs).await?; - pkg_spec_builder.schema(schema_spec); } - let spec = pkg_spec_builder.build()?; + pub fn init_change_set_map(&mut self, change_set_pk: Option) { + self.0 + .entry(change_set_pk.unwrap_or(ChangeSetPk::NONE)) + .or_default(); + } - let pkg = SiPkg::load_from_spec(spec)?; + pub fn contains_func(&self, change_set_pk: Option, func_id: FuncId) -> bool { + match self.0.get(&change_set_pk.unwrap_or(ChangeSetPk::NONE)) { + None => false, + Some(inner_map) => inner_map.contains_key(&func_id), + } + } - Ok(pkg) + pub fn insert( + &mut self, + change_set_pk: Option, + func_id: FuncId, + spec: FuncSpec, + ) -> Option { + let change_set_pk = change_set_pk.unwrap_or(ChangeSetPk::NONE); + self.0 + .entry(change_set_pk) + .or_insert(HashMap::new()) + .insert(func_id, spec) + } } -fn build_func_spec(func: &Func, args: &[FuncArgument]) -> PkgResult { - let mut func_spec_builder = FuncSpec::builder(); +pub struct PkgExporter { + name: String, + version: String, + description: Option, + kind: SiPkgKind, + created_by: String, + schema_ids: Option>, + func_map: FuncSpecMap, +} - func_spec_builder.name(func.name()); +fn std_model_change_set_matches( + change_set_pk: Option, + standard_model_thing: &StdModel, +) -> bool { + match change_set_pk { + None => true, + Some(change_set_pk) => standard_model_thing.visibility().change_set_pk == change_set_pk, + } +} - if let Some(display_name) = func.display_name() { - func_spec_builder.display_name(display_name); +fn change_set_matches( + current_change_set_pk: Option, + object_change_set_pk: ChangeSetPk, +) -> bool { + match current_change_set_pk { + None => true, + Some(current_change_set_pk) => object_change_set_pk == current_change_set_pk, } +} - if let Some(description) = func.description() { - func_spec_builder.description(description); +impl PkgExporter { + pub fn new_module_exporter( + name: impl Into, + version: impl Into, + description: Option>, + created_by: impl Into, + schema_ids: Vec, + ) -> Self { + Self { + name: name.into(), + version: version.into(), + description: description.map(Into::into), + kind: SiPkgKind::Module, + created_by: created_by.into(), + schema_ids: Some(schema_ids), + func_map: FuncSpecMap::new(), + } } - if let Some(link) = func.link() { - func_spec_builder.try_link(link)?; + pub fn new_workspace_exporter(name: impl Into, created_by: impl Into) -> Self { + let version = Utc::now().format("%Y-%m-%d_%H:%M:%S").to_string(); + let description = "workspace backup"; + + Self { + name: name.into(), + version, + description: Some(description.into()), + kind: SiPkgKind::WorkspaceBackup, + created_by: created_by.into(), + schema_ids: None, + func_map: FuncSpecMap::new(), + } } - // Should we package an empty func? - func_spec_builder.handler(func.handler().unwrap_or("")); - func_spec_builder.code_base64(func.code_base64().unwrap_or("")); - - func_spec_builder.response_type(*func.backend_response_type()); - func_spec_builder.backend_kind(*func.backend_kind()); - - func_spec_builder.hidden(func.hidden()); - - for arg in args { - func_spec_builder.argument( - FuncArgumentSpec::builder() - .name(arg.name()) - .kind(*arg.kind()) - .element_kind(arg.element_kind().cloned().map(|kind| kind.into())) - .build()?, - ); + + pub async fn export_as_bytes(&mut self, ctx: &DalContext) -> PkgResult> { + match self.kind { + SiPkgKind::Module => info!("Building module package"), + SiPkgKind::WorkspaceBackup => info!("Building workspace backup package"), + } + + let pkg = self.export(ctx).await?; + + info!("Exporting as bytes"); + + Ok(pkg.write_to_bytes()?) } - Ok(func_spec_builder.build()?) -} + async fn export_schema( + &mut self, + ctx: &DalContext, + change_set_pk: Option, + schema: &Schema, + ) -> PkgResult<(SchemaSpec, Vec)> { + let variants = schema.variants(ctx).await?; + let mut funcs = vec![]; + + let mut schema_spec_builder = SchemaSpec::builder(); + schema_spec_builder.name(schema.name()); + schema_spec_builder.unique_id(schema.id().to_string()); + + let in_change_set = std_model_change_set_matches(change_set_pk, schema); + let is_deleted = schema.visibility().is_deleted(); + + if in_change_set && is_deleted { + schema_spec_builder.deleted(true); + } else if in_change_set { + let mut data_builder = SchemaSpecData::builder(); + data_builder.name(schema.name()); + data_builder.ui_hidden(schema.ui_hidden()); + let schema_ui_menu = schema.ui_menus(ctx).await?.pop().ok_or_else(|| { + PkgError::StandardModelMissingBelongsTo( + "schema_ui_menu_belongs_to_schema", + "schema", + (*schema.id()).to_string(), + ) + })?; + data_builder.category(schema_ui_menu.category()); + data_builder.category_name(schema_ui_menu.name()); + schema_spec_builder.data(data_builder.build()?); + } -async fn build_schema_spec( - ctx: &DalContext, - variant_id: SchemaVariantId, - func_specs: &FuncSpecMap, -) -> PkgResult { - let (variant, schema) = get_schema_and_variant(ctx, variant_id).await?; + for variant in &variants { + let related_funcs = SchemaVariant::all_funcs(ctx, *variant.id()).await?; - let mut schema_spec_builder = SchemaSpec::builder(); - schema_spec_builder.name(schema.name()); - schema_spec_builder.ui_hidden(schema.ui_hidden()); - set_schema_spec_category_data(ctx, &schema, &mut schema_spec_builder).await?; + for func in &related_funcs { + if !self.func_map.contains_func(change_set_pk, *func.id()) { + if let Some(func_spec) = self.export_func(ctx, change_set_pk, func).await? { + self.func_map + .insert(change_set_pk, *func.id(), func_spec.clone()); + funcs.push(func_spec); + } + } + } - let variant_spec = build_variant_spec(ctx, variant, func_specs).await?; - schema_spec_builder.variant(variant_spec); + if !is_deleted { + let variant_spec = self.export_variant(ctx, change_set_pk, variant).await?; + schema_spec_builder.variant(variant_spec); + } + } - let schema_spec = schema_spec_builder.build()?; + let schema_spec = schema_spec_builder.build()?; - Ok(schema_spec) -} + Ok((schema_spec, funcs)) + } -async fn build_leaf_function_specs( - ctx: &DalContext, - variant_id: SchemaVariantId, - func_specs: &FuncSpecMap, -) -> PkgResult> { - let mut specs = vec![]; + async fn export_variant( + &self, + ctx: &DalContext, + change_set_pk: Option, + variant: &SchemaVariant, + ) -> PkgResult { + let mut variant_spec_builder = SchemaVariantSpec::builder(); + variant_spec_builder.name(variant.name()); + + let schema_variant_definition = + SchemaVariantDefinition::get_by_schema_variant_id(ctx, variant.id()) + .await? + .ok_or(PkgError::MissingSchemaVariantDefinition(*variant.id()))?; - for leaf_kind in LeafKind::iter() { - for leaf_func in SchemaVariant::find_leaf_item_functions(ctx, variant_id, leaf_kind).await? + if std_model_change_set_matches(change_set_pk, variant) + || std_model_change_set_matches(change_set_pk, &schema_variant_definition) { - let func_spec = func_specs - .get(leaf_func.id()) - .ok_or(PkgError::MissingExportedFunc(*leaf_func.id()))?; + if std_model_change_set_matches(change_set_pk, variant) + && variant.visibility().is_deleted() + { + variant_spec_builder.deleted(true); + } - let mut inputs = vec![]; - for arg in FuncArgument::list_for_func(ctx, *leaf_func.id()).await? { - inputs.push( - LeafInputLocation::maybe_from_arg_name(arg.name()) - .ok_or(SpecError::LeafInputLocationConversionError( - arg.name().into(), - ))? - .into(), - ); + let mut data_builder = SchemaVariantSpecData::builder(); + + data_builder.name(variant.name()); + + if let Some(color_str) = variant.color(ctx).await? { + data_builder.color(color_str); + }; + if let Some(link) = variant.link() { + data_builder.try_link(link)?; } - specs.push( - LeafFunctionSpec::builder() - .func_unique_id(func_spec.unique_id) - .leaf_kind(leaf_kind) - .inputs(inputs) - .build()?, - ); + data_builder.component_type(get_component_type(ctx, variant).await?); + + let asset_func_unique_id = self + .func_map + .get(change_set_pk, schema_variant_definition.func_id()) + .ok_or(PkgError::MissingExportedFunc( + schema_variant_definition.func_id(), + ))? + .unique_id + .to_owned(); + + data_builder.func_unique_id(asset_func_unique_id); + + variant_spec_builder.data(data_builder.build()?); } - } - Ok(specs) -} + self.export_prop_tree( + ctx, + change_set_pk, + variant, + &mut variant_spec_builder, + SchemaVariantSpecPropRoot::Domain, + ) + .await?; + + self.export_prop_tree( + ctx, + change_set_pk, + variant, + &mut variant_spec_builder, + SchemaVariantSpecPropRoot::ResourceValue, + ) + .await?; -async fn build_input_func_and_arguments( - ctx: &DalContext, - proto: &AttributePrototype, - func_specs: &FuncSpecMap, -) -> PkgResult)>> { - let proto_func = Func::get_by_id(ctx, &proto.func_id()).await?.ok_or( - PkgError::MissingAttributePrototypeFunc(*proto.id(), proto.func_id()), - )?; - - let apas = AttributePrototypeArgument::list_for_attribute_prototype(ctx, *proto.id()).await?; - - // If the prototype func is intrinsic and has no arguments, it's one that is created by default - // and we don't have to track it in the package - if apas.is_empty() && proto_func.is_intrinsic() { - return Ok(None); - } + self.export_leaf_funcs(ctx, change_set_pk, *variant.id()) + .await? + .drain(..) + .for_each(|leaf_func_spec| { + variant_spec_builder.leaf_function(leaf_func_spec); + }); - let mut inputs = vec![]; + self.export_sockets(ctx, change_set_pk, *variant.id()) + .await? + .drain(..) + .for_each(|socket_spec| { + variant_spec_builder.socket(socket_spec); + }); - for apa in apas { - let func_arg = FuncArgument::get_by_id(ctx, &apa.func_argument_id()) + self.export_action_funcs(ctx, change_set_pk, *variant.id()) .await? - .ok_or(PkgError::AttributePrototypeArgumentMissingFuncArgument( - *apa.id(), - apa.func_argument_id(), - ))?; - let arg_name = func_arg.name(); - - if apa.internal_provider_id() != InternalProviderId::NONE { - let ip = InternalProvider::get_by_id(ctx, &apa.internal_provider_id()) - .await? - .ok_or(PkgError::AttributePrototypeArgumentMissingInternalProvider( - *apa.id(), - apa.internal_provider_id(), - ))?; + .drain(..) + .for_each(|action_func_spec| { + variant_spec_builder.action_func(action_func_spec); + }); - match *ip.prop_id() { - PropId::NONE => { - inputs.push( - AttrFuncInputSpec::builder() - .name(arg_name) - .kind(AttrFuncInputSpecKind::InputSocket) - .socket_name(ip.name()) - .build()?, - ); - } - prop_id => { - let prop = Prop::get_by_id(ctx, &prop_id) - .await? - .ok_or(PkgError::InternalProviderMissingProp(*ip.id(), prop_id))?; + self.export_si_prop_funcs(ctx, change_set_pk, variant) + .await? + .drain(..) + .for_each(|si_prop_func_spec| { + variant_spec_builder.si_prop_func(si_prop_func_spec); + }); - inputs.push( - AttrFuncInputSpec::builder() - .name(arg_name) - .kind(AttrFuncInputSpecKind::Prop) - .prop_path(prop.path()) - .build()?, - ); + let variant_spec = variant_spec_builder.build()?; + + Ok(variant_spec) + } + + async fn export_si_prop_funcs( + &self, + ctx: &DalContext, + change_set_pk: Option, + variant: &SchemaVariant, + ) -> PkgResult> { + let mut specs = vec![]; + + for kind in SiPropFuncSpecKind::iter() { + let prop = variant.find_prop(ctx, &kind.prop_path()).await?; + + let context = AttributeContextBuilder::new() + .set_prop_id(*prop.id()) + .to_context()?; + + if let Some(prototype) = + AttributePrototype::find_for_context_and_key(ctx, context, &None) + .await? + .pop() + { + if let Some((func_unique_id, mut inputs)) = self + .export_input_func_and_arguments(ctx, change_set_pk, &prototype) + .await? + { + let mut builder = SiPropFuncSpec::builder(); + builder + .unique_id(prototype.id().to_string()) + .deleted(prototype.visibility().is_deleted()) + .func_unique_id(func_unique_id) + .kind(kind); + inputs.drain(..).for_each(|input| { + builder.input(input); + }); + + specs.push(builder.build()?); } } - } else if apa.external_provider_id() != ExternalProviderId::NONE { - let ep = ExternalProvider::get_by_id(ctx, &apa.external_provider_id()) - .await? - .ok_or(PkgError::AttributePrototypeArgumentMissingExternalProvider( - *apa.id(), - apa.external_provider_id(), - ))?; - - inputs.push( - AttrFuncInputSpec::builder() - .name(arg_name) - .kind(AttrFuncInputSpecKind::OutputSocket) - .socket_name(ep.name()) - .build()?, - ); } + + Ok(specs) } - let func_spec = func_specs - .get(proto_func.id()) - .ok_or(PkgError::MissingExportedFunc(*proto_func.id()))?; + async fn export_leaf_funcs( + &self, + ctx: &DalContext, + change_set_pk: Option, + variant_id: SchemaVariantId, + ) -> PkgResult> { + let mut specs = vec![]; + + for leaf_kind in LeafKind::iter() { + for (prototype, leaf_func) in + SchemaVariant::find_leaf_item_functions(ctx, variant_id, leaf_kind).await? + { + if !std_model_change_set_matches(change_set_pk, &prototype) { + continue; + } - let func_unique_id = func_spec.unique_id; + let func_spec = self + .func_map + .get(change_set_pk, *leaf_func.id()) + .ok_or(PkgError::MissingExportedFunc(*leaf_func.id()))?; - Ok(Some((func_unique_id, inputs))) -} + let mut inputs = vec![]; + for arg in FuncArgument::list_for_func(ctx, *leaf_func.id()).await? { + if arg.visibility().is_deleted() { + continue; + } -async fn build_socket_specs( - ctx: &DalContext, - schema_variant_id: SchemaVariantId, - func_specs: &FuncSpecMap, -) -> PkgResult> { - let mut specs = vec![]; - - for input_socket_ip in - InternalProvider::list_explicit_for_schema_variant(ctx, schema_variant_id).await? - { - let socket = Socket::find_for_internal_provider(ctx, *input_socket_ip.id()) - .await? - .pop() - .ok_or(PkgError::ExplicitInternalProviderMissingSocket( - *input_socket_ip.id(), - ))?; + inputs.push( + LeafInputLocation::maybe_from_arg_name(arg.name()) + .ok_or(SpecError::LeafInputLocationConversionError( + arg.name().into(), + ))? + .into(), + ); + } - if let SocketKind::Frame = socket.kind() { - continue; + specs.push( + LeafFunctionSpec::builder() + .func_unique_id(&func_spec.unique_id) + .leaf_kind(leaf_kind) + .inputs(inputs) + .deleted(prototype.visibility().is_deleted()) + .unique_id(prototype.id().to_string()) + .build()?, + ); + } } - let mut socket_spec_builder = SocketSpec::builder(); - socket_spec_builder - .name(input_socket_ip.name()) - .kind(SocketSpecKind::Input) - .ui_hidden(socket.ui_hidden()) - .arity(socket.arity()); + Ok(specs) + } + + async fn export_sockets( + &self, + ctx: &DalContext, + change_set_pk: Option, + variant_id: SchemaVariantId, + ) -> PkgResult> { + let mut specs = vec![]; - if let Some(attr_proto_id) = input_socket_ip.attribute_prototype_id() { - let proto = AttributePrototype::get_by_id(ctx, attr_proto_id) + for input_socket_ip in + InternalProvider::list_explicit_for_schema_variant(ctx, variant_id).await? + { + let socket = Socket::find_for_internal_provider(ctx, *input_socket_ip.id()) .await? - .ok_or(PkgError::MissingAttributePrototypeForInputSocket( - *attr_proto_id, + .pop() + .ok_or(PkgError::ExplicitInternalProviderMissingSocket( *input_socket_ip.id(), ))?; - if let Some((func_unique_id, mut inputs)) = - build_input_func_and_arguments(ctx, &proto, func_specs).await? - { - socket_spec_builder.func_unique_id(func_unique_id); - inputs.drain(..).for_each(|input| { - socket_spec_builder.input(input); - }); + if let SocketKind::Frame = socket.kind() { + continue; } - } - specs.push(socket_spec_builder.build()?); - } + let mut socket_spec_builder = SocketSpec::builder(); + socket_spec_builder + .name(input_socket_ip.name()) + .unique_id(input_socket_ip.id().to_string()); + let mut data_builder = SocketSpecData::builder(); + + data_builder + .name(input_socket_ip.name()) + .kind(SocketSpecKind::Input) + .arity(socket.arity()) + .ui_hidden(socket.ui_hidden()); + + let mut has_custom_func = false; + if let Some(attr_proto_id) = input_socket_ip.attribute_prototype_id() { + let proto = AttributePrototype::get_by_id(ctx, attr_proto_id) + .await? + .ok_or(PkgError::MissingAttributePrototypeForInputSocket( + *attr_proto_id, + *input_socket_ip.id(), + ))?; + + if let Some((func_unique_id, mut inputs)) = self + .export_input_func_and_arguments(ctx, change_set_pk, &proto) + .await? + { + has_custom_func = true; + data_builder.func_unique_id(func_unique_id); + inputs.drain(..).for_each(|input| { + socket_spec_builder.input(input); + }); + } + } - for output_socket_ep in - ExternalProvider::list_for_schema_variant(ctx, schema_variant_id).await? - { - let socket = Socket::find_for_external_provider(ctx, *output_socket_ep.id()) - .await? - .pop() - .ok_or(PkgError::ExternalProviderMissingSocket( - *output_socket_ep.id(), - ))?; + if std_model_change_set_matches(change_set_pk, &socket) || has_custom_func { + socket_spec_builder.data(data_builder.build()?); + } - if let SocketKind::Frame = socket.kind() { - continue; + specs.push(socket_spec_builder.build()?); } - let mut socket_spec_builder = SocketSpec::builder(); - socket_spec_builder - .name(output_socket_ep.name()) - .kind(SocketSpecKind::Output) - .ui_hidden(socket.ui_hidden()) - .arity(socket.arity()); - - if let Some(attr_proto_id) = output_socket_ep.attribute_prototype_id() { - let proto = AttributePrototype::get_by_id(ctx, attr_proto_id) + for output_socket_ep in ExternalProvider::list_for_schema_variant(ctx, variant_id).await? { + let socket = Socket::find_for_external_provider(ctx, *output_socket_ep.id()) .await? - .ok_or(PkgError::MissingAttributePrototypeForOutputSocket( - *attr_proto_id, + .pop() + .ok_or(PkgError::ExternalProviderMissingSocket( *output_socket_ep.id(), ))?; - if let Some((func_unique_id, mut inputs)) = - build_input_func_and_arguments(ctx, &proto, func_specs).await? - { - socket_spec_builder.func_unique_id(func_unique_id); - inputs.drain(..).for_each(|input| { - socket_spec_builder.input(input); - }); + if let SocketKind::Frame = socket.kind() { + continue; } + + let mut socket_spec_builder = SocketSpec::builder(); + socket_spec_builder + .name(output_socket_ep.name()) + .unique_id(output_socket_ep.id().to_string()); + + let mut data_builder = SocketSpecData::builder(); + data_builder + .name(output_socket_ep.name()) + .kind(SocketSpecKind::Output) + .arity(socket.arity()) + .ui_hidden(socket.ui_hidden()); + + let mut has_custom_func = false; + if let Some(attr_proto_id) = output_socket_ep.attribute_prototype_id() { + let proto = AttributePrototype::get_by_id(ctx, attr_proto_id) + .await? + .ok_or(PkgError::MissingAttributePrototypeForOutputSocket( + *attr_proto_id, + *output_socket_ep.id(), + ))?; + + if let Some((func_unique_id, mut inputs)) = self + .export_input_func_and_arguments(ctx, change_set_pk, &proto) + .await? + { + has_custom_func = true; + data_builder.func_unique_id(func_unique_id); + inputs.drain(..).for_each(|input| { + socket_spec_builder.input(input); + }); + } + } + + if std_model_change_set_matches(change_set_pk, &socket) || has_custom_func { + socket_spec_builder.data(data_builder.build()?); + } + + specs.push(socket_spec_builder.build()?); } - specs.push(socket_spec_builder.build()?); + Ok(specs) } - Ok(specs) -} + async fn export_action_funcs( + &self, + ctx: &DalContext, + change_set_pk: Option, + variant_id: SchemaVariantId, + ) -> PkgResult> { + let mut specs = vec![]; + + let action_prototypes = ActionPrototype::find_for_context( + ctx, + ActionPrototypeContext { + schema_variant_id: variant_id, + }, + ) + .await?; -pub async fn get_component_type( - ctx: &DalContext, - variant: &SchemaVariant, -) -> Result { - let type_prop = variant.find_prop(ctx, &["root", "si", "type"]).await?; - let type_context = AttributeReadContext { - prop_id: Some(*type_prop.id()), - ..Default::default() - }; + for action_proto in action_prototypes { + if !std_model_change_set_matches(change_set_pk, &action_proto) { + continue; + } - let type_av = AttributeValue::find_for_context(ctx, type_context) - .await? - .ok_or(SchemaVariantError::AttributeValueNotFoundForContext( - type_context, - ))?; + let func_spec = self + .func_map + .get(change_set_pk, action_proto.func_id()) + .ok_or(PkgError::MissingExportedFunc(action_proto.func_id()))?; - Ok(match type_av.get_value(ctx).await? { - Some(type_value) => { - let component_type: ComponentType = serde_json::from_value(type_value)?; - component_type.into() + specs.push( + ActionFuncSpec::builder() + .kind(action_proto.kind()) + .func_unique_id(&func_spec.unique_id) + .unique_id(action_proto.id().to_string()) + .deleted(action_proto.visibility().is_deleted()) + .build()?, + ) } - None => SchemaVariantSpecComponentType::default(), - }) -} -async fn build_action_func_specs( - ctx: &DalContext, - schema_variant_id: SchemaVariantId, - func_specs: &FuncSpecMap, -) -> PkgResult> { - let mut specs = vec![]; - - let action_prototypes = - ActionPrototype::find_for_context(ctx, ActionPrototypeContext { schema_variant_id }) - .await?; - - for action_proto in action_prototypes { - let func_spec = func_specs - .get(&action_proto.func_id()) - .ok_or(PkgError::MissingExportedFunc(action_proto.func_id()))?; - - specs.push( - ActionFuncSpec::builder() - .kind(action_proto.kind()) - .func_unique_id(func_spec.unique_id) - .build()?, - ) + Ok(specs) } - Ok(specs) -} + async fn export_prop_tree( + &self, + ctx: &DalContext, + change_set_pk: Option, + variant: &SchemaVariant, + variant_spec: &mut SchemaVariantSpecBuilder, + prop_root: SchemaVariantSpecPropRoot, + ) -> PkgResult<()> { + let mut prop_tree = PropTree::new(ctx, true, Some(vec![*variant.id()]), None).await?; + let root_tree_node = prop_tree + .root_props + .pop() + .ok_or_else(|| PkgError::prop_tree_invalid("root prop not found"))?; + if !prop_tree.root_props.is_empty() { + return Err(PkgError::prop_tree_invalid( + "prop tree contained multiple root props", + )); + } + let prop_root_tree_node = match root_tree_node.children.into_iter().find(|tree_node| { + match prop_root { + SchemaVariantSpecPropRoot::Domain => { + tree_node.name == "domain" && tree_node.path == "/root/" + } + SchemaVariantSpecPropRoot::ResourceValue => { + tree_node.name == "resource_value" && tree_node.path == "/root/" + } + } + }) { + Some(root_tree_node) => root_tree_node, + None => { + if matches!(prop_root, SchemaVariantSpecPropRoot::Domain) { + return Err(PkgError::prop_tree_invalid("domain prop not found")); + } else { + warn!("/root/resource_value prop not found, if value prop PR has merged, this should be an error not a warning."); + return Ok(()); + } + } + }; -async fn build_si_prop_func_specs( - ctx: &DalContext, - variant: &SchemaVariant, - func_specs: &FuncSpecMap, -) -> PkgResult> { - let mut specs = vec![]; + #[derive(Debug)] + struct TraversalStackEntry { + builder: PropSpecBuilder, + prop_id: PropId, + parent_prop_id: Option, + inside_map_or_array: bool, + } - for kind in SiPropFuncSpecKind::iter() { - let prop = variant.find_prop(ctx, &kind.prop_path()).await?; + let mut stack: Vec<(PropTreeNode, Option, bool)> = Vec::new(); + for child_tree_node in prop_root_tree_node.children { + stack.push((child_tree_node, None, false)); + } - let context = AttributeContextBuilder::new() - .set_prop_id(*prop.id()) - .to_context()?; + let mut traversal_stack: Vec = Vec::new(); - if let Some(prototype) = AttributePrototype::find_for_context_and_key(ctx, context, &None) - .await? - .pop() - { - if let Some((func_unique_id, mut inputs)) = - build_input_func_and_arguments(ctx, &prototype, func_specs).await? - { - let mut builder = SiPropFuncSpec::builder(); - builder.func_unique_id(func_unique_id); - builder.kind(kind); - inputs.drain(..).for_each(|input| { - builder.input(input); - }); + while let Some((tree_node, parent_prop_id, inside_map_or_array)) = stack.pop() { + let prop_id = tree_node.prop_id; + let mut builder = PropSpec::builder(); - specs.push(builder.build()?); + if !change_set_matches(change_set_pk, tree_node.visibility_change_set_pk) { + builder.has_data(false); } - } - } - Ok(specs) -} - -async fn build_variant_spec( - ctx: &DalContext, - variant: SchemaVariant, - func_specs: &FuncSpecMap, -) -> PkgResult { - let mut variant_spec_builder = SchemaVariantSpec::builder(); - variant_spec_builder.name(variant.name()); - if let Some(color_str) = variant.color(ctx).await? { - variant_spec_builder.color(color_str); - }; - if let Some(link) = variant.link() { - variant_spec_builder.try_link(link)?; - } + builder + .name(tree_node.name) + .unique_id(prop_id) + .kind(match tree_node.kind { + PropKind::Array => PropSpecKind::Array, + PropKind::Boolean => PropSpecKind::Boolean, + PropKind::Integer => PropSpecKind::Number, + PropKind::Object => PropSpecKind::Object, + PropKind::String => PropSpecKind::String, + PropKind::Map => PropSpecKind::Map, + }) + .hidden(tree_node.hidden) + .widget_kind(tree_node.widget_kind) + .widget_options(tree_node.widget_options); + + if let Some(doc_link) = tree_node.doc_link { + builder.try_doc_link(doc_link.as_str())?; + } - variant_spec_builder.component_type(get_component_type(ctx, &variant).await?); - - set_variant_spec_prop_data( - ctx, - &variant, - &mut variant_spec_builder, - SchemaVariantSpecPropRoot::Domain, - func_specs, - ) - .await?; - set_variant_spec_prop_data( - ctx, - &variant, - &mut variant_spec_builder, - SchemaVariantSpecPropRoot::ResourceValue, - func_specs, - ) - .await?; - - build_leaf_function_specs(ctx, *variant.id(), func_specs) - .await? - .drain(..) - .for_each(|leaf_func_spec| { - variant_spec_builder.leaf_function(leaf_func_spec); - }); + traversal_stack.push(TraversalStackEntry { + builder, + prop_id, + parent_prop_id, + inside_map_or_array, + }); + + for child_tree_node in tree_node.children { + stack.push(( + child_tree_node, + Some(prop_id), + matches!(tree_node.kind, PropKind::Array | PropKind::Map) + || inside_map_or_array, + )); + } + } - build_socket_specs(ctx, *variant.id(), func_specs) - .await? - .drain(..) - .for_each(|socket_spec| { - variant_spec_builder.socket(socket_spec); - }); + let mut prop_children_map: HashMap> = HashMap::new(); + + while let Some(mut entry) = traversal_stack.pop() { + let mut maybe_type_prop_id: Option = None; + + if let Some(mut prop_children) = prop_children_map.remove(&entry.prop_id) { + match entry.builder.get_kind() { + Some(kind) => match kind { + PropSpecKind::Object => { + entry.builder.entries( + prop_children + .iter() + .map(|(prop_spec, _)| prop_spec.to_owned()) + .collect(), + ); + } + PropSpecKind::Map | PropSpecKind::Array => { + let (type_prop, type_prop_id) = + prop_children.pop().ok_or_else(|| { + PkgError::prop_spec_children_invalid(format!( + "found no child for map/array for prop id {}", + entry.prop_id, + )) + })?; + if !prop_children.is_empty() { + return Err(PkgError::prop_spec_children_invalid(format!( + "found multiple children for map/array for prop id {}", + entry.prop_id, + ))); + } + entry.builder.type_prop(type_prop); + maybe_type_prop_id = Some(type_prop_id); + } + PropSpecKind::String | PropSpecKind::Number | PropSpecKind::Boolean => { + return Err(PkgError::prop_spec_children_invalid(format!( + "primitve prop type should have no children for prop id {}", + entry.prop_id, + ))); + } + }, + None => { + return Err(SpecError::UninitializedField("kind").into()); + } + }; + } - build_action_func_specs(ctx, *variant.id(), func_specs) - .await? - .drain(..) - .for_each(|action_func_spec| { - variant_spec_builder.action_func(action_func_spec); - }); + if matches!(entry.builder.get_kind(), Some(PropSpecKind::Map)) { + if let Some(type_prop_id) = maybe_type_prop_id { + let context = AttributeContextBuilder::new() + .set_prop_id(type_prop_id) + .to_context()?; + + for proto in AttributePrototype::list_for_context(ctx, context).await? { + if let Some(key) = proto.key() { + if let Some((func_unique_id, mut inputs)) = self + .export_input_func_and_arguments(ctx, change_set_pk, &proto) + .await? + { + let mut map_key_func_builder = MapKeyFuncSpec::builder(); + map_key_func_builder.key(key); + map_key_func_builder.func_unique_id(func_unique_id); + inputs.drain(..).for_each(|input| { + map_key_func_builder.input(input); + }); + entry.builder.map_key_func(map_key_func_builder.build()?); + } + } + } + } + } - build_si_prop_func_specs(ctx, &variant, func_specs) - .await? - .drain(..) - .for_each(|si_prop_func_spec| { - variant_spec_builder.si_prop_func(si_prop_func_spec); - }); + // TODO: if we get funcs here but we also got map_key_funcs above, that's a sign of a + // TODO: misconfigured set of attribute prototypes. check and error + let context = AttributeContextBuilder::new() + .set_prop_id(entry.prop_id) + .to_context()?; - let schema_variant_definition = - SchemaVariantDefinition::get_by_schema_variant_id(ctx, variant.id()) - .await? - .ok_or(PkgError::MissingSchemaVariantDefinition(*variant.id()))?; + if let Some(prototype) = + AttributePrototype::find_for_context_and_key(ctx, context, &None) + .await? + .pop() + { + if std_model_change_set_matches(change_set_pk, &prototype) { + entry.builder.has_data(true); + } - let asset_func_unique_id = func_specs - .get(&schema_variant_definition.func_id()) - .ok_or(PkgError::MissingExportedFunc( - schema_variant_definition.func_id(), - ))? - .unique_id; + if let Some((func_unique_id, mut inputs)) = self + .export_input_func_and_arguments(ctx, change_set_pk, &prototype) + .await? + { + entry.builder.has_data(true); - variant_spec_builder.func_unique_id(asset_func_unique_id); + entry.builder.func_unique_id(func_unique_id); + inputs.drain(..).for_each(|input| { + entry.builder.input(input); + }); + } + } - let variant_spec = variant_spec_builder.build()?; + // TODO: handle default values for complex types. We also cannot set default values for + // children of arrays and maps, at any depth (currently), since that requires tracking the + // key or index + if matches!( + entry.builder.get_kind(), + Some(PropSpecKind::String) + | Some(PropSpecKind::Number) + | Some(PropSpecKind::Boolean) + ) && !entry.inside_map_or_array + { + if let Some(av) = AttributeValue::find_for_context(ctx, context.into()).await? { + if let Some(default_value) = av.get_value(ctx).await? { + entry.builder.has_data(true); + entry.builder.default_value(default_value); + } + } + } - Ok(variant_spec) -} + for validation in self + .export_validations_for_prop(ctx, change_set_pk, entry.prop_id) + .await? + { + entry.builder.validation(validation); + } -async fn get_schema_and_variant( - ctx: &DalContext, - variant_id: SchemaVariantId, -) -> PkgResult<(SchemaVariant, Schema)> { - let variant = SchemaVariant::get_by_id(ctx, &variant_id) - .await? - .ok_or_else(|| { - StandardModelError::ModelMissing("schema_variants".to_string(), variant_id.to_string()) - })?; - - let schema = variant.schema(ctx).await?.ok_or_else(|| { - PkgError::StandardModelMissingBelongsTo( - "schema_variant_belongs_to_schema", - "schema_variant", - variant_id.to_string(), - ) - })?; + let prop_spec = entry.builder.build()?; - Ok((variant, schema)) -} + match entry.parent_prop_id { + None => { + variant_spec.prop(prop_root, prop_spec); + } + Some(parent_prop_id) => { + match prop_children_map.entry(parent_prop_id) { + Entry::Occupied(mut occupied) => { + occupied.get_mut().push((prop_spec, entry.prop_id)); + } + Entry::Vacant(vacant) => { + vacant.insert(vec![(prop_spec, entry.prop_id)]); + } + }; + } + }; + } -async fn set_schema_spec_category_data( - ctx: &DalContext, - schema: &Schema, - schema_spec_builder: &mut si_pkg::SchemaSpecBuilder, -) -> PkgResult<()> { - let mut schema_ui_menus = schema.ui_menus(ctx).await?; - let schema_ui_menu = schema_ui_menus.pop().ok_or_else(|| { - PkgError::StandardModelMissingBelongsTo( - "schema_ui_menu_belongs_to_schema", - "schema", - (*schema.id()).to_string(), - ) - })?; - if !schema_ui_menus.is_empty() { - return Err(PkgError::StandardModelMultipleBelongsTo( - "schema_ui_menu_belongs_to_schema", - "schema", - (*schema.id()).to_string(), - )); + Ok(()) } - schema_spec_builder.category(schema_ui_menu.category()); - schema_spec_builder.category_name(schema_ui_menu.name()); + async fn export_input_func_and_arguments( + &self, + ctx: &DalContext, + change_set_pk: Option, + proto: &AttributePrototype, + ) -> PkgResult)>> { + let proto_func = Func::get_by_id(ctx, &proto.func_id()).await?.ok_or( + PkgError::MissingAttributePrototypeFunc(*proto.id(), proto.func_id()), + )?; + + let apas: Vec = + AttributePrototypeArgument::list_for_attribute_prototype(ctx, *proto.id()) + .await? + .into_iter() + .filter(|apa| std_model_change_set_matches(change_set_pk, apa)) + .collect(); + + // If the prototype func is intrinsic and has no arguments, it's one that is created by default + // and we don't have to track it in the package + if apas.is_empty() && proto_func.is_intrinsic() { + return Ok(None); + } - Ok(()) -} + let mut inputs = vec![]; -async fn set_variant_spec_prop_data( - ctx: &DalContext, - variant: &SchemaVariant, - variant_spec: &mut SchemaVariantSpecBuilder, - prop_root: SchemaVariantSpecPropRoot, - func_specs: &HashMap, -) -> PkgResult<()> { - let mut prop_tree = PropTree::new(ctx, true, Some(vec![*variant.id()]), None).await?; - let root_tree_node = prop_tree - .root_props - .pop() - .ok_or_else(|| PkgError::prop_tree_invalid("root prop not found"))?; - if !prop_tree.root_props.is_empty() { - return Err(PkgError::prop_tree_invalid( - "prop tree contained multiple root props", - )); - } - let prop_root_tree_node = match root_tree_node.children.into_iter().find(|tree_node| { - match prop_root { - SchemaVariantSpecPropRoot::Domain => { - tree_node.name == "domain" && tree_node.path == "/root/" - } - SchemaVariantSpecPropRoot::ResourceValue => { - tree_node.name == "resource_value" && tree_node.path == "/root/" - } - } - }) { - Some(root_tree_node) => root_tree_node, - None => { - if matches!(prop_root, SchemaVariantSpecPropRoot::Domain) { - return Err(PkgError::prop_tree_invalid("domain prop not found")); - } else { - warn!("/root/resource_value prop not found, if value prop PR has merged, this should be an error not a warning."); - return Ok(()); + for apa in &apas { + let func_arg = FuncArgument::get_by_id(ctx, &apa.func_argument_id()) + .await? + .ok_or(PkgError::AttributePrototypeArgumentMissingFuncArgument( + *apa.id(), + apa.func_argument_id(), + ))?; + let arg_name = func_arg.name(); + + let mut builder = AttrFuncInputSpec::builder(); + builder + .name(arg_name) + .unique_id(apa.id().to_string()) + .deleted(apa.visibility().is_deleted()); + + if apa.internal_provider_id() != InternalProviderId::NONE { + let ip = InternalProvider::get_by_id(ctx, &apa.internal_provider_id()) + .await? + .ok_or(PkgError::AttributePrototypeArgumentMissingInternalProvider( + *apa.id(), + apa.internal_provider_id(), + ))?; + + match *ip.prop_id() { + PropId::NONE => { + inputs.push( + AttrFuncInputSpec::builder() + .name(arg_name) + .kind(AttrFuncInputSpecKind::InputSocket) + .socket_name(ip.name()) + .build()?, + ); + } + prop_id => { + let prop = Prop::get_by_id(ctx, &prop_id) + .await? + .ok_or(PkgError::InternalProviderMissingProp(*ip.id(), prop_id))?; + + inputs.push( + AttrFuncInputSpec::builder() + .name(arg_name) + .kind(AttrFuncInputSpecKind::Prop) + .prop_path(prop.path()) + .build()?, + ); + } + } + } else if apa.external_provider_id() != ExternalProviderId::NONE { + let ep = ExternalProvider::get_by_id(ctx, &apa.external_provider_id()) + .await? + .ok_or(PkgError::AttributePrototypeArgumentMissingExternalProvider( + *apa.id(), + apa.external_provider_id(), + ))?; + + inputs.push( + AttrFuncInputSpec::builder() + .name(arg_name) + .kind(AttrFuncInputSpecKind::OutputSocket) + .socket_name(ep.name()) + .build()?, + ); } } - }; - #[derive(Debug)] - struct TraversalStackEntry { - builder: PropSpecBuilder, - prop_id: PropId, - parent_prop_id: Option, - inside_map_or_array: bool, - } + let func_spec = self + .func_map + .get(change_set_pk, *proto_func.id()) + .ok_or(PkgError::MissingExportedFunc(*proto_func.id()))?; + + let func_unique_id = func_spec.unique_id.to_owned(); - let mut stack: Vec<(PropTreeNode, Option, bool)> = Vec::new(); - for child_tree_node in prop_root_tree_node.children { - stack.push((child_tree_node, None, false)); + Ok(Some((func_unique_id, inputs))) } - let mut traversal_stack: Vec = Vec::new(); - - while let Some((tree_node, parent_prop_id, inside_map_or_array)) = stack.pop() { - let prop_id = tree_node.prop_id; - let mut builder = PropSpec::builder(); - builder - .kind(match tree_node.kind { - PropKind::Array => PropSpecKind::Array, - PropKind::Boolean => PropSpecKind::Boolean, - PropKind::Integer => PropSpecKind::Number, - PropKind::Object => PropSpecKind::Object, - PropKind::String => PropSpecKind::String, - PropKind::Map => PropSpecKind::Map, - }) - .name(tree_node.name) - .hidden(tree_node.hidden) - .widget_kind(tree_node.widget_kind) - .widget_options(tree_node.widget_options); + async fn export_validations_for_prop( + &self, + ctx: &DalContext, + change_set_pk: Option, + prop_id: PropId, + ) -> PkgResult> { + let mut validation_specs = vec![]; - if let Some(doc_link) = tree_node.doc_link { - builder.try_doc_link(doc_link.as_str())?; - } + let validation_prototypes = ValidationPrototype::list_for_prop(ctx, prop_id).await?; - traversal_stack.push(TraversalStackEntry { - builder, - prop_id, - parent_prop_id, - inside_map_or_array, - }); - - for child_tree_node in tree_node.children { - stack.push(( - child_tree_node, - Some(prop_id), - matches!(tree_node.kind, PropKind::Array | PropKind::Map) || inside_map_or_array, - )); - } - } + for prototype in &validation_prototypes { + if !std_model_change_set_matches(change_set_pk, prototype) { + continue; + } - let mut prop_children_map: HashMap> = HashMap::new(); + let mut spec_builder = ValidationSpec::builder(); - while let Some(mut entry) = traversal_stack.pop() { - let mut maybe_type_prop_id: Option = None; + spec_builder.unique_id(prototype.id().to_string()); + if prototype.visibility().is_deleted() { + spec_builder.deleted(true); + } - if let Some(mut prop_children) = prop_children_map.remove(&entry.prop_id) { - match entry.builder.get_kind() { - Some(kind) => match kind { - PropSpecKind::Object => { - entry.builder.entries( - prop_children - .iter() - .map(|(prop_spec, _)| prop_spec.to_owned()) - .collect(), - ); + let args: Option = + serde_json::from_value(prototype.args().clone())?; + + match args { + Some(validation) => match validation.validation { + Validation::IntegerIsBetweenTwoIntegers { + lower_bound, + upper_bound, + .. + } => { + spec_builder.kind(ValidationSpecKind::IntegerIsBetweenTwoIntegers); + spec_builder.upper_bound(upper_bound); + spec_builder.lower_bound(lower_bound); } - PropSpecKind::Map | PropSpecKind::Array => { - let (type_prop, type_prop_id) = prop_children.pop().ok_or_else(|| { - PkgError::prop_spec_children_invalid(format!( - "found no child for map/array for prop id {}", - entry.prop_id, - )) - })?; - if !prop_children.is_empty() { - return Err(PkgError::prop_spec_children_invalid(format!( - "found multiple children for map/array for prop id {}", - entry.prop_id, - ))); - } - entry.builder.type_prop(type_prop); - maybe_type_prop_id = Some(type_prop_id); + Validation::IntegerIsNotEmpty { .. } => { + spec_builder.kind(ValidationSpecKind::IntegerIsNotEmpty); + } + Validation::StringHasPrefix { expected, .. } => { + spec_builder.kind(ValidationSpecKind::StringHasPrefix); + spec_builder.expected_string(expected); + } + Validation::StringEquals { expected, .. } => { + spec_builder.kind(ValidationSpecKind::StringEquals); + spec_builder.expected_string(expected); } - PropSpecKind::String | PropSpecKind::Number | PropSpecKind::Boolean => { - return Err(PkgError::prop_spec_children_invalid(format!( - "primitve prop type should have no children for prop id {}", - entry.prop_id, - ))); + Validation::StringInStringArray { + expected, + display_expected, + .. + } => { + spec_builder.kind(ValidationSpecKind::StringInStringArray); + spec_builder.expected_string_array(expected); + spec_builder.display_expected(display_expected); + } + Validation::StringIsNotEmpty { .. } => { + spec_builder.kind(ValidationSpecKind::StringIsNotEmpty); + } + Validation::StringIsValidIpAddr { .. } => { + spec_builder.kind(ValidationSpecKind::StringIsValidIpAddr); + } + Validation::StringIsHexColor { .. } => { + spec_builder.kind(ValidationSpecKind::StringIsHexColor); } }, None => { - return Err(SpecError::UninitializedField("kind").into()); - } - }; - } + let func_spec = self + .func_map + .get(change_set_pk, prototype.func_id()) + .ok_or(PkgError::MissingExportedFunc(prototype.func_id()))?; - if matches!(entry.builder.get_kind(), Some(PropSpecKind::Map)) { - if let Some(type_prop_id) = maybe_type_prop_id { - let context = AttributeContextBuilder::new() - .set_prop_id(type_prop_id) - .to_context()?; - - for proto in AttributePrototype::list_for_context(ctx, context).await? { - if let Some(key) = proto.key() { - if let Some((func_unique_id, mut inputs)) = - build_input_func_and_arguments(ctx, &proto, func_specs).await? - { - let mut map_key_func_builder = MapKeyFuncSpec::builder(); - map_key_func_builder.key(key); - map_key_func_builder.func_unique_id(func_unique_id); - inputs.drain(..).for_each(|input| { - map_key_func_builder.input(input); - }); - entry.builder.map_key_func(map_key_func_builder.build()?); - } - } + spec_builder.kind(ValidationSpecKind::CustomValidation); + spec_builder.func_unique_id(&func_spec.unique_id); } } + + validation_specs.push(spec_builder.build()?); + } + + Ok(validation_specs) + } + + async fn export_func( + &self, + ctx: &DalContext, + change_set_pk: Option, + func: &Func, + ) -> PkgResult> { + let mut func_spec_builder = FuncSpec::builder(); + + func_spec_builder.name(func.name()); + func_spec_builder.unique_id(func.id().to_string()); + + let in_change_set = std_model_change_set_matches(change_set_pk, func); + + if in_change_set && func.visibility().is_deleted() { + func_spec_builder.deleted(true); + return Ok(Some(func_spec_builder.build()?)); } - // TODO: if we get funcs here but we also got map_key_funcs above, that's a sign of a - // TODO: misconfigured set of attribute prototypes. check and error - let context = AttributeContextBuilder::new() - .set_prop_id(entry.prop_id) - .to_context()?; + if in_change_set { + let mut data_builder = FuncSpecData::builder(); - if let Some(prototype) = AttributePrototype::find_for_context_and_key(ctx, context, &None) - .await? - .pop() - { - if let Some((func_unique_id, mut inputs)) = - build_input_func_and_arguments(ctx, &prototype, func_specs).await? - { - entry.builder.func_unique_id(func_unique_id); - inputs.drain(..).for_each(|input| { - entry.builder.input(input); - }); + data_builder.name(func.name()); + + if let Some(display_name) = func.display_name() { + data_builder.display_name(display_name); } - } - // TODO: handle default values for complex types. We also cannot set default values for - // children of arrays and maps, at any depth (currently), since that requires tracking the - // key or index - if matches!( - entry.builder.get_kind(), - Some(PropSpecKind::String) | Some(PropSpecKind::Number) | Some(PropSpecKind::Boolean) - ) && !entry.inside_map_or_array - { - if let Some(av) = AttributeValue::find_for_context(ctx, context.into()).await? { - if let Some(default_value) = av.get_value(ctx).await? { - entry.builder.default_value(default_value); - } + if let Some(description) = func.description() { + data_builder.description(description); } + + if let Some(link) = func.link() { + data_builder.try_link(link)?; + } + // Should we package an empty func? + data_builder.handler(func.handler().unwrap_or("")); + data_builder.code_base64(func.code_base64().unwrap_or("")); + + data_builder.response_type(*func.backend_response_type()); + data_builder.backend_kind(*func.backend_kind()); + + data_builder.hidden(func.hidden()); + + func_spec_builder.data(data_builder.build()?); } - for validation in get_validations_for_prop(ctx, entry.prop_id, func_specs).await? { - entry.builder.validation(validation); + let args: Vec = FuncArgument::list_for_func(ctx, *func.id()) + .await? + .into_iter() + .filter(|f| std_model_change_set_matches(change_set_pk, f)) + .collect(); + + for arg in &args { + func_spec_builder.argument( + FuncArgumentSpec::builder() + .name(arg.name()) + .kind(*arg.kind()) + .element_kind(arg.element_kind().cloned().map(|kind| kind.into())) + .unique_id(arg.id().to_string()) + .deleted(arg.visibility().is_deleted()) + .build()?, + ); } - let prop_spec = entry.builder.build()?; + let func_spec = func_spec_builder.build()?; - match entry.parent_prop_id { - None => { - variant_spec.prop(prop_root, prop_spec); - } - Some(parent_prop_id) => { - match prop_children_map.entry(parent_prop_id) { - Entry::Occupied(mut occupied) => { - occupied.get_mut().push((prop_spec, entry.prop_id)); - } - Entry::Vacant(vacant) => { - vacant.insert(vec![(prop_spec, entry.prop_id)]); - } - }; + // If we have data, or change set specific arguments, we're valid for this changeset + Ok(if func_spec.data.is_some() || !args.is_empty() { + Some(func_spec) + } else { + None + }) + } + + /// If change_set_pk is None, we export everything in the changeset without checking for + /// differences from HEAD. Otherwise we attempt to only export the data specific to the + /// requested change_set + async fn export_change_set( + &mut self, + ctx: &DalContext, + change_set_pk: Option, + ) -> PkgResult<(Vec, Vec)> { + let mut func_specs = vec![]; + let mut schema_specs = vec![]; + + let new_ctx = match change_set_pk { + None => ctx.clone(), + Some(change_set_pk) => { + ctx.clone_with_new_visibility(ctx.visibility().to_change_set_deleted(change_set_pk)) } }; + let ctx = &new_ctx; + + self.func_map.init_change_set_map(change_set_pk); + + // Intrinsic funcs should be immutable. They're not, but we don't provide any interfaces to + // modify them via a the standard model. We only add them to the func map if the func map + // is HEAD (or if we're doing a module export) + if change_set_pk.unwrap_or(ChangeSetPk::NONE) == ChangeSetPk::NONE { + for intrinsic in crate::func::intrinsics::IntrinsicFunc::iter() { + let intrinsic_name = intrinsic.name(); + // We need a unique id for intrinsic funcs to refer to them in custom bindings (for example + // mapping one prop to another via si:identity) + let intrinsic_func = Func::find_by_name(ctx, intrinsic_name) + .await? + .ok_or(PkgError::MissingIntrinsicFunc(intrinsic_name.to_string()))?; + + let intrinsic_spec = intrinsic.to_spec()?; + self.func_map + .insert(change_set_pk, *intrinsic_func.id(), intrinsic_spec.clone()); + + func_specs.push(intrinsic_spec); + } + } + + // XXX: make this SQL query + let schemas: Vec = Schema::list(ctx) + .await? + .into_iter() + .filter(|sv| { + if let Some(schema_ids) = &self.schema_ids { + schema_ids.contains(sv.id()) + } else { + true + } + }) + .collect(); + + for schema in &schemas { + let (schema_spec, funcs) = self.export_schema(ctx, change_set_pk, schema).await?; + + func_specs.extend_from_slice(&funcs); + schema_specs.push(schema_spec); + } + + Ok((func_specs, schema_specs)) } - Ok(()) -} + pub async fn export(&mut self, ctx: &DalContext) -> PkgResult { + let mut pkg_spec_builder = PkgSpec::builder(); + pkg_spec_builder + .name(&self.name) + .kind(self.kind) + .version(&self.version) + .created_by(&self.created_by); -async fn get_validations_for_prop( - ctx: &DalContext, - prop_id: PropId, - func_specs: &HashMap, -) -> PkgResult> { - let mut validation_specs = vec![]; - - for prototype in ValidationPrototype::list_for_prop(ctx, prop_id).await? { - let mut spec_builder = ValidationSpec::builder(); - let args: Option = - serde_json::from_value(prototype.args().clone())?; - - match args { - Some(validation) => match validation.validation { - Validation::IntegerIsBetweenTwoIntegers { - lower_bound, - upper_bound, - .. - } => { - spec_builder.kind(ValidationSpecKind::IntegerIsBetweenTwoIntegers); - spec_builder.upper_bound(upper_bound); - spec_builder.lower_bound(lower_bound); - } - Validation::IntegerIsNotEmpty { .. } => { - spec_builder.kind(ValidationSpecKind::IntegerIsNotEmpty); - } - Validation::StringHasPrefix { expected, .. } => { - spec_builder.kind(ValidationSpecKind::StringHasPrefix); - spec_builder.expected_string(expected); - } - Validation::StringEquals { expected, .. } => { - spec_builder.kind(ValidationSpecKind::StringEquals); - spec_builder.expected_string(expected); - } - Validation::StringInStringArray { - expected, - display_expected, - .. - } => { - spec_builder.kind(ValidationSpecKind::StringInStringArray); - spec_builder.expected_string_array(expected); - spec_builder.display_expected(display_expected); - } - Validation::StringIsNotEmpty { .. } => { - spec_builder.kind(ValidationSpecKind::StringIsNotEmpty); - } - Validation::StringIsValidIpAddr { .. } => { - spec_builder.kind(ValidationSpecKind::StringIsValidIpAddr); - } - Validation::StringIsHexColor { .. } => { - spec_builder.kind(ValidationSpecKind::StringIsHexColor); - } - }, - None => { - let func_spec = func_specs - .get(&prototype.func_id()) - .ok_or(PkgError::MissingExportedFunc(prototype.func_id()))?; + if let Some(workspace_pk) = ctx.tenancy().workspace_pk() { + pkg_spec_builder.workspace_pk(workspace_pk.to_string()); + } + + if let Some(description) = &self.description { + pkg_spec_builder.description(description); + } - spec_builder.kind(ValidationSpecKind::CustomValidation); - spec_builder.func_unique_id(func_spec.unique_id); + match self.kind { + SiPkgKind::Module => { + let (funcs, schemas) = self.export_change_set(ctx, None).await?; + pkg_spec_builder.funcs(funcs); + pkg_spec_builder.schemas(schemas); + } + SiPkgKind::WorkspaceBackup => { + let (funcs, schemas) = self.export_change_set(ctx, Some(ChangeSetPk::NONE)).await?; + pkg_spec_builder.change_set( + ChangeSetSpec::builder() + .name("head") + .funcs(funcs) + .schemas(schemas) + .build()?, + ); + pkg_spec_builder.default_change_set("head"); + + for change_set in ChangeSet::list_open(ctx).await? { + let (funcs, schemas) = self.export_change_set(ctx, Some(change_set.pk)).await?; + + pkg_spec_builder.change_set( + ChangeSetSpec::builder() + .name(&change_set.name) + .based_on_change_set("head") + .funcs(funcs) + .schemas(schemas) + .build()?, + ); + } } } - validation_specs.push(spec_builder.build()?); + let spec = pkg_spec_builder.build()?; + let pkg = SiPkg::load_from_spec(spec)?; + + Ok(pkg) } +} - Ok(validation_specs) +pub async fn get_component_type( + ctx: &DalContext, + variant: &SchemaVariant, +) -> Result { + let type_prop = variant.find_prop(ctx, &["root", "si", "type"]).await?; + let type_context = AttributeReadContext { + prop_id: Some(*type_prop.id()), + ..Default::default() + }; + + let type_av = AttributeValue::find_for_context(ctx, type_context) + .await? + .ok_or(SchemaVariantError::AttributeValueNotFoundForContext( + type_context, + ))?; + + Ok(match type_av.get_value(ctx).await? { + Some(type_value) => { + let component_type: ComponentType = serde_json::from_value(type_value)?; + component_type.into() + } + None => SchemaVariantSpecComponentType::default(), + }) } diff --git a/lib/dal/src/pkg/import.rs b/lib/dal/src/pkg/import.rs index 75d620b2b9..e4bbc71bcd 100644 --- a/lib/dal/src/pkg/import.rs +++ b/lib/dal/src/pkg/import.rs @@ -3,14 +3,14 @@ use telemetry::prelude::*; use tokio::sync::Mutex; use si_pkg::{ - FuncUniqueId, SchemaVariantSpecPropRoot, SiPkg, SiPkgActionFunc, SiPkgAttrFuncInputView, - SiPkgError, SiPkgFunc, SiPkgLeafFunction, SiPkgProp, SiPkgSchema, SiPkgSchemaVariant, - SiPkgSocket, SiPkgValidation, SocketSpecKind, + SchemaVariantSpecPropRoot, SiPkg, SiPkgActionFunc, SiPkgAttrFuncInputView, SiPkgError, + SiPkgFunc, SiPkgLeafFunction, SiPkgProp, SiPkgSchema, SiPkgSchemaVariant, SiPkgSocket, + SiPkgValidation, SocketSpecKind, }; use crate::{ component::ComponentKind, - func::{binding::FuncBinding, binding_return_value::FuncBindingReturnValue}, + func::{self, binding::FuncBinding, binding_return_value::FuncBindingReturnValue}, installed_pkg::{ InstalledPkg, InstalledPkgAsset, InstalledPkgAssetKind, InstalledPkgAssetTyped, InstalledPkgId, @@ -31,7 +31,7 @@ use crate::{ use super::{PkgError, PkgResult}; -type FuncMap = std::collections::HashMap; +type FuncMap = std::collections::HashMap; #[derive(Clone, Debug, Default)] pub struct ImportOptions { @@ -76,33 +76,43 @@ pub async fn import_pkg_from_pkg( info!( "installing function '{}' from {}", func_spec.name(), - file_name + file_name, ); - let unique_id = func_spec.unique_id(); - - let func = if let Some(Some(func)) = options - .skip_import_funcs - .as_ref() - .map(|skip_funcs| skip_funcs.get(&unique_id)) - { - if let Some(installed_pkg_id) = installed_pkg_id { - InstalledPkgAsset::new( - ctx, - InstalledPkgAssetTyped::new_for_func( - *func.id(), - installed_pkg_id, - func_spec.hash().to_string(), - ), - ) - .await?; - } - - func.to_owned() + let unique_id = func_spec.unique_id().to_string(); + + // This is a hack because the hash of the intrinsics has changed from the version in the + // packages + if func::is_intrinsic(func_spec.name()) { + let func = if let Some(func) = Func::find_by_name(ctx, func_spec.name()).await? { + func + } else { + create_func(ctx, func_spec, installed_pkg_id).await? + }; + funcs_by_unique_id.insert(unique_id, func); } else { - create_func(ctx, func_spec, installed_pkg_id).await? - }; + let func = if let Some(Some(func)) = options + .skip_import_funcs + .as_ref() + .map(|skip_funcs| skip_funcs.get(&unique_id)) + { + if let Some(installed_pkg_id) = installed_pkg_id { + InstalledPkgAsset::new( + ctx, + InstalledPkgAssetTyped::new_for_func( + *func.id(), + installed_pkg_id, + func_spec.hash().to_string(), + ), + ) + .await?; + } - funcs_by_unique_id.insert(unique_id, func); + func.to_owned() + } else { + create_func(ctx, func_spec, installed_pkg_id).await? + }; + funcs_by_unique_id.insert(unique_id, func); + } } let mut installed_schema_variant_ids = vec![]; @@ -164,20 +174,27 @@ async fn create_func( None => { let name = func_spec.name(); + let func_spec_data = func_spec + .data() + .ok_or(PkgError::DataNotFound(name.into()))?; + // How to handle name conflicts? let mut func = Func::new( ctx, name, - func_spec.backend_kind().into(), - func_spec.response_type().into(), + func_spec_data.backend_kind().into(), + func_spec_data.response_type().into(), ) .await?; - func.set_display_name(ctx, func_spec.display_name()).await?; - func.set_code_base64(ctx, Some(func_spec.code_base64())) + func.set_display_name(ctx, func_spec_data.display_name()) + .await?; + func.set_code_base64(ctx, Some(func_spec_data.code_base64())) + .await?; + func.set_description(ctx, func_spec_data.description()) + .await?; + func.set_handler(ctx, Some(func_spec_data.handler())) .await?; - func.set_description(ctx, func_spec.description()).await?; - func.set_handler(ctx, Some(func_spec.handler())).await?; func.set_hidden(ctx, func.hidden()).await?; func.set_link(ctx, func_spec.link().map(|l| l.to_string())) .await?; @@ -225,13 +242,21 @@ async fn create_schema( let mut schema = match existing_schema { None => { let mut schema = Schema::new(ctx, schema_spec.name(), &ComponentKind::Standard).await?; - schema.set_ui_hidden(ctx, schema_spec.ui_hidden()).await?; + + let schema_spec_data = schema_spec + .data() + .ok_or(PkgError::DataNotFound(schema.name().into()))?; + + schema + .set_ui_hidden(ctx, schema_spec_data.ui_hidden()) + .await?; + let ui_menu = SchemaUiMenu::new( ctx, - schema_spec + schema_spec_data .category_name() .unwrap_or_else(|| schema_spec.name()), - schema_spec.category(), + schema_spec_data.category(), ) .await?; ui_menu.set_schema(ctx, schema.id()).await?; @@ -259,14 +284,18 @@ async fn create_schema( let mut installed_schema_variant_ids = vec![]; for variant_spec in schema_spec.variants()? { - let func_unique_id = variant_spec.func_unique_id(); + let variant_spec_data = variant_spec + .data() + .ok_or(PkgError::DataNotFound(schema_spec.name().into()))?; + + let func_unique_id = variant_spec_data.func_unique_id().to_owned(); let schema_variant_id = create_schema_variant(ctx, &mut schema, variant_spec, installed_pkg_id, func_map) .await?; installed_schema_variant_ids.push(schema_variant_id); let asset_func = func_map - .get(&func_unique_id) + .get(&func_unique_id.clone()) .ok_or(PkgError::MissingFuncUniqueId(func_unique_id.to_string()))?; create_schema_variant_definition( @@ -361,7 +390,7 @@ async fn create_schema_variant_definition( #[derive(Clone, Debug)] struct AttrFuncInfo { - func_unique_id: FuncUniqueId, + func_unique_id: String, prop_id: PropId, inputs: Vec, } @@ -405,7 +434,7 @@ async fn create_leaf_function( .map(|input| input.into()) .collect(); - match func_map.get(&leaf_func.func_unique_id()) { + match func_map.get(&leaf_func.func_unique_id().to_owned()) { Some(func) => { SchemaVariant::upsert_leaf_function( ctx, @@ -463,9 +492,14 @@ async fn create_socket( let (identity_func, identity_func_binding, identity_fbrv, _) = get_identity_func(ctx).await?; let name = socket_spec.name(); - let arity = socket_spec.arity(); - let mut socket = match socket_spec.kind() { + let data = socket_spec + .data() + .ok_or(PkgError::DataNotFound(name.into()))?; + + let arity = data.arity(); + + let mut socket = match data.kind() { SocketSpecKind::Input => { let (_, socket) = InternalProvider::new_explicit_with_socket( ctx, @@ -479,7 +513,7 @@ async fn create_socket( ) .await?; - if let Some(func_unique_id) = socket_spec.func_unique_id() { + if let Some(func_unique_id) = data.func_unique_id() { dbg!( "Input socket that is set by a function?", func_unique_id, @@ -504,7 +538,7 @@ async fn create_socket( ) .await?; - if let Some(func_unique_id) = socket_spec.func_unique_id() { + if let Some(func_unique_id) = data.func_unique_id() { create_attribute_function_for_output_socket( ctx, schema_variant_id, @@ -520,7 +554,7 @@ async fn create_socket( } }; - socket.set_ui_hidden(ctx, socket_spec.ui_hidden()).await?; + socket.set_ui_hidden(ctx, data.ui_hidden()).await?; Ok(()) } @@ -531,12 +565,11 @@ async fn create_action_func( schema_variant_id: SchemaVariantId, func_map: &FuncMap, ) -> PkgResult<()> { - let func = - func_map - .get(&action_func_spec.func_unique_id()) - .ok_or(PkgError::MissingFuncUniqueId( - action_func_spec.func_unique_id().to_string(), - ))?; + let func = func_map + .get(&action_func_spec.func_unique_id().to_owned()) + .ok_or(PkgError::MissingFuncUniqueId( + action_func_spec.func_unique_id().to_string(), + ))?; ActionPrototype::new( ctx, @@ -611,7 +644,12 @@ async fn create_schema_variant( .set_default_schema_variant_id(ctx, Some(schema_variant.id())) .await?; - if let Some(color) = variant_spec.color() { + let variant_spec_data = variant_spec.data().ok_or(PkgError::DataNotFound(format!( + "variant for {}", + schema.name() + )))?; + + if let Some(color) = variant_spec_data.color() { schema_variant.set_color(ctx, color.to_owned()).await?; } @@ -650,7 +688,7 @@ async fn create_schema_variant( }; schema_variant - .finalize(ctx, Some(variant_spec.component_type().into())) + .finalize(ctx, Some(variant_spec_data.component_type().into())) .await?; for action_func in variant_spec.action_funcs()? { @@ -696,7 +734,7 @@ async fn create_schema_variant( ctx, *schema_variant.id(), AttrFuncInfo { - func_unique_id: si_prop_func.func_unique_id(), + func_unique_id: si_prop_func.func_unique_id().to_owned(), prop_id: *prop.id(), inputs: si_prop_func .inputs()? @@ -739,7 +777,7 @@ async fn create_schema_variant( } schema_variant - .finalize(ctx, Some(variant_spec.component_type().into())) + .finalize(ctx, Some(variant_spec_data.component_type().into())) .await?; *schema_variant.id() @@ -819,12 +857,12 @@ async fn create_attribute_function_for_output_socket( ctx: &DalContext, schema_variant_id: SchemaVariantId, external_provider_id: ExternalProviderId, - func_unique_id: FuncUniqueId, + func_unique_id: &str, inputs: Vec, func_map: &FuncMap, ) -> PkgResult<()> { let func = func_map - .get(&func_unique_id) + .get(&func_unique_id.to_string()) .ok_or(PkgError::MissingFuncUniqueId(func_unique_id.to_string()))?; create_attribute_function( @@ -1053,80 +1091,38 @@ async fn create_prop( parent_prop_id: Option, ctx: &PropVisitContext<'_, '_>, ) -> PkgResult> { + let data = match &spec { + SiPkgProp::Array { data, .. } + | SiPkgProp::Boolean { data, .. } + | SiPkgProp::Map { data, .. } + | SiPkgProp::Number { data, .. } + | SiPkgProp::Object { data, .. } + | SiPkgProp::String { data, .. } => data + .to_owned() + .ok_or(PkgError::DataNotFound(format!("prop {}", spec.name())))?, + }; + let mut prop = Prop::new( ctx.ctx, spec.name(), match &spec { - SiPkgProp::String { .. } => PropKind::String, - SiPkgProp::Number { .. } => PropKind::Integer, + SiPkgProp::Array { .. } => PropKind::Array, SiPkgProp::Boolean { .. } => PropKind::Boolean, SiPkgProp::Map { .. } => PropKind::Map, - SiPkgProp::Array { .. } => PropKind::Array, + SiPkgProp::Number { .. } => PropKind::Integer, SiPkgProp::Object { .. } => PropKind::Object, + SiPkgProp::String { .. } => PropKind::String, }, - match &spec { - SiPkgProp::String { - widget_kind, - widget_options, - .. - } - | SiPkgProp::Number { - widget_kind, - widget_options, - .. - } - | SiPkgProp::Boolean { - widget_kind, - widget_options, - .. - } - | SiPkgProp::Map { - widget_kind, - widget_options, - .. - } - | SiPkgProp::Array { - widget_kind, - widget_options, - .. - } - | SiPkgProp::Object { - widget_kind, - widget_options, - .. - } => Some((widget_kind.into(), widget_options.to_owned())), - }, + Some(((&data.widget_kind).into(), data.widget_options)), ctx.schema_variant_id, parent_prop_id, ) .await .map_err(SiPkgError::visit_prop)?; - prop.set_hidden( - ctx.ctx, - match &spec { - SiPkgProp::String { hidden, .. } - | SiPkgProp::Number { hidden, .. } - | SiPkgProp::Boolean { hidden, .. } - | SiPkgProp::Map { hidden, .. } - | SiPkgProp::Array { hidden, .. } - | SiPkgProp::Object { hidden, .. } => *hidden, - }, - ) - .await?; - - prop.set_doc_link( - ctx.ctx, - match &spec { - SiPkgProp::String { doc_link, .. } - | SiPkgProp::Number { doc_link, .. } - | SiPkgProp::Boolean { doc_link, .. } - | SiPkgProp::Map { doc_link, .. } - | SiPkgProp::Array { doc_link, .. } - | SiPkgProp::Object { doc_link, .. } => doc_link.as_ref().map(|l| l.to_string()), - }, - ) - .await?; + prop.set_hidden(ctx.ctx, data.hidden).await?; + prop.set_doc_link(ctx.ctx, data.doc_link.as_ref().map(|l| l.to_string())) + .await?; let prop_id = *prop.id(); @@ -1136,23 +1132,42 @@ async fn create_prop( // mutability (maybe there's a better type for that here?) if let Some(default_value_info) = match &spec { - SiPkgProp::String { default_value, .. } => { - default_value.as_ref().map(|dv| DefaultValueInfo::String { - prop_id, - default_value: dv.to_owned(), - }) + SiPkgProp::String { .. } => { + if let Some(serde_json::Value::String(default_value)) = data.default_value { + Some(DefaultValueInfo::String { + prop_id, + default_value, + }) + } else { + // Raise error here for type mismatch + None + } } - SiPkgProp::Number { default_value, .. } => { - default_value.map(|default_value| DefaultValueInfo::Number { - prop_id, - default_value, - }) + 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 { default_value, .. } => { - default_value.map(|default_value| DefaultValueInfo::Boolean { - prop_id, - default_value, - }) + SiPkgProp::Boolean { .. } => { + if let Some(serde_json::Value::Bool(default_value)) = data.default_value { + Some(DefaultValueInfo::Boolean { + prop_id, + default_value, + }) + } else { + None + } } // Default values for complex types are not yet supported in packages _ => None, @@ -1169,7 +1184,7 @@ async fn create_prop( ctx.map_key_funcs.lock().await.push(( key.to_owned(), AttrFuncInfo { - func_unique_id, + func_unique_id: func_unique_id.to_owned(), prop_id, inputs: inputs.drain(..).map(Into::into).collect(), }, @@ -1177,10 +1192,10 @@ async fn create_prop( } } - if let Some(func_unique_id) = spec.func_unique_id() { + if let Some(func_unique_id) = data.func_unique_id { let mut inputs = spec.inputs()?; ctx.attr_funcs.lock().await.push(AttrFuncInfo { - func_unique_id, + func_unique_id: func_unique_id.to_owned(), prop_id, inputs: inputs.drain(..).map(Into::into).collect(), }); diff --git a/lib/dal/src/prop_tree.rs b/lib/dal/src/prop_tree.rs index 54270642ea..47f6b7fffc 100644 --- a/lib/dal/src/prop_tree.rs +++ b/lib/dal/src/prop_tree.rs @@ -1,3 +1,4 @@ +use crate::ChangeSetPk; use crate::{ property_editor::schema::WidgetKind, DalContext, InternalProviderId, Prop, PropId, PropKind, SchemaError, SchemaVariant, SchemaVariantError, SchemaVariantId, StandardModel, @@ -39,6 +40,7 @@ pub struct PropTreeNode { pub children: Vec, pub parent_id: PropId, pub prop_id: PropId, + pub visibility_change_set_pk: ChangeSetPk, pub kind: PropKind, pub schema_variant_id: SchemaVariantId, pub internal_provider_id: Option, @@ -141,6 +143,7 @@ impl PropTree { } let parent_id: PropId = row.try_get("parent_id")?; let schema_variant_id: SchemaVariantId = row.try_get("schema_variant_id")?; + let visibility_change_set_pk: ChangeSetPk = row.try_get("visibility_change_set_pk")?; if let Some(schema_variant_id_filter) = &schema_variant_id_filter { if !schema_variant_id_filter.contains(&schema_variant_id) { @@ -167,6 +170,7 @@ impl PropTree { widget_kind: *prop.widget_kind(), widget_options: prop.widget_options().cloned(), doc_link: prop.doc_link().map(|l| l.to_owned()), + visibility_change_set_pk, }; // The ordering of the query ensures parent nodes will always come before their children diff --git a/lib/dal/src/queries/attribute_prototype/list_protoype_funcs_for_context_and_func_backend_response_type.sql b/lib/dal/src/queries/attribute_prototype/list_protoype_funcs_for_context_and_func_backend_response_type.sql index 08d8b62f5a..7d8809989b 100644 --- a/lib/dal/src/queries/attribute_prototype/list_protoype_funcs_for_context_and_func_backend_response_type.sql +++ b/lib/dal/src/queries/attribute_prototype/list_protoype_funcs_for_context_and_func_backend_response_type.sql @@ -1,9 +1,10 @@ -SELECT DISTINCT ON (funcs.id) - row_to_json(funcs.*) AS object +SELECT + row_to_json(funcs.*) AS func_object, + row_to_json(ap.*) as prototype_object FROM attribute_prototypes_v1($1, $2) AS ap JOIN funcs_v1($1, $2) as funcs ON funcs.id = ap.func_id WHERE in_attribute_context_v1($3, ap) AND ap.attribute_context_prop_id = $4 AND funcs.backend_response_type = $5 -ORDER BY funcs.id +ORDER BY ap.id diff --git a/lib/dal/src/queries/prop/tree_for_all_schema_variants.sql b/lib/dal/src/queries/prop/tree_for_all_schema_variants.sql index 933a7472cc..01971177d4 100644 --- a/lib/dal/src/queries/prop/tree_for_all_schema_variants.sql +++ b/lib/dal/src/queries/prop/tree_for_all_schema_variants.sql @@ -10,7 +10,8 @@ WITH RECURSIVE props_tree AS (SELECT row_to_json(p.*) AS object, ident_nil_v1() AS parent_id, 0::bigint AS depth, p.hidden AS hidden, - p.schema_variant_id AS schema_variant_id + p.schema_variant_id AS schema_variant_id, + p.visibility_change_set_pk AS visibility_change_set_pk FROM props_v1($1, $2) AS p LEFT JOIN prop_belongs_to_prop_v1($1, $2) AS pbtp ON p.id = pbtp.object_id @@ -25,7 +26,8 @@ WITH RECURSIVE props_tree AS (SELECT row_to_json(p.*) AS object, parent.prop_id AS parent_id, parent.depth + 1 AS depth, child_props.hidden AS hidden, - child_props.schema_variant_id AS schema_variant_id + child_props.schema_variant_id AS schema_variant_id, + child_props.visibility_change_set_pk AS visibility_change_set_pk FROM props_v1($1, $2) AS child_props JOIN prop_belongs_to_prop_v1($1, $2) AS pbtp2 ON child_props.id = pbtp2.object_id @@ -38,6 +40,7 @@ SELECT props_tree.object, props_tree.name, props_tree.path, props_tree.schema_variant_id, + props_tree.visibility_change_set_pk, ip.id AS internal_provider_id FROM props_tree LEFT JOIN internal_providers_v1($1, $2) ip ON props_tree.prop_id = ip.prop_id diff --git a/lib/dal/src/schema/variant.rs b/lib/dal/src/schema/variant.rs index 30cd0463d8..da7d85a710 100644 --- a/lib/dal/src/schema/variant.rs +++ b/lib/dal/src/schema/variant.rs @@ -668,7 +668,7 @@ impl SchemaVariant { ctx: &DalContext, schema_variant_id: SchemaVariantId, leaf_kind: LeafKind, - ) -> SchemaVariantResult> { + ) -> SchemaVariantResult> { let leaf_item_prop = Self::find_leaf_item_prop(ctx, schema_variant_id, leaf_kind).await?; let backend_response_type: FuncBackendResponseType = leaf_kind.into(); diff --git a/lib/dal/src/schema/variant/definition.rs b/lib/dal/src/schema/variant/definition.rs index cf8d20ac8d..8185d6a9e4 100644 --- a/lib/dal/src/schema/variant/definition.rs +++ b/lib/dal/src/schema/variant/definition.rs @@ -19,9 +19,9 @@ use crate::{ }; use crate::{Component, ComponentError, SchemaId, TransactionsError}; use si_pkg::{ - AttrFuncInputSpec, FuncUniqueId, MapKeyFuncSpec, PropSpec, PropSpecWidgetKind, SchemaSpec, - SchemaVariantSpec, SiPropFuncSpec, SiPropFuncSpecKind, SocketSpec, SocketSpecArity, - SocketSpecKind, SpecError, ValidationSpec, + AttrFuncInputSpec, MapKeyFuncSpec, PropSpec, SchemaSpec, SchemaSpecData, SchemaVariantSpec, + SchemaVariantSpecData, SiPropFuncSpec, SiPropFuncSpecKind, SocketSpec, SocketSpecArity, + SocketSpecData, SocketSpecKind, SpecError, ValidationSpec, }; #[remain::sorted] @@ -295,10 +295,13 @@ impl SchemaVariantDefinitionMetadataJson { pub fn to_spec(&self, variant: SchemaVariantSpec) -> SchemaVariantDefinitionResult { let mut builder = SchemaSpec::builder(); builder.name(&self.name); - builder.category(&self.category); + let mut data_builder = SchemaSpecData::builder(); + data_builder.name(&self.name); + data_builder.category(&self.category); if let Some(menu_name) = &self.menu_name { - builder.category_name(menu_name.as_str()); + data_builder.category_name(menu_name.as_str()); } + builder.data(data_builder.build()?); builder.variant(variant); Ok(builder.build()?) @@ -420,11 +423,25 @@ impl SchemaVariantDefinitionJson { pub fn to_spec( &self, metadata: SchemaVariantDefinitionMetadataJson, - identity_func_unique_id: FuncUniqueId, - asset_func_spec_unique_id: FuncUniqueId, + identity_func_unique_id: &str, + asset_func_spec_unique_id: &str, ) -> SchemaVariantDefinitionResult { let mut builder = SchemaVariantSpec::builder(); - builder.name("v0"); + let name = "v0"; + builder.name(name); + + let mut data_builder = SchemaVariantSpecData::builder(); + + data_builder.name(name); + data_builder.color(metadata.color); + data_builder.component_type(metadata.component_type); + if let Some(link) = metadata.link { + data_builder.try_link(link.as_str())?; + } + + data_builder.func_unique_id(asset_func_spec_unique_id); + builder.data(data_builder.build()?); + for si_prop_value_from in &self.si_prop_value_froms { builder.si_prop_func(si_prop_value_from.to_spec(identity_func_unique_id)); } @@ -434,11 +451,6 @@ impl SchemaVariantDefinitionJson { for resource_prop in &self.resource_props { builder.resource_value_prop(resource_prop.to_spec(identity_func_unique_id)?); } - builder.color(metadata.color); - builder.component_type(metadata.component_type); - if let Some(link) = metadata.link { - builder.try_link(link.as_str())?; - } for input_socket in &self.input_sockets { builder.socket(input_socket.to_spec(true, identity_func_unique_id)?); } @@ -446,8 +458,6 @@ impl SchemaVariantDefinitionJson { builder.socket(output_socket.to_spec(false, identity_func_unique_id)?); } - builder.func_unique_id(asset_func_spec_unique_id); - Ok(builder.build()?) } @@ -463,14 +473,35 @@ impl SchemaVariantDefinitionJson { .get(0) .ok_or(SchemaVariantDefinitionError::NoVariants)?; + let schema_data = schema_spec.data.unwrap_or(SchemaSpecData { + name: schema_spec.name.to_owned(), + category: "".into(), + category_name: None, + ui_hidden: false, + }); + + let variant_spec_data = variant_spec + .data + .to_owned() + .unwrap_or(SchemaVariantSpecData { + name: "v0".into(), + color: None, + link: None, + component_type: si_pkg::SchemaVariantSpecComponentType::Component, + func_unique_id: "0".into(), + }); + let metadata = SchemaVariantDefinitionMetadataJson { name: schema_spec.name, - menu_name: schema_spec.category_name, - category: schema_spec.category, - color: variant_spec.color.to_owned().unwrap_or("000000".into()), + menu_name: schema_data.category_name, + category: schema_data.category, + color: variant_spec_data + .color + .to_owned() + .unwrap_or("000000".into()), component_kind: ComponentKind::Standard, - component_type: variant_spec.component_type.into(), - link: variant_spec.link.as_ref().map(|l| l.to_string()), + component_type: variant_spec_data.component_type.into(), + link: variant_spec_data.link.as_ref().map(|l| l.to_string()), description: None, // XXX - does this exist? }; @@ -488,15 +519,6 @@ pub struct PropWidgetDefinition { options: Option, } -impl PropWidgetDefinition { - fn from_spec(kind: Option, options: Option) -> Option { - kind.map(|kind| Self { - kind: (&kind).into(), - options, - }) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct MapKeyFunc { @@ -509,7 +531,7 @@ pub struct MapKeyFunc { impl MapKeyFunc { pub fn to_spec( &self, - identity_func_unique_id: FuncUniqueId, + identity_func_unique_id: &str, ) -> SchemaVariantDefinitionResult { let mut builder = MapKeyFuncSpec::builder(); builder.func_unique_id(identity_func_unique_id); @@ -519,20 +541,6 @@ impl MapKeyFunc { }; Ok(builder.build()?) } - - pub fn from_spec(spec: MapKeyFuncSpec, identity_func_unique_id: FuncUniqueId) -> Option { - match ValueFrom::maybe_from_spec( - Some(spec.inputs), - Some(spec.func_unique_id), - identity_func_unique_id, - ) { - Some(value_from) => Some(Self { - key: spec.key, - value_from: Some(value_from), - }), - None => None, - } - } } /// The definition for a [`Prop`](crate::Prop) in a [`SchemaVariant`](crate::SchemaVariant). @@ -580,11 +588,12 @@ pub struct PropDefinition { impl PropDefinition { pub fn to_spec( &self, - identity_func_unique_id: FuncUniqueId, + identity_func_unique_id: &str, ) -> SchemaVariantDefinitionResult { let mut builder = PropSpec::builder(); builder.name(&self.name); builder.kind(self.kind); + builder.has_data(true); if let Some(doc_url) = &self.doc_link { builder.try_doc_link(doc_url.as_str())?; } @@ -630,215 +639,6 @@ impl PropDefinition { Ok(builder.build()?) } - - pub fn from_spec( - prop_spec: PropSpec, - identity_func_unique_id: FuncUniqueId, - ) -> SchemaVariantDefinitionResult { - Ok(match prop_spec { - PropSpec::Array { - name, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - type_prop, - .. - } => PropDefinition { - name, - kind: PropKind::Array, - doc_link_ref: None, - doc_link: doc_link.map(|l| l.to_string()), - children: vec![], - entry: Some(Box::new(Self::from_spec( - *type_prop, - identity_func_unique_id, - )?)), - widget: PropWidgetDefinition::from_spec(widget_kind, widget_options), - value_from: ValueFrom::maybe_from_spec( - inputs, - func_unique_id, - identity_func_unique_id, - ), - hidden, - validations, - default_value: None, - map_key_funcs: None, - }, - PropSpec::Boolean { - name, - default_value, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - } => PropDefinition { - name, - kind: PropKind::Boolean, - doc_link_ref: None, - doc_link: doc_link.map(|l| l.to_string()), - children: vec![], - entry: None, - widget: PropWidgetDefinition::from_spec(widget_kind, widget_options), - value_from: ValueFrom::maybe_from_spec( - inputs, - func_unique_id, - identity_func_unique_id, - ), - hidden, - validations, - default_value: match default_value { - Some(dv) => Some(serde_json::to_value(dv)?), - None => None, - }, - map_key_funcs: None, - }, - PropSpec::Map { - name, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - type_prop, - map_key_funcs, - .. - } => PropDefinition { - name, - kind: PropKind::Array, - doc_link_ref: None, - doc_link: doc_link.map(|l| l.to_string()), - children: vec![], - entry: Some(Box::new(Self::from_spec( - *type_prop, - identity_func_unique_id, - )?)), - widget: PropWidgetDefinition::from_spec(widget_kind, widget_options), - value_from: ValueFrom::maybe_from_spec( - inputs, - func_unique_id, - identity_func_unique_id, - ), - hidden, - validations, - default_value: None, - map_key_funcs: map_key_funcs.map(|specs| { - specs - .iter() - .filter_map(|spec| { - MapKeyFunc::from_spec(spec.to_owned(), identity_func_unique_id) - }) - .collect() - }), - }, - PropSpec::Number { - name, - default_value, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - } => PropDefinition { - name, - kind: PropKind::Integer, - doc_link_ref: None, - doc_link: doc_link.map(|l| l.to_string()), - children: vec![], - entry: None, - widget: PropWidgetDefinition::from_spec(widget_kind, widget_options), - value_from: ValueFrom::maybe_from_spec( - inputs, - func_unique_id, - identity_func_unique_id, - ), - hidden, - validations, - default_value: match default_value { - Some(dv) => Some(serde_json::to_value(dv)?), - None => None, - }, - map_key_funcs: None, - }, - PropSpec::Object { - name, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - entries, - .. - } => { - let mut children = vec![]; - for entry in entries { - children.push(Self::from_spec(entry, identity_func_unique_id)?); - } - - PropDefinition { - name, - kind: PropKind::Integer, - doc_link_ref: None, - doc_link: doc_link.map(|l| l.to_string()), - children, - entry: None, - widget: PropWidgetDefinition::from_spec(widget_kind, widget_options), - value_from: ValueFrom::maybe_from_spec( - inputs, - func_unique_id, - identity_func_unique_id, - ), - hidden, - validations, - default_value: None, - map_key_funcs: None, - } - } - PropSpec::String { - name, - default_value, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - } => PropDefinition { - name, - kind: PropKind::String, - doc_link_ref: None, - doc_link: doc_link.map(|l| l.to_string()), - children: vec![], - entry: None, - widget: PropWidgetDefinition::from_spec(widget_kind, widget_options), - value_from: ValueFrom::maybe_from_spec( - inputs, - func_unique_id, - identity_func_unique_id, - ), - hidden, - validations, - default_value: match default_value { - Some(dv) => Some(serde_json::to_value(dv)?), - None => None, - }, - map_key_funcs: None, - }, - }) - } } /// The definition for a [`Socket`](crate::Socket) in a [`SchemaVariant`](crate::SchemaVariant). @@ -863,46 +663,36 @@ impl SocketDefinition { pub fn to_spec( &self, is_input: bool, - identity_func_unique_id: FuncUniqueId, + identity_func_unique_id: &str, ) -> SchemaVariantDefinitionResult { let mut builder = SocketSpec::builder(); + let mut data_builder = SocketSpecData::builder(); builder.name(&self.name); + data_builder.name(&self.name); if is_input { - builder.kind(SocketSpecKind::Input); + data_builder.kind(SocketSpecKind::Input); } else { - builder.kind(SocketSpecKind::Output); + data_builder.kind(SocketSpecKind::Output); } if let Some(arity) = &self.arity { - builder.arity(arity); + data_builder.arity(arity); } else { - builder.arity(SocketSpecArity::Many); + data_builder.arity(SocketSpecArity::Many); } if let Some(hidden) = &self.ui_hidden { - builder.ui_hidden(*hidden); + data_builder.ui_hidden(*hidden); } else { - builder.ui_hidden(false); + data_builder.ui_hidden(false); } if let Some(value_from) = &self.value_from { - builder.func_unique_id(identity_func_unique_id); + data_builder.func_unique_id(identity_func_unique_id); builder.input(value_from.to_spec()); } + builder.data(data_builder.build()?); Ok(builder.build()?) } - - pub fn from_spec(spec: SocketSpec, identity_func_unique_id: FuncUniqueId) -> Self { - SocketDefinition { - name: spec.name, - arity: Some(spec.arity.into()), - ui_hidden: Some(spec.ui_hidden), - value_from: ValueFrom::maybe_from_spec( - Some(spec.inputs), - spec.func_unique_id, - identity_func_unique_id, - ), - } - } } /// The definition for the source of the information for a prop or a socket in a [`SchemaVariant`](crate::SchemaVariant). @@ -920,60 +710,23 @@ impl ValueFrom { ValueFrom::InputSocket { socket_name } => AttrFuncInputSpec::InputSocket { name: "identity".to_string(), socket_name: socket_name.to_owned(), + unique_id: None, + deleted: false, }, ValueFrom::Prop { prop_path } => AttrFuncInputSpec::Prop { name: "identity".to_string(), prop_path: PropPath::new(prop_path).into(), + unique_id: None, + deleted: false, }, ValueFrom::OutputSocket { socket_name } => AttrFuncInputSpec::OutputSocket { name: "identity".to_string(), socket_name: socket_name.to_owned(), + unique_id: None, + deleted: false, }, } } - - fn maybe_from_spec( - inputs: Option>, - func_unique_id: Option, - identity_func_unique_id: FuncUniqueId, - ) -> Option { - match func_unique_id { - Some(func_unique_id) => { - if func_unique_id != identity_func_unique_id { - return None; - } - } - None => { - return None; - } - } - - match inputs { - Some(inputs) => { - if inputs.len() > 1 { - return None; - } - inputs.get(0).map(|input| match input { - AttrFuncInputSpec::Prop { prop_path, .. } => { - let path: PropPath = prop_path.into(); - - ValueFrom::Prop { - prop_path: path.as_owned_parts(), - } - } - AttrFuncInputSpec::InputSocket { socket_name, .. } => ValueFrom::InputSocket { - socket_name: socket_name.to_owned(), - }, - AttrFuncInputSpec::OutputSocket { socket_name, .. } => { - ValueFrom::OutputSocket { - socket_name: socket_name.to_owned(), - } - } - }) - } - None => None, - } - } } /// The definition for the source of the data for prop under "/root/"si" in a [`SchemaVariant`](crate::SchemaVariant). @@ -985,11 +738,13 @@ pub struct SiPropValueFrom { } impl SiPropValueFrom { - fn to_spec(&self, identity_func_unique_id: FuncUniqueId) -> SiPropFuncSpec { + fn to_spec(&self, identity_func_unique_id: &str) -> SiPropFuncSpec { SiPropFuncSpec { kind: self.kind, - func_unique_id: identity_func_unique_id, + func_unique_id: identity_func_unique_id.to_owned(), inputs: vec![self.value_from.to_spec()], + unique_id: None, + deleted: false, } } } diff --git a/lib/dal/src/visibility.rs b/lib/dal/src/visibility.rs index 19e27b4416..8d390b57b3 100644 --- a/lib/dal/src/visibility.rs +++ b/lib/dal/src/visibility.rs @@ -81,8 +81,13 @@ impl Visibility { } /// Converts this `Visibility` to a new change set `Visibility`. - pub fn to_change_set(&self) -> Self { - Self::new_change_set(self.change_set_pk, self.deleted_at.is_some()) + pub fn to_change_set(&self, change_set_pk: ChangeSetPk) -> Self { + Self::new_change_set(change_set_pk, self.deleted_at.is_some()) + } + + /// Converts this `Visibility` to a new change set `Visibility`. + pub fn to_change_set_deleted(&self, change_set_pk: ChangeSetPk) -> Self { + Self::new_change_set(change_set_pk, true) } /// Returns true if this [`Visibility`] is in a working changeset (and not in head) @@ -91,6 +96,10 @@ impl Visibility { self.change_set_pk != ChangeSetPk::NONE } + pub fn is_deleted(&self) -> bool { + self.deleted_at.is_some() + } + #[instrument(skip(ctx))] pub async fn is_visible_to( &self, diff --git a/lib/dal/tests/integration_test/internal/pkg.rs b/lib/dal/tests/integration_test/internal/pkg.rs index c2e2806276..596cbbc3e7 100644 --- a/lib/dal/tests/integration_test/internal/pkg.rs +++ b/lib/dal/tests/integration_test/internal/pkg.rs @@ -1,18 +1,173 @@ use base64::{engine::general_purpose, Engine}; -use dal::func::intrinsics::IntrinsicFunc; use dal::{ - func::backend::validation::FuncBackendValidationArgs, installed_pkg::*, pkg::*, - schema::variant::leaves::LeafKind, validation::Validation, DalContext, ExternalProvider, Func, - InternalProvider, Schema, SchemaVariant, StandardModel, ValidationPrototype, + func::backend::validation::FuncBackendValidationArgs, func::intrinsics::IntrinsicFunc, + installed_pkg::*, pkg::*, schema::variant::leaves::LeafKind, validation::Validation, ChangeSet, + ChangeSetPk, DalContext, ExternalProvider, Func, InternalProvider, Schema, SchemaVariant, + StandardModel, ValidationPrototype, }; -use dal_test::test; +use dal_test::{test, DalContextHeadRef}; use si_pkg::{ - FuncSpec, FuncSpecBackendKind, FuncSpecBackendResponseType, LeafFunctionSpec, + FuncSpec, FuncSpecBackendKind, FuncSpecBackendResponseType, FuncSpecData, LeafFunctionSpec, LeafInputLocation as PkgLeafInputLocation, LeafKind as PkgLeafKind, PkgSpec, PropSpec, - PropSpecKind, SchemaSpec, SchemaVariantSpec, SiPkg, SocketSpec, SocketSpecArity, - SocketSpecKind, ValidationSpec, ValidationSpecKind, + PropSpecKind, SchemaSpec, SchemaSpecData, SchemaVariantSpec, SchemaVariantSpecData, SiPkg, + SocketSpec, SocketSpecArity, SocketSpecData, SocketSpecKind, ValidationSpec, + ValidationSpecKind, }; +#[test] +async fn test_workspace_pkg_export(DalContextHeadRef(ctx): DalContextHeadRef<'_>) { + let new_change_set = ChangeSet::new(ctx, "cs1", None) + .await + .expect("can create change set"); + + let cs_ctx = ctx.clone_with_new_visibility(ctx.visibility().to_change_set(new_change_set.pk)); + + let mut func = Func::find_by_name(&cs_ctx, "test:refreshActionStarfield") + .await + .expect("able to get refreshActionStarfield") + .expect("func exists"); + + func.delete_by_id(&cs_ctx).await.expect("able to delete"); + + cs_ctx.blocking_commit().await.expect("able to commit"); + + let change_set_2 = ChangeSet::new(ctx, "cs2", None) + .await + .expect("can create change set"); + let cs2_ctx = ctx.clone_with_new_visibility(ctx.visibility().to_change_set(change_set_2.pk)); + + let mut func = Func::find_by_name(&cs2_ctx, "test:refreshActionStarfield") + .await + .expect("able to get refreshActionStarfield") + .expect("func exists"); + + func.set_display_name(&cs2_ctx, Some("foo")) + .await + .expect("set display name"); + cs2_ctx.blocking_commit().await.expect("able to commit"); + + let mut exporter = PkgExporter::new_workspace_exporter("workspace", "sally@systeminit.com"); + + let package_bytes = exporter.export_as_bytes(ctx).await.expect("able to export"); + + let pkg = SiPkg::load_from_bytes(package_bytes).expect("able to load from bytes"); + let spec = pkg.to_spec().await.expect("can convert to spec"); + + assert_eq!(Some("head"), spec.default_change_set.as_deref()); + assert_eq!(3, spec.change_sets.len()); + + let cs1 = spec.change_sets.get(2).expect("has second change set"); + + assert_eq!("cs1", &cs1.name); + assert_eq!(Some("head"), cs1.based_on_change_set.as_deref()); + + assert_eq!(1, cs1.funcs.len()); + let refresh_func_in_changeset = cs1.funcs.get(0).expect("get first func"); + assert_eq!( + "test:refreshActionStarfield", + &refresh_func_in_changeset.name + ); + assert!(matches!(refresh_func_in_changeset.data, None)); + assert!(refresh_func_in_changeset.deleted); + + let cs2 = spec.change_sets.get(1).expect("has second change set"); + + assert_eq!("cs2", &cs2.name); + assert_eq!(Some("head"), cs2.based_on_change_set.as_deref()); + + assert_eq!(1, cs2.funcs.len()); + let refresh_func_in_changeset = cs2.funcs.get(0).expect("get first func"); + + assert_eq!( + Some("foo"), + refresh_func_in_changeset + .data + .as_ref() + .and_then(|data| data.display_name.as_deref()) + ); +} + +#[test] +async fn test_module_pkg_export(DalContextHeadRef(ctx): DalContextHeadRef<'_>) { + let generic_frame_id = Schema::find_by_name(ctx, "Generic Frame") + .await + .expect("get generic frame") + .id() + .to_owned(); + + let starfield_id = Schema::find_by_name(ctx, "starfield") + .await + .expect("get starfield") + .id() + .to_owned(); + + let fallout_id = Schema::find_by_name(ctx, "fallout") + .await + .expect("get fallout") + .id() + .to_owned(); + + let schema_ids = vec![generic_frame_id, starfield_id, fallout_id]; + + let mut exporter = PkgExporter::new_module_exporter( + "module", + "test-version", + None::, + "sally@systeminit.com", + schema_ids, + ); + + let package_bytes = exporter.export_as_bytes(ctx).await.expect("able to export"); + + let pkg = SiPkg::load_from_bytes(package_bytes).expect("able to load from bytes"); + let _spec = pkg.to_spec().await.expect("can convert to spec"); + + let new_change_set = ChangeSet::new(ctx, "cs1", None) + .await + .expect("can create change set"); + + let new_ctx = ctx.clone_with_new_visibility(ctx.visibility().to_change_set(new_change_set.pk)); + import_pkg_from_pkg( + &new_ctx, + &pkg, + pkg.metadata().expect("get metadata").name(), + None, + ) + .await + .expect("able to import pkg"); + + let sv_change_sets: Vec = SchemaVariant::list(&new_ctx) + .await + .expect("get svs again") + .iter() + .map(|sv| sv.visibility().change_set_pk) + .collect(); + + let installed_variants = sv_change_sets + .into_iter() + .filter(|cspk| *cspk == new_change_set.pk) + .collect::>(); + + assert_eq!(3, installed_variants.len()); + + let installed_schemas: Vec = Schema::list(&new_ctx) + .await + .expect("get schemas") + .into_iter() + .filter(|schema| schema.visibility().change_set_pk == new_change_set.pk) + .collect(); + + let generic_frame = installed_schemas + .iter() + .find(|schema| schema.name() == "Generic Frame") + .expect("able to find generic frame"); + + dbg!(generic_frame + .ui_menus(&new_ctx) + .await + .expect("get ui menus for generic frame")); +} + #[test] async fn test_install_pkg(ctx: &DalContext) { let qualification_code = "function qualification(_input) { return { result: 'warning', message: 'omit needless words' }; } }"; @@ -20,18 +175,25 @@ async fn test_install_pkg(ctx: &DalContext) { let qualification_func_spec = FuncSpec::builder() .name("si:qualificationWarning") - .display_name("warning") - .description("it warns") - .handler("qualification") - .code_base64(&qualification_b64) - .backend_kind(FuncSpecBackendKind::JsAttribute) - .response_type(FuncSpecBackendResponseType::Qualification) - .hidden(false) + .unique_id("si:qualificationWarning") + .data( + FuncSpecData::builder() + .name("si:qualificationWarning") + .display_name("warning") + .description("it warns") + .handler("qualification") + .code_base64(&qualification_b64) + .backend_kind(FuncSpecBackendKind::JsAttribute) + .response_type(FuncSpecBackendResponseType::Qualification) + .hidden(false) + .build() + .expect("able to build func data for qual"), + ) .build() .expect("build qual func spec"); let qualification_spec = LeafFunctionSpec::builder() - .func_unique_id(qualification_func_spec.unique_id) + .func_unique_id(&qualification_func_spec.unique_id) .leaf_kind(PkgLeafKind::Qualification) .inputs(vec![ PkgLeafInputLocation::Domain, @@ -44,23 +206,42 @@ async fn test_install_pkg(ctx: &DalContext) { return new AssetBuilder().build(); }"; let scaffold_func_spec_a = FuncSpec::builder() - .name("si:scaffoldFunc") - .code_plaintext(scaffold_func_a) - .handler("createAsset") - .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) - .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) + .name("si:scaffoldFuncA") + .unique_id("si:scaffoldFuncA") + .data( + FuncSpecData::builder() + .name("si:scaffoldFuncA") + .code_plaintext(scaffold_func_a) + .handler("createAsset") + .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) + .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) + .build() + .expect("build func data"), + ) .build() .expect("could not build schema variant definition spec"); let schema_a = SchemaSpec::builder() .name("Tyrone Slothrop") - .category("Banana Puddings") - .ui_hidden(false) + .data( + SchemaSpecData::builder() + .name("Tyrone Slothrop") + .category("Banana Puddings") + .ui_hidden(false) + .build() + .expect("you never did the kenosha kid?"), + ) .variant( SchemaVariantSpec::builder() .name("Pig Bodine") - .color("baddad") - .func_unique_id(scaffold_func_spec_a.unique_id) + .data( + SchemaVariantSpecData::builder() + .name("Pig Bodine") + .color("baddad") + .func_unique_id(&scaffold_func_spec_a.unique_id) + .build() + .expect("pig bodine"), + ) .domain_prop( PropSpec::builder() .name("ImpolexG") @@ -86,13 +267,20 @@ async fn test_install_pkg(ctx: &DalContext) { let validation_b64 = general_purpose::STANDARD_NO_PAD.encode(custom_validation_code.as_bytes()); let validation_func_spec = FuncSpec::builder() .name("groucho") - .display_name("Horse Feathers") - .description("it rejects values") - .handler("validate") - .code_base64(&validation_b64) - .backend_kind(FuncSpecBackendKind::JsValidation) - .response_type(FuncSpecBackendResponseType::Validation) - .hidden(false) + .unique_id("groucho") + .data( + FuncSpecData::builder() + .name("groucho") + .display_name("Horse Feathers") + .description("it rejects values") + .handler("validate") + .code_base64(&validation_b64) + .backend_kind(FuncSpecBackendKind::JsValidation) + .response_type(FuncSpecBackendResponseType::Validation) + .hidden(false) + .build() + .expect("whatever it is, i'm against it"), + ) .build() .expect("able to build validation func spec"); @@ -100,38 +288,69 @@ async fn test_install_pkg(ctx: &DalContext) { return new AssetBuilder().build(); }"; let scaffold_func_spec_b = FuncSpec::builder() - .name("si:scaffoldFunc") - .code_plaintext(scaffold_func_b) - .handler("createAsset") - .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) - .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) + .name("si:scaffoldFuncB") + .unique_id("si:scaffoldFuncB") + .data( + FuncSpecData::builder() + .name("si:scaffoldFuncB") + .code_plaintext(scaffold_func_b) + .handler("createAsset") + .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) + .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) + .build() + .expect("scaffold b func data"), + ) .build() .expect("could not build schema variant definition spec"); let schema_b = SchemaSpec::builder() .name("Roger Mexico") - .ui_hidden(false) - .category("Banana Puddings") + .data( + SchemaSpecData::builder() + .name("Roger Mexico") + .ui_hidden(false) + .category("Banana Puddings") + .build() + .expect("roger mexico data"), + ) .variant( SchemaVariantSpec::builder() .name("The Light Bulb Conspiracy") - .color("baddad") - .func_unique_id(scaffold_func_spec_b.unique_id) + .data( + SchemaVariantSpecData::builder() + .name("The Light Bulb Conspiracy") + .color("baddad") + .func_unique_id(&scaffold_func_spec_b.unique_id) + .build() + .expect("light bulb spec data"), + ) .socket( SocketSpec::builder() .name("AC Power") - .ui_hidden(false) - .kind(SocketSpecKind::Input) - .arity(SocketSpecArity::One) + .data( + SocketSpecData::builder() + .name("AC Power") + .ui_hidden(false) + .kind(SocketSpecKind::Input) + .arity(SocketSpecArity::One) + .build() + .expect("build socket data"), + ) .build() .expect("able to make input socket"), ) .socket( SocketSpec::builder() .name("Light") - .kind(SocketSpecKind::Output) - .arity(SocketSpecArity::Many) - .ui_hidden(false) + .data( + SocketSpecData::builder() + .name("Light") + .kind(SocketSpecKind::Output) + .arity(SocketSpecArity::Many) + .ui_hidden(false) + .build() + .expect("build light socket data"), + ) .build() .expect("able to make output socket"), ) @@ -157,7 +376,7 @@ async fn test_install_pkg(ctx: &DalContext) { .validation( ValidationSpec::builder() .kind(ValidationSpecKind::CustomValidation) - .func_unique_id(validation_func_spec.unique_id) + .func_unique_id(&validation_func_spec.unique_id) .build() .expect("able to add custom validation"), ) @@ -178,44 +397,23 @@ async fn test_install_pkg(ctx: &DalContext) { let func_spec = FuncSpec::builder() .name("si:truthy") - .display_name("truth") - .description("it returns true") - .handler("truth") - .code_base64(&code_base64) - .backend_kind(FuncSpecBackendKind::JsAttribute) - .response_type(FuncSpecBackendResponseType::Boolean) - .hidden(false) - .build() - .expect("build func spec"); - - let func_spec_2 = FuncSpec::builder() - .name("si:truthy") - .display_name("truth") - .description("it returns true") - .handler("truth") - .code_base64(&code_base64) - .backend_kind(FuncSpecBackendKind::JsAttribute) - .response_type(FuncSpecBackendResponseType::Boolean) - .hidden(false) - .build() - .expect("build func spec"); - - let func_spec_3 = FuncSpec::builder() - .name("si:truthy") - .display_name("truth") - .description("it returns true, but this time with a different description") - .handler("truth") - .code_base64(&code_base64) - .backend_kind(FuncSpecBackendKind::JsAttribute) - .response_type(FuncSpecBackendResponseType::Boolean) - .hidden(false) + .unique_id("si:truthy") + .data( + FuncSpecData::builder() + .name("si:truthy") + .display_name("truth") + .description("it returns true") + .handler("truth") + .code_base64(&code_base64) + .backend_kind(FuncSpecBackendKind::JsAttribute) + .response_type(FuncSpecBackendResponseType::Boolean) + .hidden(false) + .build() + .expect("truth func data"), + ) .build() .expect("build func spec"); - // Ensure unique ids are stable and change with properties changing - assert_eq!(func_spec.unique_id, func_spec_2.unique_id); - assert_ne!(func_spec.unique_id, func_spec_3.unique_id); - let spec_a = PkgSpec::builder() .name("The White Visitation") .version("0.1") @@ -224,7 +422,7 @@ async fn test_install_pkg(ctx: &DalContext) { .func(func_spec) .func(identity_func_spec.clone()) .func(qualification_func_spec) - .func(scaffold_func_spec_a) + .func(scaffold_func_spec_a.clone()) .build() .expect("able to build package spec"); @@ -238,6 +436,7 @@ async fn test_install_pkg(ctx: &DalContext) { .func(identity_func_spec.clone()) .schema(schema_a) .schema(schema_b) + .func(scaffold_func_spec_a) .func(scaffold_func_spec_b) .build() .expect("able to build package spec"); diff --git a/lib/sdf-server/src/server/service/change_set.rs b/lib/sdf-server/src/server/service/change_set.rs index e321a8caa9..679e3edbd7 100644 --- a/lib/sdf-server/src/server/service/change_set.rs +++ b/lib/sdf-server/src/server/service/change_set.rs @@ -107,46 +107,3 @@ pub fn routes() -> Router { post(update_selected_change_set::update_selected_change_set), ) } - -// Ideally, this would be in a background job (and triggered directly by ChangeSet::apply_raw), -// but we'll need to nail down exactly how the job will auth to the module-index API first. -// Passing the user's access token into the background job processing system is kind of a -// non-starter. -/* -async fn upload_workspace_backup_module( - ctx: DalContext, - access_token: String, -) -> ChangeSetResult<()> { - let ctx = &ctx; - - let schema_variant_ids = SchemaVariant::list(ctx) - .await? - .iter() - .map(|sv| *sv.id()) - .collect(); - let module_name = "Workspace Backup"; - let module_version = Ulid::new().to_string(); - let module_bytes = dal::pkg::export_pkg_as_bytes( - ctx, - module_name, - &module_version, - Some("Backup of all schema variants on HEAD."), - "Sally Signup", - schema_variant_ids, - ) - .instrument(debug_span!("Generating workspace backup module")) - .await?; - let Some(module_index_url) = ctx.module_index_url() else { - return Err(PkgError::ModuleIndexNotConfigured.into()); - }; - let index_client = - module_index_client::IndexClient::new(module_index_url.try_into()?, &access_token); - let _upload_response = index_client - .upload_module(module_name, &module_version, module_bytes) - .instrument(debug_span!("Uploading module")) - .await?; - - info!("Success"); - Ok(()) -} -*/ diff --git a/lib/sdf-server/src/server/service/pkg.rs b/lib/sdf-server/src/server/service/pkg.rs index 3e3aa3a5f6..3b74ba645f 100644 --- a/lib/sdf-server/src/server/service/pkg.rs +++ b/lib/sdf-server/src/server/service/pkg.rs @@ -7,7 +7,8 @@ use axum::{ use convert_case::{Case, Casing}; use dal::{ installed_pkg::InstalledPkgError, pkg::PkgError as DalPkgError, DalContextBuilder, - StandardModelError, TenancyError, TransactionsError, UserError, WsEventError, + SchemaVariantError, SchemaVariantId, StandardModelError, TenancyError, TransactionsError, + UserError, WsEventError, }; use serde::{Deserialize, Serialize}; use si_pkg::{SiPkg, SiPkgError}; @@ -67,6 +68,12 @@ pub enum PkgError { PgPool(#[from] si_data_pg::PgPoolError), #[error(transparent)] Reqwest(#[from] reqwest::Error), + #[error("schema not found for variant {0}")] + SchemaNotFoundForVariant(SchemaVariantId), + #[error(transparent)] + SchemaVariant(#[from] SchemaVariantError), + #[error("schema variant not found {0}")] + SchemaVariantNotFound(SchemaVariantId), #[error("json serialization error: {0}")] SerdeJson(#[from] serde_json::Error), #[error(transparent)] diff --git a/lib/sdf-server/src/server/service/pkg/export_pkg.rs b/lib/sdf-server/src/server/service/pkg/export_pkg.rs index 67fd925545..3d726a898f 100644 --- a/lib/sdf-server/src/server/service/pkg/export_pkg.rs +++ b/lib/sdf-server/src/server/service/pkg/export_pkg.rs @@ -3,7 +3,7 @@ use crate::server::extract::{AccessBuilder, HandlerContext, PosthogClient, RawAc use crate::server::tracking::track; use axum::extract::OriginalUri; use axum::Json; -use dal::{HistoryActor, SchemaVariantId, User, Visibility, WsEvent}; +use dal::{HistoryActor, SchemaVariant, SchemaVariantId, StandardModel, User, Visibility, WsEvent}; use serde::{Deserialize, Serialize}; use telemetry::prelude::*; @@ -65,15 +65,28 @@ pub async fn export_pkg( )); info!("Packaging module"); - let module_payload = dal::pkg::export_pkg_as_bytes( - &ctx, + + // XXX:rework frontend to send schema ids + let mut schema_ids = vec![]; + for variant_id in &request.schema_variants { + let schema = SchemaVariant::get_by_id(&ctx, variant_id) + .await? + .ok_or(PkgError::SchemaVariantNotFound(*variant_id))? + .schema(&ctx) + .await? + .ok_or(PkgError::SchemaNotFoundForVariant(*variant_id))?; + schema_ids.push(*schema.id()); + } + + let mut exporter = dal::pkg::PkgExporter::new_module_exporter( &request.name, &request.version, request.description.as_ref(), &created_by_email, - request.schema_variants.clone(), - ) - .await?; + schema_ids, + ); + + let module_payload = exporter.export_as_bytes(&ctx).await?; let index_client = module_index_client::IndexClient::new(module_index_url.try_into()?, &raw_access_token); diff --git a/lib/sdf-server/src/server/service/variant_definition.rs b/lib/sdf-server/src/server/service/variant_definition.rs index 17f2687c7d..86bc137fee 100644 --- a/lib/sdf-server/src/server/service/variant_definition.rs +++ b/lib/sdf-server/src/server/service/variant_definition.rs @@ -311,7 +311,7 @@ pub async fn maybe_delete_schema_variant_connected_to_variant_def( for leaf_kind in LeafKind::iter() { let leaf_funcs = SchemaVariant::find_leaf_item_functions(ctx, *variant.id(), leaf_kind).await?; - for func in leaf_funcs { + for (_, func) in leaf_funcs { let input_locations = get_leaf_function_inputs(ctx, *func.id()).await?; leaf_func_migrations.push(LeafFuncMigration { func: func.to_owned(), diff --git a/lib/sdf-server/src/server/service/variant_definition/exec_variant_def.rs b/lib/sdf-server/src/server/service/variant_definition/exec_variant_def.rs index ba61d5bbc5..3c5169915d 100644 --- a/lib/sdf-server/src/server/service/variant_definition/exec_variant_def.rs +++ b/lib/sdf-server/src/server/service/variant_definition/exec_variant_def.rs @@ -18,7 +18,9 @@ use dal::{ Func, FuncBinding, HistoryActor, SchemaVariantId, StandardModel, User, WsEvent, }; use serde::{Deserialize, Serialize}; -use si_pkg::{FuncSpec, FuncSpecBackendKind, FuncSpecBackendResponseType, PkgSpec, SiPkg}; +use si_pkg::{ + FuncSpec, FuncSpecBackendKind, FuncSpecBackendResponseType, FuncSpecData, PkgSpec, SiPkg, +}; use std::collections::HashMap; pub type ExecVariantDefRequest = super::save_variant_def::SaveVariantDefRequest; @@ -90,28 +92,34 @@ pub async fn exec_variant_def( let identity_func_spec = IntrinsicFunc::Identity.to_spec()?; let mut schema_variant_func_spec = FuncSpec::builder(); - schema_variant_func_spec.name(String::from(asset_func.name())); - schema_variant_func_spec.backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition); - schema_variant_func_spec.response_type(FuncSpecBackendResponseType::SchemaVariantDefinition); - schema_variant_func_spec.hidden(asset_func.hidden()); + schema_variant_func_spec.name(asset_func.name()); + 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()) + .backend_kind(FuncSpecBackendKind::JsSchemaVariantDefinition) + .response_type(FuncSpecBackendResponseType::SchemaVariantDefinition) + .hidden(asset_func.hidden()); if let Some(code) = asset_func.code_plaintext()? { - schema_variant_func_spec.code_plaintext(code); + func_spec_data_builder.code_plaintext(code); } if let Some(handler) = asset_func.handler() { - schema_variant_func_spec.handler(handler.to_string()); + func_spec_data_builder.handler(handler.to_string()); } if let Some(description) = asset_func.description() { - schema_variant_func_spec.description(description.to_string()); + func_spec_data_builder.description(description.to_string()); } if let Some(display_name) = asset_func.display_name() { - schema_variant_func_spec.display_name(display_name.to_string()); + func_spec_data_builder.display_name(display_name.to_string()); } - let asset_func_built = schema_variant_func_spec.build()?; + let asset_func_built = schema_variant_func_spec + .data(func_spec_data_builder.build()?) + .build()?; let variant_spec = definition.to_spec( metadata.clone(), - identity_func_spec.unique_id, - asset_func_built.unique_id, + &identity_func_spec.unique_id, + &asset_func_built.unique_id, )?; let schema_spec = metadata.to_spec(variant_spec)?; let pkg_spec = PkgSpec::builder() @@ -131,7 +139,7 @@ pub async fn exec_variant_def( Some(dal::pkg::ImportOptions { schemas: None, skip_import_funcs: Some(HashMap::from_iter([( - asset_func_built.unique_id, + asset_func_built.unique_id.to_owned(), asset_func.clone(), )])), no_record: true, diff --git a/lib/si-pkg/src/lib.rs b/lib/si-pkg/src/lib.rs index 77d516913b..88a6b348f0 100644 --- a/lib/si-pkg/src/lib.rs +++ b/lib/si-pkg/src/lib.rs @@ -11,12 +11,14 @@ pub use spec::{ ActionFuncSpec, ActionFuncSpecBuilder, ActionFuncSpecKind, AttrFuncInputSpec, AttrFuncInputSpecKind, ChangeSetSpec, ChangeSetSpecBuilder, ChangeSetSpecStatus, FuncArgumentKind, FuncArgumentSpec, FuncArgumentSpecBuilder, FuncSpec, FuncSpecBackendKind, - FuncSpecBackendResponseType, FuncUniqueId, LeafFunctionSpec, LeafFunctionSpecBuilder, - LeafInputLocation, LeafKind, MapKeyFuncSpec, MapKeyFuncSpecBuilder, PkgSpec, PkgSpecBuilder, - PropSpec, PropSpecBuilder, PropSpecKind, PropSpecWidgetKind, SchemaSpec, SchemaSpecBuilder, - SchemaVariantSpec, SchemaVariantSpecBuilder, SchemaVariantSpecComponentType, + FuncSpecBackendResponseType, FuncSpecData, FuncSpecDataBuilder, LeafFunctionSpec, + LeafFunctionSpecBuilder, LeafInputLocation, LeafKind, MapKeyFuncSpec, MapKeyFuncSpecBuilder, + PkgSpec, PkgSpecBuilder, PropSpec, PropSpecBuilder, PropSpecKind, PropSpecWidgetKind, + SchemaSpec, SchemaSpecBuilder, SchemaSpecData, SchemaSpecDataBuilder, SchemaVariantSpec, + SchemaVariantSpecBuilder, SchemaVariantSpecComponentType, SchemaVariantSpecData, SchemaVariantSpecPropRoot, SiPropFuncSpec, SiPropFuncSpecBuilder, SiPropFuncSpecKind, - SocketSpec, SocketSpecArity, SocketSpecKind, SpecError, ValidationSpec, ValidationSpecKind, + SocketSpec, SocketSpecArity, SocketSpecData, SocketSpecDataBuilder, SocketSpecKind, SpecError, + ValidationSpec, ValidationSpecKind, }; #[cfg(test)] @@ -137,7 +139,7 @@ mod tests { assert_eq!(2, leaf_funcs.len()); for func in leaf_funcs { - assert!(funcs_by_unique_id.contains_key(&func.func_unique_id())); + assert!(funcs_by_unique_id.contains_key(&func.func_unique_id().to_string())); match func.leaf_kind() { LeafKind::Qualification => { assert_eq!( diff --git a/lib/si-pkg/src/node/action_func.rs b/lib/si-pkg/src/node/action_func.rs index a6e7ff5b51..ca2c497fab 100644 --- a/lib/si-pkg/src/node/action_func.rs +++ b/lib/si-pkg/src/node/action_func.rs @@ -8,17 +8,19 @@ use object_tree::{ ReadBytes, WriteBytes, }; -use crate::{ActionFuncSpec, ActionFuncSpecKind, FuncUniqueId}; +use crate::{ActionFuncSpec, ActionFuncSpecKind}; -use super::PkgNode; +use super::{read_common_fields, write_common_fields, PkgNode}; const KEY_KIND_STR: &str = "kind"; const KEY_FUNC_UNIQUE_ID_STR: &str = "func_unique_id"; #[derive(Clone, Debug)] pub struct ActionFuncNode { - pub func_unique_id: FuncUniqueId, + pub func_unique_id: String, pub kind: ActionFuncSpecKind, + pub unique_id: Option, + pub deleted: bool, } impl WriteBytes for ActionFuncNode { @@ -31,6 +33,8 @@ impl WriteBytes for ActionFuncNode { self.func_unique_id.to_string(), )?; + write_common_fields(writer, self.unique_id.as_deref(), self.deleted)?; + Ok(()) } } @@ -43,13 +47,15 @@ impl ReadBytes for ActionFuncNode { let kind_str = read_key_value_line(reader, KEY_KIND_STR)?; let kind = ActionFuncSpecKind::from_str(&kind_str).map_err(GraphError::parse)?; - let func_unique_id_str = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; - let func_unique_id = - FuncUniqueId::from_str(&func_unique_id_str).map_err(GraphError::parse)?; + let func_unique_id = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; + + let (unique_id, deleted) = read_common_fields(reader)?; Ok(Self { kind, func_unique_id, + unique_id, + deleted, }) } } @@ -61,8 +67,10 @@ impl NodeChild for ActionFuncSpec { NodeWithChildren::new( NodeKind::Leaf, Self::NodeType::ActionFunc(ActionFuncNode { - func_unique_id: self.func_unique_id, + func_unique_id: self.func_unique_id.to_owned(), kind: self.kind, + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }), vec![], ) diff --git a/lib/si-pkg/src/node/attr_func_input.rs b/lib/si-pkg/src/node/attr_func_input.rs index 77f3fa7fe7..146db2249b 100644 --- a/lib/si-pkg/src/node/attr_func_input.rs +++ b/lib/si-pkg/src/node/attr_func_input.rs @@ -10,7 +10,7 @@ use object_tree::{ use crate::{AttrFuncInputSpec, AttrFuncInputSpecKind}; -use super::PkgNode; +use super::{read_common_fields, write_common_fields, PkgNode}; const KEY_KIND_STR: &str = "kind"; const KEY_NAME_STR: &str = "name"; @@ -20,9 +20,24 @@ const KEY_SOCKET_NAME_STR: &str = "socket_name"; #[remain::sorted] #[derive(Clone, Debug)] pub enum AttrFuncInputNode { - InputSocket { name: String, socket_name: String }, - OutputSocket { name: String, socket_name: String }, - Prop { name: String, prop_path: String }, + InputSocket { + name: String, + socket_name: String, + unique_id: Option, + deleted: bool, + }, + OutputSocket { + name: String, + socket_name: String, + unique_id: Option, + deleted: bool, + }, + Prop { + name: String, + prop_path: String, + unique_id: Option, + deleted: bool, + }, } impl NameStr for AttrFuncInputNode { @@ -59,6 +74,20 @@ impl WriteBytes for AttrFuncInputNode { } } + match self { + Self::InputSocket { + unique_id, deleted, .. + } + | Self::OutputSocket { + unique_id, deleted, .. + } + | Self::Prop { + unique_id, deleted, .. + } => { + write_common_fields(writer, unique_id.as_deref(), *deleted)?; + } + } + Ok(()) } } @@ -72,18 +101,37 @@ impl ReadBytes for AttrFuncInputNode { let kind_str = read_key_value_line(reader, KEY_KIND_STR)?; let kind = AttrFuncInputSpecKind::from_str(&kind_str).map_err(GraphError::parse)?; + Ok(match kind { AttrFuncInputSpecKind::Prop => { let prop_path = read_key_value_line(reader, KEY_PROP_PATH_STR)?; - Self::Prop { name, prop_path } + let (unique_id, deleted) = read_common_fields(reader)?; + Self::Prop { + name, + prop_path, + unique_id, + deleted, + } } AttrFuncInputSpecKind::InputSocket => { let socket_name = read_key_value_line(reader, KEY_SOCKET_NAME_STR)?; - Self::InputSocket { name, socket_name } + let (unique_id, deleted) = read_common_fields(reader)?; + Self::InputSocket { + name, + socket_name, + unique_id, + deleted, + } } AttrFuncInputSpecKind::OutputSocket => { let socket_name = read_key_value_line(reader, KEY_SOCKET_NAME_STR)?; - Self::OutputSocket { name, socket_name } + let (unique_id, deleted) = read_common_fields(reader)?; + Self::OutputSocket { + name, + socket_name, + unique_id, + deleted, + } } }) } @@ -95,23 +143,40 @@ impl NodeChild for AttrFuncInputSpec { fn as_node_with_children(&self) -> NodeWithChildren { NodeWithChildren::new( NodeKind::Leaf, - Self::NodeType::AttrFuncInput(match self { - AttrFuncInputSpec::Prop { name, prop_path } => AttrFuncInputNode::Prop { - name: name.clone(), - prop_path: prop_path.clone(), + Self::NodeType::AttrFuncInput(match self.to_owned() { + AttrFuncInputSpec::Prop { + name, + prop_path, + unique_id, + deleted, + } => AttrFuncInputNode::Prop { + name, + prop_path, + unique_id, + deleted, + }, + AttrFuncInputSpec::InputSocket { + name, + socket_name, + unique_id, + deleted, + } => AttrFuncInputNode::InputSocket { + name, + socket_name, + unique_id, + deleted, + }, + AttrFuncInputSpec::OutputSocket { + name, + socket_name, + unique_id, + deleted, + } => AttrFuncInputNode::OutputSocket { + name, + socket_name, + unique_id, + deleted, }, - AttrFuncInputSpec::InputSocket { name, socket_name } => { - AttrFuncInputNode::InputSocket { - name: name.clone(), - socket_name: socket_name.clone(), - } - } - AttrFuncInputSpec::OutputSocket { name, socket_name } => { - AttrFuncInputNode::OutputSocket { - name: name.clone(), - socket_name: socket_name.clone(), - } - } }), vec![], ) diff --git a/lib/si-pkg/src/node/func.rs b/lib/si-pkg/src/node/func.rs index 03b8108884..7a6f074ee8 100644 --- a/lib/si-pkg/src/node/func.rs +++ b/lib/si-pkg/src/node/func.rs @@ -5,13 +5,13 @@ use std::{ use url::Url; use object_tree::{ - read_key_value_line, write_key_value_line, GraphError, NameStr, NodeChild, NodeKind, - NodeWithChildren, ReadBytes, WriteBytes, + read_key_value_line, read_key_value_line_opt, write_key_value_line, GraphError, NameStr, + NodeChild, NodeKind, NodeWithChildren, ReadBytes, WriteBytes, }; -use crate::spec::{FuncSpec, FuncSpecBackendKind, FuncSpecBackendResponseType, FuncUniqueId}; +use crate::spec::{FuncSpec, FuncSpecBackendKind, FuncSpecBackendResponseType}; -use super::PkgNode; +use super::{read_common_fields, write_common_fields, PkgNode}; const KEY_NAME_STR: &str = "name"; const KEY_DISPLAY_NAME_STR: &str = "display_name"; @@ -22,10 +22,9 @@ const KEY_BACKEND_KIND_STR: &str = "backend_kind"; const KEY_RESPONSE_TYPE_STR: &str = "response_type"; const KEY_HIDDEN_STR: &str = "hidden"; const KEY_LINK_STR: &str = "link"; -const KEY_UNIQUE_ID_STR: &str = "unique_id"; #[derive(Clone, Debug)] -pub struct FuncNode { +pub struct FuncData { pub name: String, pub display_name: Option, pub description: Option, @@ -35,7 +34,14 @@ pub struct FuncNode { pub response_type: FuncSpecBackendResponseType, pub hidden: bool, pub link: Option, - pub unique_id: FuncUniqueId, +} + +#[derive(Clone, Debug)] +pub struct FuncNode { + pub name: String, + pub data: Option, + pub unique_id: String, + pub deleted: bool, } impl NameStr for FuncNode { @@ -47,27 +53,30 @@ impl NameStr for FuncNode { impl WriteBytes for FuncNode { fn write_bytes(&self, writer: &mut W) -> Result<(), GraphError> { write_key_value_line(writer, KEY_NAME_STR, self.name())?; - write_key_value_line( - writer, - KEY_DISPLAY_NAME_STR, - self.display_name.as_deref().unwrap_or(""), - )?; - write_key_value_line( - writer, - KEY_DESCRIPTION_STR, - self.description.as_deref().unwrap_or(""), - )?; - write_key_value_line(writer, KEY_HANDLER_STR, &self.handler)?; - write_key_value_line(writer, KEY_CODE_STR, &self.code_base64)?; - write_key_value_line(writer, KEY_BACKEND_KIND_STR, self.backend_kind)?; - write_key_value_line(writer, KEY_RESPONSE_TYPE_STR, self.response_type)?; - write_key_value_line(writer, KEY_HIDDEN_STR, self.hidden)?; - write_key_value_line( - writer, - KEY_LINK_STR, - self.link.as_ref().map(|l| l.as_str()).unwrap_or(""), - )?; - write_key_value_line(writer, KEY_UNIQUE_ID_STR, self.unique_id.to_string())?; + if let Some(data) = &self.data { + write_key_value_line( + writer, + KEY_DISPLAY_NAME_STR, + data.display_name.as_deref().unwrap_or(""), + )?; + write_key_value_line( + writer, + KEY_DESCRIPTION_STR, + data.description.as_deref().unwrap_or(""), + )?; + write_key_value_line(writer, KEY_HANDLER_STR, &data.handler)?; + write_key_value_line(writer, KEY_CODE_STR, &data.code_base64)?; + write_key_value_line(writer, KEY_BACKEND_KIND_STR, data.backend_kind)?; + write_key_value_line(writer, KEY_RESPONSE_TYPE_STR, data.response_type)?; + write_key_value_line(writer, KEY_HIDDEN_STR, data.hidden)?; + write_key_value_line( + writer, + KEY_LINK_STR, + data.link.as_ref().map(|l| l.as_str()).unwrap_or(""), + )?; + } + + write_common_fields(writer, Some(self.unique_id.as_str()), self.deleted)?; Ok(()) } @@ -79,48 +88,58 @@ impl ReadBytes for FuncNode { Self: std::marker::Sized, { let name = read_key_value_line(reader, KEY_NAME_STR)?; - let display_name_str = read_key_value_line(reader, KEY_DISPLAY_NAME_STR)?; - let display_name = if display_name_str.is_empty() { - None - } else { - Some(display_name_str) - }; - let description_str = read_key_value_line(reader, KEY_DESCRIPTION_STR)?; - let description = if description_str.is_empty() { - None - } else { - Some(description_str) - }; - let handler = read_key_value_line(reader, KEY_HANDLER_STR)?; - let code_base64 = read_key_value_line(reader, KEY_CODE_STR)?; - let backend_kind_str = read_key_value_line(reader, KEY_BACKEND_KIND_STR)?; - let backend_kind = - FuncSpecBackendKind::from_str(&backend_kind_str).map_err(GraphError::parse)?; - let response_type_str = read_key_value_line(reader, KEY_RESPONSE_TYPE_STR)?; - let response_type = - FuncSpecBackendResponseType::from_str(&response_type_str).map_err(GraphError::parse)?; - let hidden = bool::from_str(&read_key_value_line(reader, KEY_HIDDEN_STR)?) - .map_err(GraphError::parse)?; - let link_str = read_key_value_line(reader, KEY_LINK_STR)?; - let link = if link_str.is_empty() { - None - } else { - Some(Url::parse(&link_str).map_err(GraphError::parse)?) + let data = match read_key_value_line_opt(reader, KEY_DISPLAY_NAME_STR)? { + None => None, + Some(display_name_str) => { + let display_name = if display_name_str.is_empty() { + None + } else { + Some(display_name_str) + }; + let description_str = read_key_value_line(reader, KEY_DESCRIPTION_STR)?; + let description = if description_str.is_empty() { + None + } else { + Some(description_str) + }; + let handler = read_key_value_line(reader, KEY_HANDLER_STR)?; + let code_base64 = read_key_value_line(reader, KEY_CODE_STR)?; + let backend_kind_str = read_key_value_line(reader, KEY_BACKEND_KIND_STR)?; + let backend_kind = + FuncSpecBackendKind::from_str(&backend_kind_str).map_err(GraphError::parse)?; + let response_type_str = read_key_value_line(reader, KEY_RESPONSE_TYPE_STR)?; + let response_type = FuncSpecBackendResponseType::from_str(&response_type_str) + .map_err(GraphError::parse)?; + let hidden = bool::from_str(&read_key_value_line(reader, KEY_HIDDEN_STR)?) + .map_err(GraphError::parse)?; + let link_str = read_key_value_line(reader, KEY_LINK_STR)?; + let link = if link_str.is_empty() { + None + } else { + Some(Url::parse(&link_str).map_err(GraphError::parse)?) + }; + + Some(FuncData { + name: name.clone(), + display_name, + description, + handler, + code_base64, + backend_kind, + response_type, + hidden, + link, + }) + } }; - let unique_id_str = read_key_value_line(reader, KEY_UNIQUE_ID_STR)?; - let unique_id = FuncUniqueId::from_str(&unique_id_str).map_err(GraphError::parse)?; + + let (unique_id, deleted) = read_common_fields(reader)?; Ok(Self { name, - display_name, - description, - handler, - code_base64, - backend_kind, - response_type, - hidden, - link, - unique_id, + data, + unique_id: unique_id.unwrap_or("".into()), + deleted, }) } } @@ -138,16 +157,20 @@ impl NodeChild for FuncSpec { NodeWithChildren::new( NodeKind::Tree, Self::NodeType::Func(FuncNode { - name: self.name.to_string(), - display_name: self.display_name.as_ref().cloned(), - description: self.description.as_ref().cloned(), - handler: self.handler.to_string(), - code_base64: self.code_base64.to_string(), - backend_kind: self.backend_kind, - response_type: self.response_type, - hidden: self.hidden, - link: self.link.as_ref().cloned(), - unique_id: self.unique_id, + name: self.name.to_owned(), + data: self.data.as_ref().map(|data| FuncData { + name: self.name.to_owned(), + display_name: data.display_name.as_ref().cloned(), + description: data.description.as_ref().cloned(), + handler: data.handler.to_string(), + code_base64: data.code_base64.to_string(), + backend_kind: data.backend_kind, + response_type: data.response_type, + hidden: data.hidden, + link: data.link.as_ref().cloned(), + }), + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }), children, ) diff --git a/lib/si-pkg/src/node/func_argument.rs b/lib/si-pkg/src/node/func_argument.rs index 02dafd0d07..efaaf64efe 100644 --- a/lib/si-pkg/src/node/func_argument.rs +++ b/lib/si-pkg/src/node/func_argument.rs @@ -1,4 +1,4 @@ -use super::PkgNode; +use super::{read_common_fields, write_common_fields, PkgNode}; use crate::spec::{FuncArgumentKind, FuncArgumentSpec}; use object_tree::{ read_key_value_line, write_key_value_line, GraphError, NameStr, NodeChild, NodeKind, @@ -16,6 +16,8 @@ pub struct FuncArgumentNode { pub name: String, pub kind: FuncArgumentKind, pub element_kind: Option, + pub unique_id: Option, + pub deleted: bool, } impl NameStr for FuncArgumentNode { @@ -37,6 +39,8 @@ impl WriteBytes for FuncArgumentNode { .unwrap_or("".to_string()), )?; + write_common_fields(writer, self.unique_id.as_deref(), self.deleted)?; + Ok(()) } } @@ -57,10 +61,14 @@ impl ReadBytes for FuncArgumentNode { Some(FuncArgumentKind::from_str(&element_kind_str).map_err(GraphError::parse)?) }; + let (unique_id, deleted) = read_common_fields(reader)?; + Ok(Self { name, kind, element_kind, + unique_id, + deleted, }) } } @@ -74,7 +82,9 @@ impl NodeChild for FuncArgumentSpec { Self::NodeType::FuncArgument(FuncArgumentNode { name: self.name.to_string(), kind: self.kind, - element_kind: self.element_kind.as_ref().cloned(), + element_kind: self.element_kind.to_owned(), + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }), vec![], ) diff --git a/lib/si-pkg/src/node/leaf_function.rs b/lib/si-pkg/src/node/leaf_function.rs index 15e7303d0a..61a3b7d49e 100644 --- a/lib/si-pkg/src/node/leaf_function.rs +++ b/lib/si-pkg/src/node/leaf_function.rs @@ -8,9 +8,9 @@ use object_tree::{ ReadBytes, WriteBytes, }; -use crate::{FuncUniqueId, LeafFunctionSpec, LeafInputLocation, LeafKind}; +use crate::{LeafFunctionSpec, LeafInputLocation, LeafKind}; -use super::PkgNode; +use super::{read_common_fields, write_common_fields, PkgNode}; const FUNC_UNIQUE_ID_STR: &str = "func_unique_id"; const LEAF_KIND_STR: &str = "leaf_kind"; @@ -21,12 +21,14 @@ const INPUT_RESOURCE_STR: &str = "input_resource"; #[derive(Clone, Debug)] pub struct LeafFunctionNode { - pub func_unique_id: FuncUniqueId, + pub func_unique_id: String, pub leaf_kind: LeafKind, pub input_code: bool, pub input_deleted_at: bool, pub input_domain: bool, pub input_resource: bool, + pub unique_id: Option, + pub deleted: bool, } impl WriteBytes for LeafFunctionNode { @@ -38,6 +40,8 @@ impl WriteBytes for LeafFunctionNode { write_key_value_line(writer, INPUT_DELETED_AT_STR, self.input_deleted_at)?; write_key_value_line(writer, INPUT_RESOURCE_STR, self.input_resource)?; + write_common_fields(writer, self.unique_id.as_deref(), self.deleted)?; + Ok(()) } } @@ -49,9 +53,7 @@ impl ReadBytes for LeafFunctionNode { { let leaf_kind_str = read_key_value_line(reader, LEAF_KIND_STR)?; let leaf_kind = LeafKind::from_str(&leaf_kind_str).map_err(GraphError::parse)?; - let func_unique_id_str = read_key_value_line(reader, FUNC_UNIQUE_ID_STR)?; - let func_unique_id = - FuncUniqueId::from_str(&func_unique_id_str).map_err(GraphError::parse)?; + let func_unique_id = read_key_value_line(reader, FUNC_UNIQUE_ID_STR)?; let input_code = bool::from_str(&read_key_value_line(reader, INPUT_CODE_STR)?) .map_err(GraphError::parse)?; let input_domain = bool::from_str(&read_key_value_line(reader, INPUT_DOMAIN_STR)?) @@ -61,6 +63,8 @@ impl ReadBytes for LeafFunctionNode { let input_resource = bool::from_str(&read_key_value_line(reader, INPUT_RESOURCE_STR)?) .map_err(GraphError::parse)?; + let (unique_id, deleted) = read_common_fields(reader)?; + Ok(Self { func_unique_id, leaf_kind, @@ -68,6 +72,8 @@ impl ReadBytes for LeafFunctionNode { input_domain, input_deleted_at, input_resource, + unique_id, + deleted, }) } } @@ -79,12 +85,14 @@ impl NodeChild for LeafFunctionSpec { NodeWithChildren::new( NodeKind::Leaf, Self::NodeType::LeafFunction(LeafFunctionNode { - func_unique_id: self.func_unique_id, + func_unique_id: self.func_unique_id.to_owned(), leaf_kind: self.leaf_kind, input_code: self.inputs.contains(&LeafInputLocation::Code), input_deleted_at: self.inputs.contains(&LeafInputLocation::DeletedAt), input_domain: self.inputs.contains(&LeafInputLocation::Domain), input_resource: self.inputs.contains(&LeafInputLocation::Resource), + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }), vec![], ) diff --git a/lib/si-pkg/src/node/map_key_func.rs b/lib/si-pkg/src/node/map_key_func.rs index 5671386434..97f59422c1 100644 --- a/lib/si-pkg/src/node/map_key_func.rs +++ b/lib/si-pkg/src/node/map_key_func.rs @@ -1,14 +1,11 @@ -use std::{ - io::{BufRead, Write}, - str::FromStr, -}; +use std::io::{BufRead, Write}; use object_tree::{ read_key_value_line, write_key_value_line, GraphError, NodeChild, NodeKind, NodeWithChildren, ReadBytes, WriteBytes, }; -use crate::{FuncUniqueId, MapKeyFuncSpec}; +use crate::MapKeyFuncSpec; use super::PkgNode; @@ -18,7 +15,7 @@ const KEY_FUNC_UNIQUE_ID_STR: &str = "func_unique_id"; #[derive(Clone, Debug)] pub struct MapKeyFuncNode { pub key: String, - pub func_unique_id: FuncUniqueId, + pub func_unique_id: String, } impl WriteBytes for MapKeyFuncNode { @@ -40,9 +37,7 @@ impl ReadBytes for MapKeyFuncNode { Self: std::marker::Sized, { let key = read_key_value_line(reader, KEY_KEY_STR)?; - let func_unique_id_str = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; - let func_unique_id = - FuncUniqueId::from_str(&func_unique_id_str).map_err(GraphError::parse)?; + let func_unique_id = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; Ok(Self { key, @@ -59,7 +54,7 @@ impl NodeChild for MapKeyFuncSpec { NodeKind::Tree, Self::NodeType::MapKeyFunc(MapKeyFuncNode { key: self.key.to_owned(), - func_unique_id: self.func_unique_id, + func_unique_id: self.func_unique_id.to_owned(), }), self.inputs .iter() diff --git a/lib/si-pkg/src/node/mod.rs b/lib/si-pkg/src/node/mod.rs index c52fbb9f3a..a486d40cc6 100644 --- a/lib/si-pkg/src/node/mod.rs +++ b/lib/si-pkg/src/node/mod.rs @@ -1,7 +1,11 @@ -use std::io::{BufRead, Write}; +use std::{ + io::{BufRead, Write}, + str::FromStr, +}; use object_tree::{ - read_key_value_line, write_key_value_line, GraphError, NameStr, ReadBytes, WriteBytes, + read_key_value_line, read_key_value_line_opt, write_key_value_line, GraphError, NameStr, + ReadBytes, WriteBytes, }; mod action_func; @@ -34,7 +38,7 @@ pub(crate) use self::{ leaf_function::LeafFunctionNode, map_key_func::MapKeyFuncNode, package::PackageNode, - prop::PropNode, + prop::{PropNode, PropNodeData}, prop_child::PropChildNode, schema::SchemaNode, schema_variant::SchemaVariantNode, @@ -65,6 +69,47 @@ const NODE_KIND_VALIDATION: &str = "validation"; const KEY_NODE_KIND_STR: &str = "node_kind"; +const KEY_UNIQUE_ID_STR: &str = "unique_id"; +const KEY_DELETED_STR: &str = "deleted"; + +pub(crate) fn read_unique_id(reader: &mut R) -> Result, GraphError> { + let unique_id_opt_str = read_key_value_line_opt(reader, KEY_UNIQUE_ID_STR)?; + Ok(unique_id_opt_str.and_then(|unique_id| { + if unique_id.is_empty() { + None + } else { + Some(unique_id) + } + })) +} + +fn read_common_fields(reader: &mut R) -> Result<(Option, bool), GraphError> { + let unique_id = read_unique_id(reader)?; + + let deleted = match read_key_value_line_opt(reader, KEY_DELETED_STR)? { + None => false, + Some(deleted_str) => bool::from_str(&deleted_str).map_err(GraphError::parse)?, + }; + + Ok((unique_id, deleted)) +} + +fn write_unique_id(writer: &mut W, unique_id: Option<&str>) -> Result<(), GraphError> { + write_key_value_line(writer, KEY_UNIQUE_ID_STR, unique_id.unwrap_or(""))?; + Ok(()) +} + +fn write_common_fields( + writer: &mut W, + unique_id: Option<&str>, + deleted: bool, +) -> Result<(), GraphError> { + write_unique_id(writer, unique_id)?; + write_key_value_line(writer, KEY_DELETED_STR, deleted)?; + + Ok(()) +} + #[remain::sorted] #[derive(Clone, Debug)] pub enum PkgNode { diff --git a/lib/si-pkg/src/node/package.rs b/lib/si-pkg/src/node/package.rs index d5901fbb51..7189f03606 100644 --- a/lib/si-pkg/src/node/package.rs +++ b/lib/si-pkg/src/node/package.rs @@ -20,6 +20,7 @@ const KEY_DESCRIPTION_STR: &str = "description"; const KEY_KIND_STR: &str = "kind"; const KEY_NAME_STR: &str = "name"; const KEY_VERSION_STR: &str = "version"; +const KEY_WORKSPACE_PK_STR: &str = "workspace_pk"; #[derive(Clone, Debug)] pub struct PackageNode { @@ -31,6 +32,7 @@ pub struct PackageNode { pub created_at: DateTime, pub created_by: String, pub default_change_set: Option, + pub workspace_pk: Option, } impl NameStr for PackageNode { @@ -50,6 +52,9 @@ impl WriteBytes for PackageNode { if let Some(default_change_set) = &self.default_change_set { write_key_value_line(writer, KEY_DEFAULT_CHANGE_SET, default_change_set.as_str())?; } + if let Some(workspace_pk) = &self.workspace_pk { + write_key_value_line(writer, KEY_WORKSPACE_PK_STR, workspace_pk.as_str())?; + } Ok(()) } } @@ -72,6 +77,7 @@ impl ReadBytes for PackageNode { .map_err(GraphError::parse)?; let created_by = read_key_value_line(reader, KEY_CREATED_BY_STR)?; let default_change_set = read_key_value_line_opt(reader, KEY_DEFAULT_CHANGE_SET)?; + let workspace_pk = read_key_value_line_opt(reader, KEY_WORKSPACE_PK_STR)?; Ok(Self { kind, @@ -81,6 +87,7 @@ impl ReadBytes for PackageNode { created_at, created_by, default_change_set, + workspace_pk, }) } } @@ -97,8 +104,9 @@ impl NodeChild for PkgSpec { version: self.version.to_string(), description: self.description.to_string(), created_at: self.created_at, - created_by: self.created_by.clone(), - default_change_set: self.default_change_set.clone(), + created_by: self.created_by.to_owned(), + default_change_set: self.default_change_set.to_owned(), + workspace_pk: self.workspace_pk.to_owned(), }), match self.kind { SiPkgKind::Module => vec![ diff --git a/lib/si-pkg/src/node/prop.rs b/lib/si-pkg/src/node/prop.rs index c60732aff7..b8b3e376d1 100644 --- a/lib/si-pkg/src/node/prop.rs +++ b/lib/si-pkg/src/node/prop.rs @@ -6,11 +6,11 @@ use std::{ use url::Url; use object_tree::{ - read_key_value_line, write_key_value_line, GraphError, NameStr, NodeChild, NodeKind, - NodeWithChildren, ReadBytes, WriteBytes, + read_key_value_line, read_key_value_line_opt, write_key_value_line, GraphError, NameStr, + NodeChild, NodeKind, NodeWithChildren, ReadBytes, WriteBytes, }; -use crate::{FuncUniqueId, PropSpec, PropSpecWidgetKind}; +use crate::{spec::PropSpecData, PropSpec, PropSpecWidgetKind}; use super::{prop_child::PropChild, PkgNode}; @@ -22,6 +22,7 @@ const KEY_WIDGET_KIND_STR: &str = "widget_kind"; const KEY_WIDGET_OPTIONS_STR: &str = "widget_options"; const KEY_HIDDEN_STR: &str = "hidden"; const KEY_DOC_LINK_STR: &str = "doc_link"; +const KEY_UNIQUE_ID_STR: &str = "unique_id"; const PROP_TY_STRING: &str = "string"; const PROP_TY_INTEGER: &str = "integer"; @@ -30,62 +31,49 @@ const PROP_TY_MAP: &str = "map"; const PROP_TY_ARRAY: &str = "array"; const PROP_TY_OBJECT: &str = "object"; +#[derive(Clone, Debug)] +pub struct PropNodeData { + pub name: String, + pub func_unique_id: Option, + pub default_value: Option, + pub widget_kind: PropSpecWidgetKind, + pub widget_options: Option, + pub doc_link: Option, + pub hidden: bool, +} + #[remain::sorted] #[derive(Clone, Debug)] pub enum PropNode { Array { name: String, - func_unique_id: Option, - default_value: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - doc_link: Option, - hidden: bool, + data: Option, + unique_id: Option, }, Boolean { name: String, - func_unique_id: Option, - default_value: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - doc_link: Option, - hidden: bool, + data: Option, + unique_id: Option, }, Integer { name: String, - func_unique_id: Option, - default_value: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - hidden: bool, - doc_link: Option, + data: Option, + unique_id: Option, }, Map { name: String, - func_unique_id: Option, - default_value: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - doc_link: Option, - hidden: bool, + data: Option, + unique_id: Option, }, Object { name: String, - func_unique_id: Option, - default_value: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - doc_link: Option, - hidden: bool, + data: Option, + unique_id: Option, }, String { name: String, - func_unique_id: Option, - default_value: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - hidden: bool, - doc_link: Option, + data: Option, + unique_id: Option, }, } @@ -120,103 +108,62 @@ impl WriteBytes for PropNode { write_key_value_line(writer, KEY_KIND_STR, self.kind_str())?; write_key_value_line(writer, KEY_NAME_STR, self.name())?; - let func_unique_id = match &self { - Self::String { func_unique_id, .. } - | Self::Integer { func_unique_id, .. } - | Self::Boolean { func_unique_id, .. } - | Self::Map { func_unique_id, .. } - | Self::Array { func_unique_id, .. } - | Self::Object { func_unique_id, .. } => func_unique_id, - }; - write_key_value_line( - writer, - KEY_FUNC_UNIQUE_ID_STR, - func_unique_id - .map(|fuid| fuid.to_string()) - .unwrap_or("".to_string()), - )?; - - write_key_value_line( - writer, - KEY_DEFAULT_VALUE_STR, - match &self { - Self::String { default_value, .. } => match default_value { - Some(dv) => serde_json::to_string(dv).map_err(GraphError::parse)?, - None => "".to_string(), - }, - Self::Integer { default_value, .. } => match default_value { - Some(dv) => serde_json::to_string(dv).map_err(GraphError::parse)?, - None => "".to_string(), - }, - Self::Boolean { default_value, .. } => match default_value { - Some(dv) => serde_json::to_string(dv).map_err(GraphError::parse)?, - None => "".to_string(), - }, - Self::Map { default_value, .. } - | Self::Array { default_value, .. } - | Self::Object { default_value, .. } => match default_value { + if let Some(data) = match &self { + Self::String { data, .. } + | Self::Integer { data, .. } + | Self::Boolean { data, .. } + | Self::Map { data, .. } + | Self::Array { data, .. } + | Self::Object { data, .. } => data, + } { + write_key_value_line( + writer, + KEY_FUNC_UNIQUE_ID_STR, + data.func_unique_id + .as_ref() + .map(|fuid| fuid.to_owned()) + .unwrap_or("".to_string()), + )?; + + write_key_value_line( + writer, + KEY_DEFAULT_VALUE_STR, + match &data.default_value { Some(dv) => serde_json::to_string(dv).map_err(GraphError::parse)?, None => "".to_string(), }, - }, - )?; - - write_key_value_line( - writer, - KEY_WIDGET_KIND_STR, - match &self { - Self::String { widget_kind, .. } - | Self::Integer { widget_kind, .. } - | Self::Boolean { widget_kind, .. } - | Self::Map { widget_kind, .. } - | Self::Array { widget_kind, .. } - | Self::Object { widget_kind, .. } => widget_kind, - }, - )?; - - write_key_value_line( - writer, - KEY_WIDGET_OPTIONS_STR, - match &self { - Self::String { widget_options, .. } - | Self::Integer { widget_options, .. } - | Self::Boolean { widget_options, .. } - | Self::Map { widget_options, .. } - | Self::Array { widget_options, .. } - | Self::Object { widget_options, .. } => match widget_options { + )?; + + write_key_value_line(writer, KEY_WIDGET_KIND_STR, data.widget_kind)?; + + write_key_value_line( + writer, + KEY_WIDGET_OPTIONS_STR, + match &data.widget_options { Some(options) => serde_json::to_string(options).map_err(GraphError::parse)?, None => "".to_string(), }, - }, - )?; - - write_key_value_line( - writer, - KEY_HIDDEN_STR, - match &self { - Self::String { hidden, .. } - | Self::Integer { hidden, .. } - | Self::Boolean { hidden, .. } - | Self::Map { hidden, .. } - | Self::Array { hidden, .. } - | Self::Object { hidden, .. } => hidden, - }, - )?; - - write_key_value_line( - writer, - KEY_DOC_LINK_STR, - match &self { - Self::String { doc_link, .. } - | Self::Integer { doc_link, .. } - | Self::Boolean { doc_link, .. } - | Self::Map { doc_link, .. } - | Self::Array { doc_link, .. } - | Self::Object { doc_link, .. } => { - doc_link.as_ref().map(|l| l.as_str()).unwrap_or("") - } - }, - )?; + )?; + + write_key_value_line(writer, KEY_HIDDEN_STR, data.hidden)?; + + write_key_value_line( + writer, + KEY_DOC_LINK_STR, + data.doc_link.as_ref().map(|l| l.as_str()).unwrap_or(""), + )?; + } + + if let Some(unique_id) = match &self { + Self::String { unique_id, .. } + | Self::Integer { unique_id, .. } + | Self::Boolean { unique_id, .. } + | Self::Map { unique_id, .. } + | Self::Array { unique_id, .. } + | Self::Object { unique_id, .. } => unique_id.as_deref(), + } { + write_key_value_line(writer, KEY_UNIQUE_ID_STR, unique_id)?; + } Ok(()) } @@ -229,127 +176,87 @@ impl ReadBytes for PropNode { { let kind_str = read_key_value_line(reader, KEY_KIND_STR)?; let name = read_key_value_line(reader, KEY_NAME_STR)?; - let func_unique_id_str = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; - let func_unique_id = if func_unique_id_str.is_empty() { - None - } else { - Some(FuncUniqueId::from_str(&func_unique_id_str).map_err(GraphError::parse)?) - }; - let default_value_str = read_key_value_line(reader, KEY_DEFAULT_VALUE_STR)?; - let default_value_json: Option = if default_value_str.is_empty() { - None - } else { - Some(serde_json::from_str(&default_value_str).map_err(GraphError::parse)?) - }; + let data = match read_key_value_line_opt(reader, KEY_FUNC_UNIQUE_ID_STR)? { + None => None, + Some(func_unique_id_str) => { + let func_unique_id = if func_unique_id_str.is_empty() { + None + } else { + Some(func_unique_id_str) + }; - let widget_kind_str = read_key_value_line(reader, KEY_WIDGET_KIND_STR)?; - let widget_kind = - PropSpecWidgetKind::from_str(&widget_kind_str).map_err(GraphError::parse)?; + let default_value_str = read_key_value_line(reader, KEY_DEFAULT_VALUE_STR)?; + let default_value: Option = if default_value_str.is_empty() { + None + } else { + Some(serde_json::from_str(&default_value_str).map_err(GraphError::parse)?) + }; - let widget_options_str = read_key_value_line(reader, KEY_WIDGET_OPTIONS_STR)?; - let widget_options = if widget_options_str.is_empty() { - None - } else { - serde_json::from_str(&widget_options_str).map_err(GraphError::parse)? - }; - let hidden = bool::from_str(&read_key_value_line(reader, KEY_HIDDEN_STR)?) - .map_err(GraphError::parse)?; - - let doc_link_str = read_key_value_line(reader, KEY_DOC_LINK_STR)?; - let doc_link = if doc_link_str.is_empty() { - None - } else { - Some(Url::parse(&doc_link_str).map_err(GraphError::parse)?) + let widget_kind_str = read_key_value_line(reader, KEY_WIDGET_KIND_STR)?; + let widget_kind = + PropSpecWidgetKind::from_str(&widget_kind_str).map_err(GraphError::parse)?; + + let widget_options_str = read_key_value_line(reader, KEY_WIDGET_OPTIONS_STR)?; + let widget_options = if widget_options_str.is_empty() { + None + } else { + serde_json::from_str(&widget_options_str).map_err(GraphError::parse)? + }; + let hidden = bool::from_str(&read_key_value_line(reader, KEY_HIDDEN_STR)?) + .map_err(GraphError::parse)?; + + let doc_link_str = read_key_value_line(reader, KEY_DOC_LINK_STR)?; + let doc_link = if doc_link_str.is_empty() { + None + } else { + Some(Url::parse(&doc_link_str).map_err(GraphError::parse)?) + }; + + Some(PropNodeData { + name: name.to_owned(), + func_unique_id, + default_value, + widget_kind, + widget_options, + doc_link, + hidden, + }) + } }; + let unique_id = read_key_value_line_opt(reader, KEY_UNIQUE_ID_STR)?; + let node = match kind_str.as_str() { PROP_TY_STRING => Self::String { name, - default_value: match default_value_json { - None => None, - Some(value) => { - if value.is_string() { - value.as_str().map(|s| s.to_owned()) - } else { - return Err(GraphError::parse_custom( - "String prop must get a string as a default value", - )); - } - } - }, - func_unique_id, - widget_kind, - widget_options, - hidden, - doc_link, + data, + unique_id, }, PROP_TY_INTEGER => Self::Integer { name, - default_value: match default_value_json { - None => None, - Some(value) => { - if value.is_i64() { - value.as_i64() - } else { - return Err(GraphError::parse_custom( - "Integer prop must get an i64 as a default value", - )); - } - } - }, - func_unique_id, - widget_kind, - widget_options, - hidden, - doc_link, + data, + unique_id, }, PROP_TY_BOOLEAN => Self::Boolean { name, - default_value: match default_value_json { - None => None, - Some(value) => { - if value.is_boolean() { - value.as_bool() - } else { - return Err(GraphError::parse_custom( - "Boolean prop must get a bool as a default value", - )); - } - } - }, - func_unique_id, - widget_kind, - widget_options, - hidden, - doc_link, + data, + unique_id, }, PROP_TY_MAP => Self::Map { name, - default_value: default_value_json, - func_unique_id, - widget_kind, - widget_options, - hidden, - doc_link, + data, + unique_id, }, PROP_TY_ARRAY => Self::Array { name, - default_value: default_value_json, - func_unique_id, - widget_kind, - widget_options, - hidden, - doc_link, + data, + unique_id, }, PROP_TY_OBJECT => Self::Object { name, - default_value: default_value_json, - func_unique_id, - widget_kind, - widget_options, - hidden, - doc_link, + data, + unique_id, }, invalid_kind => { return Err(GraphError::parse_custom(format!( @@ -366,27 +273,74 @@ impl NodeChild for PropSpec { type NodeType = PkgNode; fn as_node_with_children(&self) -> NodeWithChildren { - match self { - Self::String { + let (name, data, unique_id, validations, inputs) = match &self { + Self::Array { name, - default_value, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - } => NodeWithChildren::new( + data, + unique_id, + .. + } + | Self::Boolean { + name, + data, + unique_id, + } + | Self::Map { + name, + data, + unique_id, + .. + } + | Self::Number { + name, + data, + unique_id, + } + | Self::Object { + name, + data, + unique_id, + .. + } + | Self::String { + name, + data, + unique_id, + } => ( + name.to_owned(), + data.to_owned().map( + |PropSpecData { + name, + default_value, + func_unique_id, + widget_kind, + widget_options, + hidden, + doc_link, + .. + }| PropNodeData { + name, + default_value, + func_unique_id, + widget_kind: widget_kind.unwrap_or(PropSpecWidgetKind::from(self)), + widget_options, + hidden: hidden.unwrap_or(false), + doc_link, + }, + ), + unique_id.to_owned(), + data.as_ref().and_then(|data| data.validations.to_owned()), + data.as_ref().and_then(|data| data.inputs.to_owned()), + ), + }; + + match self { + Self::String { .. } => NodeWithChildren::new( NodeKind::Tree, Self::NodeType::Prop(PropNode::String { - name: name.to_string(), - default_value: default_value.to_owned(), - func_unique_id: *func_unique_id, - widget_kind: widget_kind.unwrap_or(PropSpecWidgetKind::from(self)), - widget_options: widget_options.to_owned(), - hidden: hidden.unwrap_or(false), - doc_link: doc_link.to_owned(), + name, + data, + unique_id, }), vec![ Box::new(PropChild::Validations( @@ -397,26 +351,12 @@ impl NodeChild for PropSpec { )) as Box>, ], ), - Self::Number { - name, - default_value, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - } => NodeWithChildren::new( + Self::Number { .. } => NodeWithChildren::new( NodeKind::Tree, Self::NodeType::Prop(PropNode::Integer { - name: name.to_string(), - default_value: default_value.to_owned(), - func_unique_id: *func_unique_id, - widget_kind: widget_kind.unwrap_or(PropSpecWidgetKind::from(self)), - widget_options: widget_options.to_owned(), - hidden: hidden.unwrap_or(false), - doc_link: doc_link.to_owned(), + name, + data, + unique_id, }), vec![ Box::new(PropChild::Validations( @@ -427,26 +367,12 @@ impl NodeChild for PropSpec { )) as Box>, ], ), - Self::Boolean { - name, - default_value, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - } => NodeWithChildren::new( + Self::Boolean { .. } => NodeWithChildren::new( NodeKind::Tree, Self::NodeType::Prop(PropNode::Boolean { - name: name.to_string(), - default_value: default_value.to_owned(), - func_unique_id: *func_unique_id, - widget_kind: widget_kind.unwrap_or(PropSpecWidgetKind::from(self)), - widget_options: widget_options.to_owned(), - hidden: hidden.unwrap_or(false), - doc_link: doc_link.to_owned(), + name, + data, + unique_id, }), vec![ Box::new(PropChild::Validations( @@ -458,27 +384,15 @@ impl NodeChild for PropSpec { ], ), Self::Map { - name, - default_value, type_prop, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, map_key_funcs, + .. } => NodeWithChildren::new( NodeKind::Tree, Self::NodeType::Prop(PropNode::Map { - name: name.to_string(), - default_value: default_value.to_owned(), - func_unique_id: *func_unique_id, - widget_kind: widget_kind.unwrap_or(PropSpecWidgetKind::from(self)), - widget_options: widget_options.to_owned(), - hidden: hidden.unwrap_or(false), - doc_link: doc_link.to_owned(), + name, + data, + unique_id, }), vec![ Box::new(PropChild::MapKeyFuncs( @@ -494,27 +408,12 @@ impl NodeChild for PropSpec { )) as Box>, ], ), - Self::Array { - name, - default_value, - type_prop, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - } => NodeWithChildren::new( + Self::Array { type_prop, .. } => NodeWithChildren::new( NodeKind::Tree, Self::NodeType::Prop(PropNode::Array { - name: name.to_string(), - default_value: default_value.to_owned(), - func_unique_id: *func_unique_id, - widget_kind: widget_kind.unwrap_or(PropSpecWidgetKind::from(self)), - widget_options: widget_options.to_owned(), - hidden: hidden.unwrap_or(false), - doc_link: doc_link.to_owned(), + name, + data, + unique_id, }), vec![ Box::new(PropChild::Props(vec![*type_prop.clone()])) @@ -527,27 +426,12 @@ impl NodeChild for PropSpec { )) as Box>, ], ), - Self::Object { - name, - default_value, - entries, - validations, - func_unique_id, - inputs, - widget_kind, - widget_options, - hidden, - doc_link, - } => NodeWithChildren::new( + Self::Object { entries, .. } => NodeWithChildren::new( NodeKind::Tree, Self::NodeType::Prop(PropNode::Object { - name: name.to_string(), - default_value: default_value.to_owned(), - func_unique_id: *func_unique_id, - widget_kind: widget_kind.unwrap_or(PropSpecWidgetKind::from(self)), - widget_options: widget_options.to_owned(), - hidden: hidden.unwrap_or(false), - doc_link: doc_link.to_owned(), + name, + data, + unique_id, }), vec![ Box::new(PropChild::Props(entries.clone())) diff --git a/lib/si-pkg/src/node/schema.rs b/lib/si-pkg/src/node/schema.rs index 9ff07d7a71..0e69facc25 100644 --- a/lib/si-pkg/src/node/schema.rs +++ b/lib/si-pkg/src/node/schema.rs @@ -4,13 +4,13 @@ use std::{ }; use object_tree::{ - read_key_value_line, write_key_value_line, GraphError, NameStr, NodeChild, NodeKind, - NodeWithChildren, ReadBytes, WriteBytes, + read_key_value_line, read_key_value_line_opt, write_key_value_line, GraphError, NameStr, + NodeChild, NodeKind, NodeWithChildren, ReadBytes, WriteBytes, }; use crate::SchemaSpec; -use super::PkgNode; +use super::{read_common_fields, write_common_fields, PkgNode}; const KEY_CATEGORY_STR: &str = "category"; const KEY_CATEGORY_NAME_STR: &str = "category_name"; @@ -18,13 +18,21 @@ const KEY_NAME_STR: &str = "name"; const KEY_UI_HIDDEN_STR: &str = "ui_hidden"; #[derive(Clone, Debug)] -pub struct SchemaNode { +pub struct SchemaData { pub name: String, pub category: String, pub category_name: Option, pub ui_hidden: bool, } +#[derive(Clone, Debug)] +pub struct SchemaNode { + pub name: String, + pub data: Option, + pub unique_id: Option, + pub deleted: bool, +} + impl NameStr for SchemaNode { fn name(&self) -> &str { &self.name @@ -34,13 +42,18 @@ impl NameStr for SchemaNode { impl WriteBytes for SchemaNode { fn write_bytes(&self, writer: &mut W) -> Result<(), GraphError> { write_key_value_line(writer, KEY_NAME_STR, self.name())?; - write_key_value_line(writer, KEY_CATEGORY_STR, &self.category)?; - write_key_value_line( - writer, - KEY_CATEGORY_NAME_STR, - self.category_name.as_deref().unwrap_or(""), - )?; - write_key_value_line(writer, KEY_UI_HIDDEN_STR, self.ui_hidden)?; + + if let Some(data) = &self.data { + write_key_value_line(writer, KEY_CATEGORY_STR, &data.category)?; + write_key_value_line( + writer, + KEY_CATEGORY_NAME_STR, + data.category_name.as_deref().unwrap_or(""), + )?; + write_key_value_line(writer, KEY_UI_HIDDEN_STR, data.ui_hidden)?; + } + + write_common_fields(writer, self.unique_id.as_deref(), self.deleted)?; Ok(()) } @@ -52,21 +65,34 @@ impl ReadBytes for SchemaNode { Self: std::marker::Sized, { let name = read_key_value_line(reader, KEY_NAME_STR)?; - let category = read_key_value_line(reader, KEY_CATEGORY_STR)?; - let category_name_str = read_key_value_line(reader, KEY_CATEGORY_NAME_STR)?; - let category_name = if category_name_str.is_empty() { - None - } else { - Some(category_name_str) + let data = match read_key_value_line_opt(reader, KEY_CATEGORY_STR)? { + None => None, + Some(category) => { + let category_name_str = read_key_value_line(reader, KEY_CATEGORY_NAME_STR)?; + let category_name = if category_name_str.is_empty() { + None + } else { + Some(category_name_str) + }; + let ui_hidden = bool::from_str(&read_key_value_line(reader, KEY_UI_HIDDEN_STR)?) + .map_err(GraphError::parse)?; + + Some(SchemaData { + name: name.to_owned(), + category, + category_name, + ui_hidden, + }) + } }; - let ui_hidden = bool::from_str(&read_key_value_line(reader, KEY_UI_HIDDEN_STR)?) - .map_err(GraphError::parse)?; + + let (unique_id, deleted) = read_common_fields(reader)?; Ok(Self { name, - category, - category_name, - ui_hidden, + data, + unique_id, + deleted, }) } } @@ -84,9 +110,14 @@ impl NodeChild for SchemaSpec { NodeKind::Tree, Self::NodeType::Schema(SchemaNode { name: self.name.to_string(), - category: self.category.to_string(), - category_name: self.category_name.clone(), - ui_hidden: self.ui_hidden, + unique_id: self.unique_id.as_ref().cloned(), + deleted: self.deleted, + data: self.data.as_ref().map(|data| SchemaData { + name: data.name.to_owned(), + category: data.category.to_owned(), + category_name: data.category_name.as_ref().cloned(), + ui_hidden: data.ui_hidden, + }), }), children, ) diff --git a/lib/si-pkg/src/node/schema_variant.rs b/lib/si-pkg/src/node/schema_variant.rs index e10d11416c..425d76271e 100644 --- a/lib/si-pkg/src/node/schema_variant.rs +++ b/lib/si-pkg/src/node/schema_variant.rs @@ -4,16 +4,14 @@ use std::{ }; use object_tree::{ - read_key_value_line, write_key_value_line, GraphError, NameStr, NodeChild, NodeKind, - NodeWithChildren, ReadBytes, WriteBytes, + read_key_value_line, read_key_value_line_opt, write_key_value_line, GraphError, NameStr, + NodeChild, NodeKind, NodeWithChildren, ReadBytes, WriteBytes, }; use url::Url; -use crate::{ - node::SchemaVariantChild, FuncUniqueId, SchemaVariantSpec, SchemaVariantSpecComponentType, -}; +use crate::{node::SchemaVariantChild, SchemaVariantSpec, SchemaVariantSpecComponentType}; -use super::PkgNode; +use super::{read_common_fields, write_common_fields, PkgNode}; const KEY_COLOR_STR: &str = "color"; const KEY_LINK_STR: &str = "link"; @@ -22,12 +20,20 @@ const KEY_COMPONENT_TYPE_STR: &str = "component_type"; const KEY_FUNC_UNIQUE_ID_STR: &str = "func_unique_id"; #[derive(Clone, Debug)] -pub struct SchemaVariantNode { +pub struct SchemaVariantData { pub name: String, pub link: Option, pub color: Option, pub component_type: SchemaVariantSpecComponentType, - pub func_unique_id: FuncUniqueId, + pub func_unique_id: String, +} + +#[derive(Clone, Debug)] +pub struct SchemaVariantNode { + pub name: String, + pub data: Option, + pub unique_id: Option, + pub deleted: bool, } impl NameStr for SchemaVariantNode { @@ -39,18 +45,22 @@ impl NameStr for SchemaVariantNode { impl WriteBytes for SchemaVariantNode { fn write_bytes(&self, writer: &mut W) -> Result<(), GraphError> { write_key_value_line(writer, KEY_NAME_STR, self.name())?; - write_key_value_line( - writer, - KEY_LINK_STR, - self.link.as_ref().map(|l| l.as_str()).unwrap_or(""), - )?; - write_key_value_line(writer, KEY_COLOR_STR, self.color.as_deref().unwrap_or(""))?; - write_key_value_line(writer, KEY_COMPONENT_TYPE_STR, self.component_type)?; - write_key_value_line( - writer, - KEY_FUNC_UNIQUE_ID_STR, - self.func_unique_id.to_string(), - )?; + if let Some(data) = &self.data { + write_key_value_line( + writer, + KEY_LINK_STR, + data.link.as_ref().map(|l| l.as_str()).unwrap_or(""), + )?; + write_key_value_line(writer, KEY_COLOR_STR, data.color.as_deref().unwrap_or(""))?; + write_key_value_line(writer, KEY_COMPONENT_TYPE_STR, data.component_type)?; + write_key_value_line( + writer, + KEY_FUNC_UNIQUE_ID_STR, + data.func_unique_id.to_string(), + )?; + } + + write_common_fields(writer, self.unique_id.as_deref(), self.deleted)?; Ok(()) } @@ -62,32 +72,43 @@ impl ReadBytes for SchemaVariantNode { Self: std::marker::Sized, { let name = read_key_value_line(reader, KEY_NAME_STR)?; - let link_str = read_key_value_line(reader, KEY_LINK_STR)?; - let link = if link_str.is_empty() { - None - } else { - Some(Url::parse(&link_str).map_err(GraphError::parse)?) - }; - let color_str = read_key_value_line(reader, KEY_COLOR_STR)?; - let color = if color_str.is_empty() { - None - } else { - Some(color_str) + let data = match read_key_value_line_opt(reader, KEY_LINK_STR)? { + Some(link_str) => { + let link = if link_str.is_empty() { + None + } else { + Some(Url::parse(&link_str).map_err(GraphError::parse)?) + }; + let color_str = read_key_value_line(reader, KEY_COLOR_STR)?; + let color = if color_str.is_empty() { + None + } else { + Some(color_str) + }; + let component_type_str = read_key_value_line(reader, KEY_COMPONENT_TYPE_STR)?; + let component_type = SchemaVariantSpecComponentType::from_str(&component_type_str) + .map_err(GraphError::parse)?; + + let func_unique_id = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; + + Some(SchemaVariantData { + name: name.to_owned(), + link, + color, + component_type, + func_unique_id, + }) + } + None => None, }; - let component_type_str = read_key_value_line(reader, KEY_COMPONENT_TYPE_STR)?; - let component_type = SchemaVariantSpecComponentType::from_str(&component_type_str) - .map_err(GraphError::parse)?; - let func_unique_id_str = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; - let func_unique_id = - FuncUniqueId::from_str(&func_unique_id_str).map_err(GraphError::parse)?; + let (unique_id, deleted) = read_common_fields(reader)?; Ok(Self { name, - link, - color, - component_type, - func_unique_id, + data, + unique_id, + deleted, }) } } @@ -99,11 +120,16 @@ impl NodeChild for SchemaVariantSpec { NodeWithChildren::new( NodeKind::Tree, Self::NodeType::SchemaVariant(SchemaVariantNode { - name: self.name.to_string(), - link: self.link.as_ref().cloned(), - color: self.color.as_ref().cloned(), - component_type: self.component_type, - func_unique_id: self.func_unique_id, + name: self.name.to_owned(), + data: self.data.as_ref().map(|data| SchemaVariantData { + name: self.name.to_owned(), + link: data.link.as_ref().cloned(), + color: data.color.as_ref().cloned(), + component_type: data.component_type, + func_unique_id: data.func_unique_id.to_owned(), + }), + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }), vec![ Box::new(SchemaVariantChild::ActionFuncs(self.action_funcs.clone())) diff --git a/lib/si-pkg/src/node/si_prop_func.rs b/lib/si-pkg/src/node/si_prop_func.rs index 6b64da32ed..53b4bd89a0 100644 --- a/lib/si-pkg/src/node/si_prop_func.rs +++ b/lib/si-pkg/src/node/si_prop_func.rs @@ -8,9 +8,9 @@ use object_tree::{ ReadBytes, WriteBytes, }; -use crate::{FuncUniqueId, SiPropFuncSpec, SiPropFuncSpecKind}; +use crate::{SiPropFuncSpec, SiPropFuncSpecKind}; -use super::PkgNode; +use super::{read_common_fields, write_common_fields, PkgNode}; const KEY_KIND_STR: &str = "kind"; const KEY_FUNC_UNIQUE_ID_STR: &str = "func_unique_id"; @@ -18,7 +18,9 @@ const KEY_FUNC_UNIQUE_ID_STR: &str = "func_unique_id"; #[derive(Clone, Debug)] pub struct SiPropFuncNode { pub kind: SiPropFuncSpecKind, - pub func_unique_id: FuncUniqueId, + pub func_unique_id: String, + pub unique_id: Option, + pub deleted: bool, } impl WriteBytes for SiPropFuncNode { @@ -29,6 +31,7 @@ impl WriteBytes for SiPropFuncNode { KEY_FUNC_UNIQUE_ID_STR, self.func_unique_id.to_string(), )?; + write_common_fields(writer, self.unique_id.as_deref(), self.deleted)?; Ok(()) } @@ -42,13 +45,15 @@ impl ReadBytes for SiPropFuncNode { let kind_str = read_key_value_line(reader, KEY_KIND_STR)?; let kind = SiPropFuncSpecKind::from_str(&kind_str).map_err(GraphError::parse)?; - let func_unique_id_str = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; - let func_unique_id = - FuncUniqueId::from_str(&func_unique_id_str).map_err(GraphError::parse)?; + let func_unique_id = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; + + let (unique_id, deleted) = read_common_fields(reader)?; Ok(Self { kind, func_unique_id, + unique_id, + deleted, }) } } @@ -61,7 +66,9 @@ impl NodeChild for SiPropFuncSpec { NodeKind::Tree, Self::NodeType::SiPropFunc(SiPropFuncNode { kind: self.kind, - func_unique_id: self.func_unique_id, + func_unique_id: self.func_unique_id.to_owned(), + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }), self.inputs .iter() diff --git a/lib/si-pkg/src/node/socket.rs b/lib/si-pkg/src/node/socket.rs index e58a280d03..8b88bab866 100644 --- a/lib/si-pkg/src/node/socket.rs +++ b/lib/si-pkg/src/node/socket.rs @@ -4,13 +4,13 @@ use std::{ }; use object_tree::{ - read_key_value_line, write_key_value_line, GraphError, NameStr, NodeChild, NodeKind, - NodeWithChildren, ReadBytes, WriteBytes, + read_key_value_line, read_key_value_line_opt, write_key_value_line, GraphError, NameStr, + NodeChild, NodeKind, NodeWithChildren, ReadBytes, WriteBytes, }; -use crate::{FuncUniqueId, SocketSpec, SocketSpecArity, SocketSpecKind}; +use crate::{SocketSpec, SocketSpecArity, SocketSpecKind}; -use super::PkgNode; +use super::{read_unique_id, write_unique_id, PkgNode}; const KEY_KIND_STR: &str = "kind"; const KEY_NAME_STR: &str = "name"; @@ -19,14 +19,21 @@ const KEY_FUNC_UNIQUE_ID_STR: &str = "func_unique_id"; const KEY_UI_HIDDEN_STR: &str = "ui_hidden"; #[derive(Clone, Debug)] -pub struct SocketNode { - pub func_unique_id: Option, +pub struct SocketData { pub name: String, pub kind: SocketSpecKind, pub arity: SocketSpecArity, + pub func_unique_id: Option, pub ui_hidden: bool, } +#[derive(Clone, Debug)] +pub struct SocketNode { + pub name: String, + pub data: Option, + pub unique_id: Option, +} + impl NameStr for SocketNode { fn name(&self) -> &str { &self.name @@ -36,18 +43,20 @@ impl NameStr for SocketNode { impl WriteBytes for SocketNode { fn write_bytes(&self, writer: &mut W) -> Result<(), GraphError> { write_key_value_line(writer, KEY_NAME_STR, &self.name)?; - write_key_value_line(writer, KEY_KIND_STR, self.kind)?; - write_key_value_line(writer, KEY_ARITY_STR, self.arity)?; - write_key_value_line( - writer, - KEY_FUNC_UNIQUE_ID_STR, - self.func_unique_id - .map(|fuid| fuid.to_string()) - .unwrap_or("".to_string()), - )?; + if let Some(data) = &self.data { + write_key_value_line(writer, KEY_KIND_STR, data.kind)?; + write_key_value_line(writer, KEY_ARITY_STR, data.arity)?; + + write_key_value_line( + writer, + KEY_FUNC_UNIQUE_ID_STR, + data.func_unique_id.as_deref().unwrap_or(""), + )?; + write_key_value_line(writer, KEY_UI_HIDDEN_STR, data.ui_hidden)?; + } - write_key_value_line(writer, KEY_UI_HIDDEN_STR, self.ui_hidden)?; + write_unique_id(writer, self.unique_id.as_deref())?; Ok(()) } @@ -59,28 +68,40 @@ impl ReadBytes for SocketNode { Self: std::marker::Sized, { let name = read_key_value_line(reader, KEY_NAME_STR)?; - let kind_str = read_key_value_line(reader, KEY_KIND_STR)?; - let kind = SocketSpecKind::from_str(&kind_str).map_err(GraphError::parse)?; - - let arity_str = read_key_value_line(reader, KEY_ARITY_STR)?; - let arity = SocketSpecArity::from_str(&arity_str).map_err(GraphError::parse)?; - - let func_unique_id_str = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; - let func_unique_id = if func_unique_id_str.is_empty() { - None - } else { - Some(FuncUniqueId::from_str(&func_unique_id_str).map_err(GraphError::parse)?) + let data = match read_key_value_line_opt(reader, KEY_KIND_STR)? { + None => None, + Some(kind_str) => { + let kind = SocketSpecKind::from_str(&kind_str).map_err(GraphError::parse)?; + + let arity_str = read_key_value_line(reader, KEY_ARITY_STR)?; + let arity = SocketSpecArity::from_str(&arity_str).map_err(GraphError::parse)?; + + let func_unique_id_str = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; + let func_unique_id = if func_unique_id_str.is_empty() { + None + } else { + Some(func_unique_id_str) + }; + + let ui_hidden = bool::from_str(&read_key_value_line(reader, KEY_UI_HIDDEN_STR)?) + .map_err(GraphError::parse)?; + + Some(SocketData { + name: name.to_owned(), + kind, + arity, + func_unique_id, + ui_hidden, + }) + } }; - let ui_hidden = bool::from_str(&read_key_value_line(reader, KEY_UI_HIDDEN_STR)?) - .map_err(GraphError::parse)?; + let unique_id = read_unique_id(reader)?; Ok(Self { name, - kind, - arity, - func_unique_id, - ui_hidden, + data, + unique_id, }) } } @@ -92,11 +113,15 @@ impl NodeChild for SocketSpec { NodeWithChildren::new( NodeKind::Tree, Self::NodeType::Socket(SocketNode { - func_unique_id: self.func_unique_id, - name: self.name.clone(), - kind: self.kind, - arity: self.arity, - ui_hidden: self.ui_hidden, + name: self.name.to_owned(), + data: self.data.as_ref().map(|data| SocketData { + name: self.name.to_owned(), + kind: data.kind, + arity: data.arity, + func_unique_id: data.func_unique_id.to_owned(), + ui_hidden: data.ui_hidden, + }), + unique_id: self.unique_id.to_owned(), }), self.inputs .iter() diff --git a/lib/si-pkg/src/node/validation.rs b/lib/si-pkg/src/node/validation.rs index a06ee70ff8..79867e66fb 100644 --- a/lib/si-pkg/src/node/validation.rs +++ b/lib/si-pkg/src/node/validation.rs @@ -8,9 +8,9 @@ use object_tree::{ ReadBytes, WriteBytes, }; -use crate::{FuncUniqueId, ValidationSpec, ValidationSpecKind}; +use crate::{ValidationSpec, ValidationSpecKind}; -use super::PkgNode; +use super::{read_common_fields, write_common_fields, PkgNode}; const KEY_KIND_STR: &str = "kind"; const KEY_UPPER_BOUND_STR: &str = "upper_bound"; @@ -28,7 +28,9 @@ pub struct ValidationNode { pub expected_string: Option, pub expected_string_array: Option>, pub display_expected: Option, - pub func_unique_id: Option, + pub func_unique_id: Option, + pub unique_id: Option, + pub deleted: bool, } impl Default for ValidationNode { @@ -41,6 +43,8 @@ impl Default for ValidationNode { expected_string_array: None, display_expected: None, func_unique_id: None, + unique_id: None, + deleted: false, } } } @@ -92,7 +96,8 @@ impl WriteBytes for ValidationNode { writer, KEY_FUNC_UNIQUE_ID_STR, self.func_unique_id - .map(|id| id.to_string()) + .as_ref() + .map(|id| id.to_owned()) .unwrap_or("".to_string()), )?, ValidationSpecKind::IntegerIsNotEmpty @@ -101,6 +106,8 @@ impl WriteBytes for ValidationNode { | ValidationSpecKind::StringIsNotEmpty => {} } + write_common_fields(writer, self.unique_id.as_deref(), self.deleted)?; + Ok(()) } } @@ -149,9 +156,7 @@ impl ReadBytes for ValidationNode { } } ValidationSpecKind::CustomValidation => { - let func_unique_id_str = read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?; - func_unique_id = - Some(FuncUniqueId::from_str(&func_unique_id_str).map_err(GraphError::parse)?); + func_unique_id = Some(read_key_value_line(reader, KEY_FUNC_UNIQUE_ID_STR)?); } ValidationSpecKind::IntegerIsNotEmpty | ValidationSpecKind::StringIsValidIpAddr @@ -159,6 +164,8 @@ impl ReadBytes for ValidationNode { | ValidationSpecKind::StringIsNotEmpty => {} } + let (unique_id, deleted) = read_common_fields(reader)?; + Ok(Self { kind, lower_bound, @@ -167,6 +174,8 @@ impl ReadBytes for ValidationNode { expected_string_array, display_expected, func_unique_id, + unique_id, + deleted, }) } } @@ -181,50 +190,84 @@ impl NodeChild for ValidationSpec { ValidationSpec::IntegerIsBetweenTwoIntegers { lower_bound, upper_bound, + unique_id, + deleted, } => ValidationNode { kind: ValidationSpecKind::IntegerIsBetweenTwoIntegers, upper_bound: Some(*upper_bound), lower_bound: Some(*lower_bound), + unique_id: unique_id.clone(), + deleted: *deleted, ..ValidationNode::default() }, - ValidationSpec::IntegerIsNotEmpty => ValidationNode { + ValidationSpec::IntegerIsNotEmpty { unique_id, deleted } => ValidationNode { kind: ValidationSpecKind::IntegerIsNotEmpty, + unique_id: unique_id.clone(), + deleted: *deleted, ..ValidationNode::default() }, - ValidationSpec::StringEquals { expected } => ValidationNode { + ValidationSpec::StringEquals { + expected, + unique_id, + deleted, + } => ValidationNode { kind: ValidationSpecKind::StringEquals, expected_string: Some(expected.clone()), + unique_id: unique_id.clone(), + deleted: *deleted, ..ValidationNode::default() }, - ValidationSpec::StringHasPrefix { expected } => ValidationNode { + ValidationSpec::StringHasPrefix { + expected, + unique_id, + deleted, + } => ValidationNode { kind: ValidationSpecKind::StringHasPrefix, expected_string: Some(expected.clone()), + unique_id: unique_id.clone(), + deleted: *deleted, ..ValidationNode::default() }, ValidationSpec::StringInStringArray { expected, display_expected, + unique_id, + deleted, } => ValidationNode { kind: ValidationSpecKind::StringInStringArray, expected_string_array: Some(expected.clone()), display_expected: Some(*display_expected), + unique_id: unique_id.clone(), + deleted: *deleted, ..ValidationNode::default() }, - ValidationSpec::StringIsValidIpAddr => ValidationNode { + ValidationSpec::StringIsValidIpAddr { unique_id, deleted } => ValidationNode { kind: ValidationSpecKind::StringIsValidIpAddr, + unique_id: unique_id.clone(), + deleted: *deleted, ..ValidationNode::default() }, - ValidationSpec::StringIsHexColor => ValidationNode { + ValidationSpec::StringIsHexColor { unique_id, deleted } => ValidationNode { kind: ValidationSpecKind::StringIsHexColor, + unique_id: unique_id.clone(), + deleted: *deleted, ..ValidationNode::default() }, - ValidationSpec::StringIsNotEmpty => ValidationNode { + ValidationSpec::StringIsNotEmpty { unique_id, deleted } => ValidationNode { kind: ValidationSpecKind::StringIsNotEmpty, + unique_id: unique_id.clone(), + deleted: *deleted, ..ValidationNode::default() }, - ValidationSpec::CustomValidation { func_unique_id } => ValidationNode { + ValidationSpec::CustomValidation { + func_unique_id, + unique_id, + deleted, + } => ValidationNode { kind: ValidationSpecKind::CustomValidation, - func_unique_id: Some(*func_unique_id), + func_unique_id: Some(func_unique_id.to_owned()), + unique_id: unique_id.clone(), + deleted: *deleted, ..ValidationNode::default() }, }), diff --git a/lib/si-pkg/src/pkg.rs b/lib/si-pkg/src/pkg.rs index b018a0ac30..b1640820ab 100644 --- a/lib/si-pkg/src/pkg.rs +++ b/lib/si-pkg/src/pkg.rs @@ -37,8 +37,6 @@ use crate::{ #[remain::sorted] #[derive(Debug, Error)] pub enum SiPkgError { - #[error("Package missing required category: {0}")] - CategoryNotFound(&'static str), #[error(transparent)] Graph(#[from] GraphError), #[error(transparent)] @@ -137,11 +135,11 @@ impl SiPkg { Ok(self.metadata()?.hash()) } - pub fn funcs_by_unique_id(&self) -> PkgResult> { - let func_map: HashMap = self + pub fn funcs_by_unique_id(&self) -> PkgResult> { + let func_map: HashMap = self .funcs()? .drain(..) - .map(|func| (func.unique_id(), func)) + .map(|func| (func.unique_id().to_string(), func)) .collect(); Ok(func_map) @@ -227,6 +225,16 @@ impl SiPkg { builder.schema(schema.to_spec().await?); } + if let SiPkgKind::WorkspaceBackup = metadata.kind() { + if let Some(default_change_set) = metadata.default_change_set() { + builder.default_change_set(default_change_set); + } + + for change_set in self.change_sets()? { + builder.change_set(change_set.to_spec().await?); + } + } + Ok(builder.build()?) } } @@ -266,10 +274,11 @@ fn category_node_idxs( .find(|node_idx| match &graph[*node_idx].inner() { PkgNode::Category(node) => *node == category_node, _ => false, - }) - .ok_or(SiPkgError::CategoryNotFound(category_node.kind_str()))?; + }); - Ok(graph.neighbors_directed(node_idxs, Outgoing).collect()) + Ok(node_idxs + .map(|node_idx| graph.neighbors_directed(node_idx, Outgoing).collect()) + .unwrap_or(vec![])) } fn schema_node_idxs( @@ -337,7 +346,7 @@ pub struct SiPkgMetadata { created_at: DateTime, created_by: String, default_change_set: Option, - + workspace_pk: Option, hash: Hash, } @@ -362,6 +371,7 @@ impl SiPkgMetadata { created_at: metadata_node.created_at, created_by: metadata_node.created_by, default_change_set: metadata_node.default_change_set, + workspace_pk: metadata_node.workspace_pk, hash: metadata_hashed_node.hash(), }) } @@ -394,6 +404,10 @@ impl SiPkgMetadata { self.default_change_set.as_deref() } + pub fn workspace_pk(&self) -> Option<&str> { + self.workspace_pk.as_deref() + } + pub fn hash(&self) -> Hash { self.hash } diff --git a/lib/si-pkg/src/pkg/action_func.rs b/lib/si-pkg/src/pkg/action_func.rs index e29e8fb40e..ec5cbcba86 100644 --- a/lib/si-pkg/src/pkg/action_func.rs +++ b/lib/si-pkg/src/pkg/action_func.rs @@ -3,15 +3,15 @@ use petgraph::prelude::*; use super::{PkgResult, SiPkgError, Source}; -use crate::{ - ActionFuncSpec, ActionFuncSpecKind, - {node::PkgNode, spec::FuncUniqueId}, -}; +use crate::{node::PkgNode, ActionFuncSpec, ActionFuncSpecKind}; #[derive(Clone, Debug)] pub struct SiPkgActionFunc<'a> { - func_unique_id: FuncUniqueId, + func_unique_id: String, kind: ActionFuncSpecKind, + unique_id: Option, + deleted: bool, + hash: Hash, source: Source<'a>, } @@ -35,19 +35,30 @@ impl<'a> SiPkgActionFunc<'a> { Ok(Self { func_unique_id: node.func_unique_id, kind: node.kind, + unique_id: node.unique_id, + deleted: node.deleted, + hash: hashed_node.hash(), source: Source::new(graph, node_idx), }) } - pub fn func_unique_id(&self) -> FuncUniqueId { - self.func_unique_id + pub fn func_unique_id(&self) -> &str { + self.func_unique_id.as_str() } pub fn kind(&self) -> ActionFuncSpecKind { self.kind } + pub fn unique_id(&self) -> Option<&str> { + self.unique_id.as_deref() + } + + pub fn deleted(&self) -> bool { + self.deleted + } + pub fn hash(&self) -> Hash { self.hash } @@ -63,7 +74,9 @@ impl<'a> TryFrom> for ActionFuncSpec { fn try_from(value: SiPkgActionFunc<'a>) -> Result { Ok(ActionFuncSpec::builder() .kind(value.kind()) + .deleted(value.deleted()) .func_unique_id(value.func_unique_id) + .unique_id(value.unique_id) .build()?) } } diff --git a/lib/si-pkg/src/pkg/attr_func_input.rs b/lib/si-pkg/src/pkg/attr_func_input.rs index f6a7b5a95d..cc78192e04 100644 --- a/lib/si-pkg/src/pkg/attr_func_input.rs +++ b/lib/si-pkg/src/pkg/attr_func_input.rs @@ -14,18 +14,27 @@ pub enum SiPkgAttrFuncInput<'a> { InputSocket { name: String, socket_name: String, + unique_id: Option, + deleted: bool, + hash: Hash, source: Source<'a>, }, OutputSocket { name: String, socket_name: String, + unique_id: Option, + deleted: bool, + hash: Hash, source: Source<'a>, }, Prop { name: String, prop_path: String, + unique_id: Option, + deleted: bool, + hash: Hash, source: Source<'a>, }, @@ -35,23 +44,65 @@ pub enum SiPkgAttrFuncInput<'a> { #[remain::sorted] #[derive(Clone, Debug)] pub enum SiPkgAttrFuncInputView { - InputSocket { name: String, socket_name: String }, - OutputSocket { name: String, socket_name: String }, - Prop { name: String, prop_path: String }, + InputSocket { + name: String, + socket_name: String, + unique_id: Option, + deleted: bool, + }, + OutputSocket { + name: String, + socket_name: String, + unique_id: Option, + deleted: bool, + }, + Prop { + name: String, + prop_path: String, + unique_id: Option, + deleted: bool, + }, } impl<'a> From> for SiPkgAttrFuncInputView { fn from(value: SiPkgAttrFuncInput<'a>) -> Self { match value { SiPkgAttrFuncInput::Prop { - name, prop_path, .. - } => Self::Prop { name, prop_path }, + name, + prop_path, + unique_id, + deleted, + .. + } => Self::Prop { + name, + prop_path, + unique_id, + deleted, + }, SiPkgAttrFuncInput::InputSocket { - name, socket_name, .. - } => Self::InputSocket { name, socket_name }, + name, + socket_name, + unique_id, + deleted, + .. + } => Self::InputSocket { + name, + socket_name, + unique_id, + deleted, + }, SiPkgAttrFuncInput::OutputSocket { - name, socket_name, .. - } => Self::OutputSocket { name, socket_name }, + name, + socket_name, + unique_id, + deleted, + .. + } => Self::OutputSocket { + name, + socket_name, + unique_id, + deleted, + }, } } } @@ -75,21 +126,45 @@ impl<'a> SiPkgAttrFuncInput<'a> { let source = Source::new(graph, node_idx); Ok(match node { - AttrFuncInputNode::Prop { name, prop_path } => Self::Prop { + AttrFuncInputNode::Prop { + name, + prop_path, + unique_id, + deleted, + } => Self::Prop { name, prop_path, + unique_id, + deleted, + hash, source, }, - AttrFuncInputNode::InputSocket { name, socket_name } => Self::InputSocket { + AttrFuncInputNode::InputSocket { + name, + socket_name, + unique_id, + deleted, + } => Self::InputSocket { name, socket_name, + unique_id, + deleted, + hash, source, }, - AttrFuncInputNode::OutputSocket { name, socket_name } => Self::OutputSocket { + AttrFuncInputNode::OutputSocket { + name, + socket_name, + unique_id, + deleted, + } => Self::OutputSocket { name, socket_name, + unique_id, + deleted, + hash, source, }, @@ -102,6 +177,23 @@ impl<'a> TryFrom> for AttrFuncInputSpec { fn try_from(value: SiPkgAttrFuncInput<'a>) -> Result { let mut builder = AttrFuncInputSpec::builder(); + let (unique_id, deleted) = match &value { + SiPkgAttrFuncInput::InputSocket { + unique_id, deleted, .. + } + | SiPkgAttrFuncInput::OutputSocket { + unique_id, deleted, .. + } + | SiPkgAttrFuncInput::Prop { + unique_id, deleted, .. + } => (unique_id.as_deref(), *deleted), + }; + + if let Some(unique_id) = unique_id { + builder.unique_id(unique_id); + } + builder.deleted(deleted); + match value { SiPkgAttrFuncInput::Prop { name, prop_path, .. diff --git a/lib/si-pkg/src/pkg/change_set.rs b/lib/si-pkg/src/pkg/change_set.rs index d04b6176cf..80671300b0 100644 --- a/lib/si-pkg/src/pkg/change_set.rs +++ b/lib/si-pkg/src/pkg/change_set.rs @@ -2,10 +2,9 @@ use object_tree::{Hash, HashedNode}; use petgraph::prelude::*; use super::{PkgResult, SiPkgError, SiPkgSchema, Source}; -use crate::SiPkgFunc; use crate::{ node::{ChangeSetChildNode, PkgNode}, - ChangeSetSpecStatus, + ChangeSetSpec, ChangeSetSpecStatus, FuncSpec, SiPkgFunc, }; #[derive(Clone, Debug)] @@ -95,4 +94,23 @@ impl<'a> SiPkgChangeSet<'a> { impl_change_set_children_from_graph!(funcs, ChangeSetChildNode::Funcs, SiPkgFunc); impl_change_set_children_from_graph!(schemas, ChangeSetChildNode::Schemas, SiPkgSchema); + + pub async fn to_spec(&self) -> PkgResult { + let mut builder = ChangeSetSpec::builder(); + + builder.name(self.name()).status(self.status()); + if let Some(based_on_change_set) = self.based_on_change_set() { + builder.based_on_change_set(based_on_change_set); + } + + for func in self.funcs()? { + builder.func(FuncSpec::try_from(func)?); + } + + for schema in self.schemas()? { + builder.schema(schema.to_spec().await?); + } + + Ok(builder.build()?) + } } diff --git a/lib/si-pkg/src/pkg/func.rs b/lib/si-pkg/src/pkg/func.rs index 961f7216e1..35760ea526 100644 --- a/lib/si-pkg/src/pkg/func.rs +++ b/lib/si-pkg/src/pkg/func.rs @@ -7,7 +7,7 @@ use crate::{ node::PkgNode, spec::{ FuncArgumentKind, FuncArgumentSpec, FuncSpec, FuncSpecBackendKind, - FuncSpecBackendResponseType, + FuncSpecBackendResponseType, FuncSpecData, }, }; @@ -18,6 +18,8 @@ pub struct SiPkgFuncArgument<'a> { name: String, kind: FuncArgumentKind, element_kind: Option, + unique_id: Option, + deleted: bool, hash: Hash, source: Source<'a>, @@ -43,6 +45,8 @@ impl<'a> SiPkgFuncArgument<'a> { name: node.name, kind: node.kind, element_kind: node.element_kind, + unique_id: node.unique_id, + deleted: node.deleted, hash: hashed_node.hash(), source: Source::new(graph, node_idx), @@ -61,6 +65,14 @@ impl<'a> SiPkgFuncArgument<'a> { self.element_kind.as_ref() } + pub fn unique_id(&self) -> Option<&str> { + self.unique_id.as_deref() + } + + pub fn deleted(&self) -> bool { + self.deleted + } + pub fn hash(&self) -> Hash { self.hash } @@ -83,7 +95,7 @@ impl<'a> TryFrom> for FuncArgumentSpec { } #[derive(Clone, Debug)] -pub struct SiPkgFunc<'a> { +pub struct SiPkgFuncData { name: String, display_name: Option, description: Option, @@ -93,7 +105,52 @@ pub struct SiPkgFunc<'a> { response_type: FuncSpecBackendResponseType, hidden: bool, link: Option, - unique_id: Hash, +} + +impl SiPkgFuncData { + pub fn name(&self) -> &str { + self.name.as_str() + } + + pub fn display_name(&self) -> Option<&str> { + self.display_name.as_deref() + } + + pub fn description(&self) -> Option<&str> { + self.description.as_deref() + } + + pub fn handler(&self) -> &str { + self.handler.as_str() + } + + pub fn code_base64(&self) -> &str { + self.code_base64.as_str() + } + + pub fn backend_kind(&self) -> FuncSpecBackendKind { + self.backend_kind + } + + pub fn response_type(&self) -> FuncSpecBackendResponseType { + self.response_type + } + + pub fn hidden(&self) -> bool { + self.hidden + } + + pub fn link(&self) -> Option<&Url> { + self.link.as_ref() + } +} + +#[derive(Clone, Debug)] +pub struct SiPkgFunc<'a> { + name: String, + data: Option, + unique_id: String, + deleted: bool, hash: Hash, source: Source<'a>, @@ -117,16 +174,20 @@ impl<'a> SiPkgFunc<'a> { Ok(Self { name: func_node.name, - display_name: func_node.display_name, - description: func_node.description, - handler: func_node.handler, - code_base64: func_node.code_base64, - backend_kind: func_node.backend_kind, - response_type: func_node.response_type, - hidden: func_node.hidden, - link: func_node.link, + data: func_node.data.map(|data| SiPkgFuncData { + name: data.name, + display_name: data.display_name, + description: data.description, + handler: data.handler, + code_base64: data.code_base64, + backend_kind: data.backend_kind, + response_type: data.response_type, + hidden: data.hidden, + link: data.link, + }), hash: func_hashed_node.hash(), unique_id: func_node.unique_id, + deleted: func_node.deleted, source: Source::new(graph, node_idx), }) } @@ -148,44 +209,61 @@ impl<'a> SiPkgFunc<'a> { self.name.as_ref() } + pub fn data(&self) -> Option<&SiPkgFuncData> { + self.data.as_ref() + } + + pub fn deleted(&self) -> bool { + self.deleted + } + pub fn display_name(&self) -> Option<&str> { - self.display_name.as_deref() + match self.data() { + None => None, + Some(data) => data.display_name.as_deref(), + } } pub fn description(&self) -> Option<&str> { - self.description.as_deref() + match self.data() { + None => None, + Some(data) => data.description.as_deref(), + } } - pub fn handler(&self) -> &str { - self.handler.as_ref() + pub fn handler(&self) -> Option<&str> { + self.data().map(|data| data.handler.as_str()) } - pub fn code_base64(&self) -> &str { - self.code_base64.as_ref() + pub fn code_base64(&self) -> Option<&str> { + self.data().map(|data| data.code_base64.as_str()) } - pub fn backend_kind(&self) -> FuncSpecBackendKind { - self.backend_kind + pub fn backend_kind(&self) -> Option { + self.data().map(|data| data.backend_kind) } - pub fn response_type(&self) -> FuncSpecBackendResponseType { - self.response_type + pub fn response_type(&self) -> Option { + self.data().map(|data| data.response_type) } - pub fn hidden(&self) -> bool { - self.hidden + pub fn hidden(&self) -> Option { + self.data().map(|data| data.hidden) } pub fn link(&self) -> Option<&Url> { - self.link.as_ref() + match self.data() { + None => None, + Some(data) => data.link.as_ref(), + } } pub fn hash(&self) -> Hash { self.hash } - pub fn unique_id(&self) -> Hash { - self.unique_id + pub fn unique_id(&self) -> &str { + self.unique_id.as_str() } pub fn source(&self) -> &Source<'a> { @@ -198,31 +276,41 @@ impl<'a> TryFrom> for FuncSpec { fn try_from(value: SiPkgFunc<'a>) -> Result { let mut builder = FuncSpec::builder(); + let mut data_builder = FuncSpecData::builder(); builder .name(&value.name) - .handler(&value.handler) - .code_base64(&value.code_base64) - .backend_kind(value.backend_kind) - .response_type(value.response_type) - .hidden(value.hidden); - - if let Some(display_name) = &value.display_name { - builder.display_name(display_name); - } + .unique_id(&value.unique_id) + .deleted(value.deleted); + + if let Some(data) = value.data() { + data_builder + .name(&data.name) + .handler(&data.handler) + .code_base64(&data.code_base64) + .backend_kind(data.backend_kind) + .response_type(data.response_type) + .hidden(data.hidden); + + if let Some(display_name) = &data.display_name { + data_builder.display_name(display_name); + } + + if let Some(description) = &data.description { + data_builder.description(description); + } - if let Some(description) = &value.description { - builder.description(description); + if let Some(link) = &data.link { + data_builder.link(link.to_owned()); + } + + builder.data(data_builder.build()?); } for argument in value.arguments()? { builder.argument(argument.try_into()?); } - if let Some(link) = value.link { - builder.link(link); - } - Ok(builder.build()?) } } diff --git a/lib/si-pkg/src/pkg/leaf_function.rs b/lib/si-pkg/src/pkg/leaf_function.rs index 362e4e9305..96c4bb5b7d 100644 --- a/lib/si-pkg/src/pkg/leaf_function.rs +++ b/lib/si-pkg/src/pkg/leaf_function.rs @@ -5,15 +5,18 @@ use super::{PkgResult, SiPkgError, Source}; use crate::{ node::PkgNode, - spec::{FuncUniqueId, LeafInputLocation, LeafKind}, + spec::{LeafInputLocation, LeafKind}, LeafFunctionSpec, }; #[derive(Clone, Debug)] pub struct SiPkgLeafFunction<'a> { - func_unique_id: FuncUniqueId, + func_unique_id: String, leaf_kind: LeafKind, + unique_id: Option, + deleted: bool, inputs: Vec, + hash: Hash, source: Source<'a>, } @@ -51,20 +54,31 @@ impl<'a> SiPkgLeafFunction<'a> { Ok(Self { func_unique_id: node.func_unique_id, leaf_kind: node.leaf_kind, + unique_id: node.unique_id, + deleted: node.deleted, inputs, + hash: hashed_node.hash(), source: Source::new(graph, node_idx), }) } - pub fn func_unique_id(&self) -> FuncUniqueId { - self.func_unique_id + pub fn func_unique_id(&self) -> &str { + self.func_unique_id.as_str() } pub fn leaf_kind(&self) -> LeafKind { self.leaf_kind } + pub fn unique_id(&self) -> Option<&str> { + self.unique_id.as_deref() + } + + pub fn deleted(&self) -> bool { + self.deleted + } + pub fn inputs(&self) -> &[LeafInputLocation] { &self.inputs } @@ -85,6 +99,8 @@ impl<'a> TryFrom> for LeafFunctionSpec { Ok(LeafFunctionSpec::builder() .leaf_kind(value.leaf_kind) .func_unique_id(value.func_unique_id) + .unique_id(value.unique_id) + .deleted(value.deleted) .inputs(value.inputs) .build()?) } diff --git a/lib/si-pkg/src/pkg/map_key_func.rs b/lib/si-pkg/src/pkg/map_key_func.rs index 8a53d1d23c..c77a05ea87 100644 --- a/lib/si-pkg/src/pkg/map_key_func.rs +++ b/lib/si-pkg/src/pkg/map_key_func.rs @@ -3,12 +3,12 @@ use petgraph::prelude::*; use super::{PkgResult, SiPkgError, Source}; -use crate::{node::PkgNode, AttrFuncInputSpec, FuncUniqueId, MapKeyFuncSpec, SiPkgAttrFuncInput}; +use crate::{node::PkgNode, AttrFuncInputSpec, MapKeyFuncSpec, SiPkgAttrFuncInput}; #[derive(Clone, Debug)] pub struct SiPkgMapKeyFunc<'a> { key: String, - func_unique_id: FuncUniqueId, + func_unique_id: String, hash: Hash, source: Source<'a>, } @@ -31,14 +31,14 @@ impl<'a> SiPkgMapKeyFunc<'a> { Ok(Self { key: node.key.to_owned(), - func_unique_id: node.func_unique_id, + func_unique_id: node.func_unique_id.to_owned(), hash: hashed_node.hash(), source: Source::new(graph, node_idx), }) } - pub fn func_unique_id(&self) -> FuncUniqueId { - self.func_unique_id + pub fn func_unique_id(&self) -> &str { + self.func_unique_id.as_str() } pub fn inputs(&self) -> PkgResult> { diff --git a/lib/si-pkg/src/pkg/prop.rs b/lib/si-pkg/src/pkg/prop.rs index 7888d269f4..7430c3809c 100644 --- a/lib/si-pkg/src/pkg/prop.rs +++ b/lib/si-pkg/src/pkg/prop.rs @@ -5,76 +5,63 @@ use url::Url; use super::{PkgResult, SiPkgAttrFuncInput, SiPkgError, SiPkgMapKeyFunc, SiPkgValidation, Source}; use crate::{ - node::{PkgNode, PropChildNode, PropNode}, - FuncUniqueId, PropSpecWidgetKind, + node::{PkgNode, PropChildNode, PropNode, PropNodeData}, + PropSpecWidgetKind, }; +#[derive(Clone, Debug)] +pub struct SiPkgPropData { + pub name: String, + pub default_value: Option, + pub func_unique_id: Option, + pub widget_kind: PropSpecWidgetKind, + pub widget_options: Option, + pub doc_link: Option, + pub hidden: bool, +} + #[remain::sorted] #[derive(Clone, Debug)] pub enum SiPkgProp<'a> { Array { name: String, - default_value: Option, - func_unique_id: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - doc_link: Option, - hidden: bool, + data: Option, + unique_id: Option, hash: Hash, source: Source<'a>, }, Boolean { name: String, - default_value: Option, - func_unique_id: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - doc_link: Option, - hidden: bool, + data: Option, + unique_id: Option, hash: Hash, source: Source<'a>, }, Map { name: String, - default_value: Option, - func_unique_id: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - doc_link: Option, - hidden: bool, + data: Option, + unique_id: Option, hash: Hash, source: Source<'a>, }, Number { name: String, - default_value: Option, - func_unique_id: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - doc_link: Option, - hidden: bool, + data: Option, + unique_id: Option, hash: Hash, source: Source<'a>, }, Object { name: String, - default_value: Option, - func_unique_id: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - doc_link: Option, - hidden: bool, + data: Option, + unique_id: Option, hash: Hash, source: Source<'a>, }, String { name: String, - default_value: Option, - func_unique_id: Option, - widget_kind: PropSpecWidgetKind, - widget_options: Option, - hidden: bool, - doc_link: Option, + data: Option, + unique_id: Option, hash: Hash, source: Source<'a>, }, @@ -141,146 +128,113 @@ impl<'a> SiPkgProp<'a> { let hash = prop_hashed_node.hash(); let source = Source::new(graph, node_idx); - Ok(match prop_node { - PropNode::String { + let (name, data, unique_id) = match &prop_node { + PropNode::Array { + name, + data, + unique_id, + } + | PropNode::Boolean { + name, + data, + unique_id, + } + | PropNode::Map { + name, + data, + unique_id, + } + | PropNode::Integer { + name, + data, + unique_id, + } + | PropNode::Object { + name, + data, + unique_id, + } + | PropNode::String { name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, + data, + unique_id, + } => ( + name.to_owned(), + data.to_owned().map( + |PropNodeData { + name, + default_value, + func_unique_id, + widget_kind, + widget_options, + hidden, + doc_link, + }| SiPkgPropData { + name, + default_value, + func_unique_id, + widget_kind, + widget_options, + hidden, + doc_link, + }, + ), + unique_id.to_owned(), + ), + }; - doc_link, - } => Self::String { + Ok(match prop_node { + PropNode::String { .. } => Self::String { name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, + data, + unique_id, - doc_link, hash, source, }, - PropNode::Integer { - name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, - - doc_link, - } => Self::Number { + PropNode::Integer { .. } => Self::Number { name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, + data, + unique_id, - doc_link, hash, source, }, - PropNode::Boolean { - name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, - - doc_link, - } => Self::Boolean { + PropNode::Boolean { .. } => Self::Boolean { name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, + data, + unique_id, - doc_link, hash, source, }, - PropNode::Map { - name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, - - doc_link, - } => Self::Map { + PropNode::Map { .. } => Self::Map { name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, + data, + unique_id, - doc_link, hash, source, }, - PropNode::Array { + PropNode::Array { .. } => Self::Array { name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, + data, + unique_id, - doc_link, - } => Self::Array { - name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, - doc_link, hash, source, }, - PropNode::Object { - name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, - - doc_link, - } => Self::Object { + PropNode::Object { .. } => Self::Object { name, - default_value, - func_unique_id, - widget_kind, - widget_options, - hidden, + data, + unique_id, - doc_link, hash, source, }, }) } - pub fn func_unique_id(&self) -> Option { - match self { - Self::String { func_unique_id, .. } - | Self::Number { func_unique_id, .. } - | Self::Boolean { func_unique_id, .. } - | Self::Map { func_unique_id, .. } - | Self::Array { func_unique_id, .. } - | Self::Object { func_unique_id, .. } => *func_unique_id, - } - } - pub fn name(&self) -> &str { match self { Self::String { name, .. } diff --git a/lib/si-pkg/src/pkg/schema.rs b/lib/si-pkg/src/pkg/schema.rs index eb8a691de0..9957d7dcb0 100644 --- a/lib/si-pkg/src/pkg/schema.rs +++ b/lib/si-pkg/src/pkg/schema.rs @@ -3,14 +3,41 @@ use petgraph::prelude::*; use super::{PkgResult, SiPkgError, SiPkgSchemaVariant, Source}; +use crate::SchemaSpecData; use crate::{node::PkgNode, SchemaSpec}; +#[derive(Clone, Debug)] +pub struct SiPkgSchemaData { + pub name: String, + pub category: String, + pub category_name: Option, + pub ui_hidden: bool, +} + +impl SiPkgSchemaData { + pub fn name(&self) -> &str { + self.name.as_ref() + } + + pub fn category(&self) -> &str { + self.category.as_str() + } + + pub fn category_name(&self) -> Option<&str> { + self.category_name.as_deref() + } + + pub fn ui_hidden(&self) -> bool { + self.ui_hidden + } +} + #[derive(Clone, Debug)] pub struct SiPkgSchema<'a> { name: String, - category: String, - category_name: Option, - ui_hidden: bool, + data: Option, + unique_id: Option, + deleted: bool, hash: Hash, @@ -35,9 +62,14 @@ impl<'a> SiPkgSchema<'a> { let schema = Self { name: schema_node.name, - category: schema_node.category, - category_name: schema_node.category_name, - ui_hidden: schema_node.ui_hidden, + data: schema_node.data.map(|data| SiPkgSchemaData { + name: data.name, + category: data.category, + category_name: data.category_name, + ui_hidden: data.ui_hidden, + }), + unique_id: schema_node.unique_id, + deleted: schema_node.deleted, hash: schema_hashed_node.hash(), source: Source::new(graph, node_idx), }; @@ -49,12 +81,16 @@ impl<'a> SiPkgSchema<'a> { self.name.as_ref() } - pub fn category(&self) -> &str { - self.category.as_ref() + pub fn unique_id(&self) -> Option<&str> { + self.unique_id.as_deref() } - pub fn category_name(&self) -> Option<&str> { - self.category_name.as_deref() + pub fn deleted(&self) -> bool { + self.deleted + } + + pub fn data(&self) -> Option<&SiPkgSchemaData> { + self.data.as_ref() } pub fn variants(&self) -> PkgResult>> { @@ -73,10 +109,6 @@ impl<'a> SiPkgSchema<'a> { Ok(variants) } - pub fn ui_hidden(&self) -> bool { - self.ui_hidden - } - pub fn hash(&self) -> Hash { self.hash } @@ -84,17 +116,26 @@ impl<'a> SiPkgSchema<'a> { pub async fn to_spec(&self) -> PkgResult { let mut builder = SchemaSpec::builder(); - builder.name(self.name()).category(self.category()); - if let Some(category_name) = self.category_name() { - builder.category_name(category_name); + builder.name(self.name()); + if let Some(unique_id) = self.unique_id() { + builder.unique_id(unique_id); + } + + if let Some(data) = self.data() { + let mut data_builder = SchemaSpecData::builder(); + data_builder.name(self.name()); + if let Some(category_name) = data.category_name() { + data_builder.category_name(category_name); + } + data_builder.ui_hidden(data.ui_hidden()); + data_builder.category(data.category()); + builder.data(data_builder.build()?); } for variant in self.variants()? { builder.variant(variant.to_spec().await?); } - builder.ui_hidden(self.ui_hidden()); - Ok(builder.build()?) } } diff --git a/lib/si-pkg/src/pkg/si_prop_func.rs b/lib/si-pkg/src/pkg/si_prop_func.rs index 5d3b15f35c..d03848f1c7 100644 --- a/lib/si-pkg/src/pkg/si_prop_func.rs +++ b/lib/si-pkg/src/pkg/si_prop_func.rs @@ -4,14 +4,16 @@ use petgraph::prelude::*; use super::{PkgResult, SiPkgError, Source}; use crate::{ - node::PkgNode, AttrFuncInputSpec, FuncUniqueId, SiPkgAttrFuncInput, SiPropFuncSpec, - SiPropFuncSpecKind, + node::PkgNode, AttrFuncInputSpec, SiPkgAttrFuncInput, SiPropFuncSpec, SiPropFuncSpecKind, }; #[derive(Clone, Debug)] pub struct SiPkgSiPropFunc<'a> { kind: SiPropFuncSpecKind, - func_unique_id: FuncUniqueId, + func_unique_id: String, + unique_id: Option, + deleted: bool, + hash: Hash, source: Source<'a>, } @@ -35,6 +37,9 @@ impl<'a> SiPkgSiPropFunc<'a> { Ok(Self { kind: node.kind, func_unique_id: node.func_unique_id, + unique_id: node.unique_id, + deleted: node.deleted, + hash: hashed_node.hash(), source: Source::new(graph, node_idx), }) @@ -44,8 +49,16 @@ impl<'a> SiPkgSiPropFunc<'a> { self.kind } - pub fn func_unique_id(&self) -> FuncUniqueId { - self.func_unique_id + pub fn func_unique_id(&self) -> &str { + self.func_unique_id.as_str() + } + + pub fn unique_id(&self) -> Option<&str> { + self.unique_id.as_deref() + } + + pub fn deleted(&self) -> bool { + self.deleted } pub fn inputs(&self) -> PkgResult> { @@ -82,6 +95,8 @@ impl<'a> TryFrom> for SiPropFuncSpec { Ok(builder .kind(value.kind) + .unique_id(value.unique_id) + .deleted(value.deleted) .func_unique_id(value.func_unique_id) .build()?) } diff --git a/lib/si-pkg/src/pkg/socket.rs b/lib/si-pkg/src/pkg/socket.rs index 64d6a9aff3..78793868d0 100644 --- a/lib/si-pkg/src/pkg/socket.rs +++ b/lib/si-pkg/src/pkg/socket.rs @@ -3,15 +3,44 @@ use petgraph::prelude::*; use super::{PkgResult, SiPkgAttrFuncInput, SiPkgError, Source}; -use crate::{node::PkgNode, FuncUniqueId, SocketSpec, SocketSpecArity, SocketSpecKind}; +use crate::spec::SocketSpecData; +use crate::{node::PkgNode, SocketSpec, SocketSpecArity, SocketSpecKind}; #[derive(Clone, Debug)] -pub struct SiPkgSocket<'a> { - func_unique_id: Option, - kind: SocketSpecKind, +pub struct SiPkgSocketData { name: String, + func_unique_id: Option, + kind: SocketSpecKind, arity: SocketSpecArity, ui_hidden: bool, +} + +impl SiPkgSocketData { + pub fn name(&self) -> &str { + self.name.as_str() + } + pub fn func_unique_id(&self) -> Option<&str> { + self.func_unique_id.as_deref() + } + + pub fn kind(&self) -> SocketSpecKind { + self.kind + } + + pub fn arity(&self) -> SocketSpecArity { + self.arity + } + + pub fn ui_hidden(&self) -> bool { + self.ui_hidden + } +} + +#[derive(Clone, Debug)] +pub struct SiPkgSocket<'a> { + name: String, + data: Option, + unique_id: Option, hash: Hash, source: Source<'a>, @@ -34,11 +63,16 @@ impl<'a> SiPkgSocket<'a> { }; Ok(Self { - func_unique_id: node.func_unique_id, - arity: node.arity, - kind: node.kind, name: node.name, - ui_hidden: node.ui_hidden, + data: node.data.map(|data| SiPkgSocketData { + name: data.name, + kind: data.kind, + func_unique_id: data.func_unique_id, + arity: data.arity, + ui_hidden: data.ui_hidden, + }), + unique_id: node.unique_id, + hash: hashed_node.hash(), source: Source::new(graph, node_idx), }) @@ -58,18 +92,6 @@ impl<'a> SiPkgSocket<'a> { Ok(inputs) } - pub fn arity(&self) -> SocketSpecArity { - self.arity - } - - pub fn func_unique_id(&self) -> Option { - self.func_unique_id - } - - pub fn kind(&self) -> SocketSpecKind { - self.kind - } - pub fn name(&self) -> &str { &self.name } @@ -78,8 +100,12 @@ impl<'a> SiPkgSocket<'a> { self.hash } - pub fn ui_hidden(&self) -> bool { - self.ui_hidden + pub fn data(&self) -> Option<&SiPkgSocketData> { + self.data.as_ref() + } + + pub fn unique_id(&self) -> Option<&str> { + self.unique_id.as_deref() } pub fn source(&self) -> &Source<'a> { @@ -94,11 +120,20 @@ impl<'a> TryFrom> for SocketSpec { let mut builder = SocketSpec::builder(); builder - .kind(value.kind) - .name(value.name()) - .func_unique_id(value.func_unique_id) - .arity(value.arity) - .ui_hidden(value.ui_hidden); + .name(&value.name) + .unique_id(value.unique_id.to_owned()); + + if let Some(data) = &value.data { + let mut data_builder = SocketSpecData::builder(); + if let Some(func_unique_id) = &data.func_unique_id { + data_builder.func_unique_id(func_unique_id); + } + data_builder + .name(&data.name) + .kind(data.kind) + .arity(data.arity) + .ui_hidden(data.ui_hidden); + } for input in value.inputs()? { builder.input(input.try_into()?); diff --git a/lib/si-pkg/src/pkg/validation.rs b/lib/si-pkg/src/pkg/validation.rs index 5b6bb2e6fc..930a9ed096 100644 --- a/lib/si-pkg/src/pkg/validation.rs +++ b/lib/si-pkg/src/pkg/validation.rs @@ -9,7 +9,7 @@ use crate::{node::PkgNode, ValidationSpec, ValidationSpecKind}; #[derive(Clone, Debug)] pub enum SiPkgValidation<'a> { CustomValidation { - func_unique_id: Hash, + func_unique_id: String, hash: Hash, source: Source<'a>, }, @@ -170,7 +170,7 @@ impl<'a> TryFrom> for ValidationSpec { } SiPkgValidation::CustomValidation { func_unique_id, .. } => { builder.kind(ValidationSpecKind::CustomValidation); - builder.func_unique_id(func_unique_id); + builder.func_unique_id(&func_unique_id); } SiPkgValidation::StringInStringArray { expected, diff --git a/lib/si-pkg/src/pkg/variant.rs b/lib/si-pkg/src/pkg/variant.rs index cf0c580f6e..0fb1ca55ea 100644 --- a/lib/si-pkg/src/pkg/variant.rs +++ b/lib/si-pkg/src/pkg/variant.rs @@ -7,24 +7,54 @@ use tokio::sync::Mutex; use url::Url; use super::{ - PkgResult, SiPkgActionFunc, SiPkgError, SiPkgLeafFunction, SiPkgProp, SiPkgSiPropFunc, - SiPkgSocket, Source, + PkgResult, SiPkgActionFunc, SiPkgError, SiPkgLeafFunction, SiPkgProp, SiPkgPropData, + SiPkgSiPropFunc, SiPkgSocket, Source, }; use crate::{ node::{PkgNode, PropChildNode, SchemaVariantChildNode}, - AttrFuncInputSpec, FuncUniqueId, MapKeyFuncSpec, PropSpec, PropSpecBuilder, PropSpecKind, - SchemaVariantSpec, SchemaVariantSpecBuilder, SchemaVariantSpecComponentType, + AttrFuncInputSpec, MapKeyFuncSpec, PropSpec, PropSpecBuilder, PropSpecKind, SchemaVariantSpec, + SchemaVariantSpecBuilder, SchemaVariantSpecComponentType, SchemaVariantSpecData, SchemaVariantSpecPropRoot, }; #[derive(Clone, Debug)] -pub struct SiPkgSchemaVariant<'a> { +pub struct SiPkgSchemaVariantData { name: String, link: Option, color: Option, component_type: SchemaVariantSpecComponentType, - func_unique_id: FuncUniqueId, + func_unique_id: String, +} + +impl SiPkgSchemaVariantData { + pub fn name(&self) -> &str { + self.name.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() + } +} + +#[derive(Clone, Debug)] +pub struct SiPkgSchemaVariant<'a> { + name: String, + data: Option, + unique_id: Option, + deleted: bool, hash: Hash, @@ -79,36 +109,37 @@ impl<'a> SiPkgSchemaVariant<'a> { }; let schema_variant = Self { - name: schema_variant_node.name, - link: schema_variant_node.link, - color: schema_variant_node.color, - component_type: schema_variant_node.component_type, + name: schema_variant_node.name.to_owned(), + data: schema_variant_node.data.map(|data| SiPkgSchemaVariantData { + name: schema_variant_node.name, + link: data.link, + color: data.color, + component_type: data.component_type, + func_unique_id: data.func_unique_id, + }), + unique_id: schema_variant_node.unique_id, + deleted: schema_variant_node.deleted, hash: schema_variant_hashed_node.hash(), source: Source::new(graph, node_idx), - func_unique_id: schema_variant_node.func_unique_id, }; Ok(schema_variant) } - pub fn name(&self) -> &str { - self.name.as_ref() - } - - pub fn link(&self) -> Option<&Url> { - self.link.as_ref() + pub fn data(&self) -> Option<&SiPkgSchemaVariantData> { + self.data.as_ref() } - pub fn color(&self) -> Option<&str> { - self.color.as_deref() + pub fn name(&self) -> &str { + self.name.as_ref() } - pub fn component_type(&self) -> SchemaVariantSpecComponentType { - self.component_type + pub fn unique_id(&self) -> Option<&str> { + self.unique_id.as_deref() } - pub fn func_unique_id(&self) -> FuncUniqueId { - self.func_unique_id + pub fn deleted(&self) -> bool { + self.deleted } impl_variant_children_from_graph!(sockets, SchemaVariantChildNode::Sockets, SiPkgSocket); @@ -320,16 +351,27 @@ impl<'a> SiPkgSchemaVariant<'a> { pub async fn to_spec(&self) -> PkgResult { let mut builder = SchemaVariantSpec::builder(); - builder - .name(self.name()) - .component_type(self.component_type); + builder.name(self.name()).deleted(self.deleted); - if let Some(link) = self.link() { - builder.link(link.to_owned()); + if let Some(unique_id) = self.unique_id() { + builder.unique_id(unique_id); } - if let Some(color) = self.color() { - builder.color(color); + if let Some(data) = self.data() { + let mut data_builder = SchemaVariantSpecData::builder(); + + data_builder.name(self.name()); + 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.func_unique_id(data.func_unique_id()); + builder.data(data_builder.build()?); } for action_func in self.action_funcs()? { @@ -344,8 +386,6 @@ impl<'a> SiPkgSchemaVariant<'a> { builder.si_prop_func(si_prop_func.try_into()?); } - builder.func_unique_id(self.func_unique_id); - self.build_prop_specs(SchemaVariantSpecPropRoot::Domain, &mut builder) .await?; self.build_prop_specs(SchemaVariantSpecPropRoot::ResourceValue, &mut builder) @@ -378,24 +418,34 @@ async fn create_prop_stack( }; let mut builder = PropSpec::builder(); + builder.has_data(false); + + let default_value = match &spec { + SiPkgProp::Array { data, .. } + | SiPkgProp::Boolean { data, .. } + | SiPkgProp::Number { data, .. } => { + data.as_ref().and_then(|data| data.default_value.to_owned()) + } + _ => None, + }; match &spec { - SiPkgProp::String { default_value, .. } => { + SiPkgProp::String { .. } => { builder.kind(PropSpecKind::String); if let Some(dv) = default_value { - builder.default_value(serde_json::to_value(dv)?); + builder.default_value(dv); } } - SiPkgProp::Boolean { default_value, .. } => { + SiPkgProp::Boolean { .. } => { builder.kind(PropSpecKind::Boolean); if let Some(dv) = default_value { builder.default_value(serde_json::to_value(dv)?); } } - SiPkgProp::Number { default_value, .. } => { + SiPkgProp::Number { .. } => { builder.kind(PropSpecKind::Number); if let Some(dv) = default_value { - builder.default_value(serde_json::to_value(dv)?); + builder.default_value(dv); } } SiPkgProp::Object { .. } => { @@ -413,66 +463,36 @@ async fn create_prop_stack( } match &spec { - SiPkgProp::String { - name, - func_unique_id, - widget_kind, - widget_options, - hidden, - .. - } - | SiPkgProp::Map { - name, - func_unique_id, - widget_kind, - widget_options, - hidden, - .. - } - | SiPkgProp::Array { - name, - func_unique_id, - widget_kind, - widget_options, - hidden, - .. - } - | SiPkgProp::Number { - name, - func_unique_id, - widget_kind, - widget_options, - hidden, - .. - } - | SiPkgProp::Object { - name, - func_unique_id, - widget_kind, - widget_options, - hidden, - .. - } - | SiPkgProp::Boolean { - name, - func_unique_id, - widget_kind, - widget_options, - hidden, - .. - } => { + SiPkgProp::String { name, data, .. } + | SiPkgProp::Map { name, data, .. } + | SiPkgProp::Array { name, data, .. } + | SiPkgProp::Number { name, data, .. } + | SiPkgProp::Object { name, data, .. } + | SiPkgProp::Boolean { name, data, .. } => { builder.name(name); - builder.hidden(*hidden); - builder.widget_kind(*widget_kind); - if let Some(widget_options) = widget_options { - builder.widget_options(widget_options.to_owned()); - } + if let Some(SiPkgPropData { + widget_kind, + widget_options, + func_unique_id, + hidden, + .. + }) = data + { + builder + .has_data(true) + .hidden(*hidden) + .widget_kind(*widget_kind); - if let Some(func_unique_id) = func_unique_id { - builder.func_unique_id(*func_unique_id); - for input in spec.inputs()? { - builder.input(AttrFuncInputSpec::try_from(input)?); + 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)?); + } } } } diff --git a/lib/si-pkg/src/spec.rs b/lib/si-pkg/src/spec.rs index 38ee5632c8..eef1a002e0 100644 --- a/lib/si-pkg/src/spec.rs +++ b/lib/si-pkg/src/spec.rs @@ -39,9 +39,12 @@ pub struct PkgSpec { pub created_at: DateTime, #[builder(setter(into))] pub created_by: String, - #[builder(setter(into), default)] + #[builder(setter(into, strip_option), default)] #[serde(default)] pub default_change_set: Option, + #[builder(setter(into, strip_option), default)] + #[serde(default)] + pub workspace_pk: Option, #[builder(setter(each(name = "schema", into)), default)] #[serde(default)] @@ -61,10 +64,10 @@ impl PkgSpec { PkgSpecBuilder::default() } - pub fn func_for_unique_id(&self, unique_id: &FuncUniqueId) -> Option<&FuncSpec> { + pub fn func_for_unique_id(&self, unique_id: &str) -> Option<&FuncSpec> { self.funcs .iter() - .find(|func_spec| &func_spec.unique_id == unique_id) + .find(|func_spec| func_spec.unique_id == unique_id) } pub fn func_for_name(&self, name: impl AsRef) -> Option<&FuncSpec> { diff --git a/lib/si-pkg/src/spec/action_func.rs b/lib/si-pkg/src/spec/action_func.rs index 2682c50fe9..1428bf2f2a 100644 --- a/lib/si-pkg/src/spec/action_func.rs +++ b/lib/si-pkg/src/spec/action_func.rs @@ -2,7 +2,7 @@ use derive_builder::Builder; use serde::{Deserialize, Serialize}; use strum::{AsRefStr, Display, EnumIter, EnumString}; -use super::{FuncUniqueId, SpecError}; +use super::SpecError; #[derive( Debug, @@ -30,10 +30,18 @@ pub enum ActionFuncSpecKind { #[builder(build_fn(error = "SpecError"))] pub struct ActionFuncSpec { #[builder(setter(into))] - pub func_unique_id: FuncUniqueId, + pub func_unique_id: String, #[builder(setter(into))] pub kind: ActionFuncSpecKind, + + #[builder(setter(into), default)] + #[serde(default)] + pub unique_id: Option, + + #[builder(setter(into), default)] + #[serde(default)] + pub deleted: bool, } impl ActionFuncSpec { diff --git a/lib/si-pkg/src/spec/attr_func_input.rs b/lib/si-pkg/src/spec/attr_func_input.rs index db19540882..4d8ccd3c74 100644 --- a/lib/si-pkg/src/spec/attr_func_input.rs +++ b/lib/si-pkg/src/spec/attr_func_input.rs @@ -29,9 +29,30 @@ pub enum AttrFuncInputSpecKind { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "kind", rename_all = "camelCase")] pub enum AttrFuncInputSpec { - InputSocket { name: String, socket_name: String }, - OutputSocket { name: String, socket_name: String }, - Prop { name: String, prop_path: String }, + InputSocket { + name: String, + socket_name: String, + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, + }, + OutputSocket { + name: String, + socket_name: String, + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, + }, + Prop { + name: String, + prop_path: String, + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, + }, } #[derive(Clone, Debug, Default)] @@ -40,6 +61,8 @@ pub struct AttrFuncInputSpecBuilder { name: Option, prop_path: Option, socket_name: Option, + unique_id: Option, + deleted: bool, } impl AttrFuncInputSpec { @@ -69,38 +92,49 @@ impl AttrFuncInputSpecBuilder { self } + pub fn unique_id(&mut self, unique_id: impl Into) -> &mut Self { + self.unique_id = Some(unique_id.into()); + self + } + + pub fn deleted(&mut self, deleted: impl Into) -> &mut Self { + self.deleted = deleted.into(); + self + } + pub fn build(&self) -> Result { - Ok(match self.kind { + let self_clone = self.to_owned(); + Ok(match self_clone.kind { Some(kind) => match kind { AttrFuncInputSpecKind::Prop => AttrFuncInputSpec::Prop { - name: self + name: self_clone .name - .clone() .ok_or(UninitializedFieldError::from("name"))?, - prop_path: self - .clone() + prop_path: self_clone .prop_path .ok_or(UninitializedFieldError::from("prop_path"))?, + deleted: self_clone.deleted, + unique_id: self_clone.unique_id, }, AttrFuncInputSpecKind::InputSocket => AttrFuncInputSpec::InputSocket { - name: self + name: self_clone .name - .clone() .ok_or(UninitializedFieldError::from("name"))?, - socket_name: self + socket_name: self_clone .socket_name - .clone() .ok_or(UninitializedFieldError::from("socket_name"))?, + deleted: self_clone.deleted, + unique_id: self_clone.unique_id, }, AttrFuncInputSpecKind::OutputSocket => AttrFuncInputSpec::OutputSocket { - name: self + name: self_clone .name - .clone() .ok_or(UninitializedFieldError::from("name"))?, - socket_name: self + socket_name: self_clone .socket_name - .clone() .ok_or(UninitializedFieldError::from("socket_name"))?, + deleted: self_clone.deleted, + unique_id: self_clone.unique_id, }, }, None => { diff --git a/lib/si-pkg/src/spec/change_set.rs b/lib/si-pkg/src/spec/change_set.rs index 4dc96e9d2a..a2a1081fab 100644 --- a/lib/si-pkg/src/spec/change_set.rs +++ b/lib/si-pkg/src/spec/change_set.rs @@ -20,10 +20,10 @@ pub struct ChangeSetSpec { #[builder(setter(into))] pub name: String, - #[builder(setter(into))] + #[builder(setter(into, strip_option), default)] pub based_on_change_set: Option, - #[builder(setter(into))] + #[builder(setter(into), default = "ChangeSetSpecStatus::Open")] pub status: ChangeSetSpecStatus, #[builder(setter(each(name = "schema", into)), default)] diff --git a/lib/si-pkg/src/spec/func.rs b/lib/si-pkg/src/spec/func.rs index b5f332d53d..1810fb180b 100644 --- a/lib/si-pkg/src/spec/func.rs +++ b/lib/si-pkg/src/spec/func.rs @@ -1,6 +1,5 @@ use base64::{engine::general_purpose, Engine}; use derive_builder::Builder; -use object_tree::Hash; use serde::{Deserialize, Serialize}; use strum::{AsRefStr, Display, EnumIter, EnumString}; use url::Url; @@ -42,6 +41,12 @@ pub struct FuncArgumentSpec { pub kind: FuncArgumentKind, #[builder(setter(into), default)] pub element_kind: Option, + #[builder(setter(into), default)] + #[serde(default)] + pub unique_id: Option, + #[builder(setter(into), default)] + #[serde(default)] + pub deleted: bool, } impl FuncArgumentSpec { @@ -92,12 +97,10 @@ pub enum FuncSpecBackendResponseType { Validation, } -pub type FuncUniqueId = Hash; - #[derive(Builder, Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[builder(build_fn(error = "SpecError"))] -pub struct FuncSpec { +pub struct FuncSpecData { #[builder(setter(into))] pub name: String, #[builder(setter(into, strip_option), default)] @@ -114,24 +117,18 @@ pub struct FuncSpec { pub response_type: FuncSpecBackendResponseType, #[builder(setter(into), default)] pub hidden: bool, - #[builder(field(type = "FuncUniqueId", build = "self.build_func_unique_id()"))] - pub unique_id: FuncUniqueId, - #[builder(setter(into, strip_option), default)] pub link: Option, - - #[builder(setter(each(name = "argument"), into), default)] - pub arguments: Vec, } -impl FuncSpec { +impl FuncSpecData { #[must_use] - pub fn builder() -> FuncSpecBuilder { - FuncSpecBuilder::default() + pub fn builder() -> FuncSpecDataBuilder { + FuncSpecDataBuilder::default() } } -impl FuncSpecBuilder { +impl FuncSpecDataBuilder { #[allow(unused_mut)] pub fn try_link(&mut self, value: V) -> Result<&mut Self, V::Error> where @@ -145,46 +142,29 @@ impl FuncSpecBuilder { let code_plaintext = code.into(); self.code_base64(general_purpose::STANDARD_NO_PAD.encode(code_plaintext)) } +} - fn build_func_unique_id(&self) -> Hash { - // Not happy about all these clones and unwraps... - let mut bytes = vec![]; - bytes.extend(self.name.clone().unwrap_or("".to_string()).as_bytes()); - bytes.extend( - self.display_name - .clone() - .unwrap_or(Some("".to_string())) - .unwrap_or("".to_string()) - .as_bytes(), - ); - bytes.extend( - self.description - .clone() - .unwrap_or(Some("".to_string())) - .unwrap_or("".to_string()) - .as_bytes(), - ); - bytes.extend(self.handler.clone().unwrap_or("".to_string()).as_bytes()); - bytes.extend( - self.code_base64 - .clone() - .unwrap_or("".to_string()) - .as_bytes(), - ); - bytes.extend( - self.backend_kind - .unwrap_or(FuncSpecBackendKind::JsAttribute) - .to_string() - .as_bytes(), - ); - bytes.extend( - self.response_type - .unwrap_or(FuncSpecBackendResponseType::Object) - .to_string() - .as_bytes(), - ); - bytes.extend(&[self.hidden.unwrap_or(false).into()]); +#[derive(Builder, Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[builder(build_fn(error = "SpecError"))] +pub struct FuncSpec { + #[builder(setter(into))] + pub name: String, + #[builder(setter(into))] + pub unique_id: String, + #[builder(setter(into, strip_option), default)] + pub data: Option, + #[builder(setter(into), default)] + #[serde(default)] + pub deleted: bool, + + #[builder(setter(each(name = "argument"), into), default)] + pub arguments: Vec, +} - Hash::new(&bytes) +impl FuncSpec { + #[must_use] + pub fn builder() -> FuncSpecBuilder { + FuncSpecBuilder::default() } } diff --git a/lib/si-pkg/src/spec/leaf_function.rs b/lib/si-pkg/src/spec/leaf_function.rs index acb69a17ec..500cc5c260 100644 --- a/lib/si-pkg/src/spec/leaf_function.rs +++ b/lib/si-pkg/src/spec/leaf_function.rs @@ -2,7 +2,7 @@ use derive_builder::Builder; use serde::{Deserialize, Serialize}; use strum::{AsRefStr, Display, EnumIter, EnumString}; -use super::{FuncUniqueId, SpecError}; +use super::SpecError; #[remain::sorted] #[derive( @@ -51,11 +51,19 @@ pub enum LeafInputLocation { #[builder(build_fn(error = "SpecError"))] pub struct LeafFunctionSpec { #[builder(setter(into))] - pub func_unique_id: FuncUniqueId, + pub func_unique_id: String, #[builder(setter(into))] pub leaf_kind: LeafKind, + #[builder(setter(into), default)] + #[serde(default)] + pub unique_id: Option, + + #[builder(setter(into), default)] + #[serde(default)] + pub deleted: bool, + #[builder(setter(into), default)] pub inputs: Vec, } diff --git a/lib/si-pkg/src/spec/map_key_func.rs b/lib/si-pkg/src/spec/map_key_func.rs index ea5b0e187f..9f112a3bde 100644 --- a/lib/si-pkg/src/spec/map_key_func.rs +++ b/lib/si-pkg/src/spec/map_key_func.rs @@ -1,7 +1,7 @@ use derive_builder::Builder; use serde::{Deserialize, Serialize}; -use super::{AttrFuncInputSpec, FuncUniqueId, SpecError}; +use super::{AttrFuncInputSpec, SpecError}; /// MapKeyFuncSpecs track custom functions set on keys to a map #[derive(Builder, Clone, Debug, Deserialize, Serialize)] @@ -11,7 +11,7 @@ pub struct MapKeyFuncSpec { #[builder(setter(into))] pub key: String, #[builder(setter(into))] - pub func_unique_id: FuncUniqueId, + pub func_unique_id: String, #[builder(setter(each(name = "input"), into), default)] pub inputs: Vec, } diff --git a/lib/si-pkg/src/spec/prop.rs b/lib/si-pkg/src/spec/prop.rs index bffcbc46fc..7ef7365a21 100644 --- a/lib/si-pkg/src/spec/prop.rs +++ b/lib/si-pkg/src/spec/prop.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use strum::{AsRefStr, Display, EnumIter, EnumString}; use url::Url; -use super::{AttrFuncInputSpec, FuncUniqueId, MapKeyFuncSpec, SpecError, ValidationSpec}; +use super::{AttrFuncInputSpec, MapKeyFuncSpec, SpecError, ValidationSpec}; #[remain::sorted] #[derive( @@ -46,6 +46,20 @@ impl From<&PropSpec> for PropSpecWidgetKind { } } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PropSpecData { + pub name: String, + pub default_value: Option, + pub validations: Option>, + pub func_unique_id: Option, + pub inputs: Option>, + pub widget_kind: Option, + pub widget_options: Option, + pub hidden: Option, + pub doc_link: Option, +} + #[remain::sorted] #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "kind", rename_all = "camelCase")] @@ -53,78 +67,42 @@ pub enum PropSpec { #[serde(rename_all = "camelCase")] Array { name: String, - default_value: Option, + data: Option, + unique_id: Option, type_prop: Box, - validations: Option>, - func_unique_id: Option, - inputs: Option>, - widget_kind: Option, - widget_options: Option, - hidden: Option, - doc_link: Option, }, #[serde(rename_all = "camelCase")] Boolean { name: String, - default_value: Option, - validations: Option>, - func_unique_id: Option, - inputs: Option>, - widget_kind: Option, - widget_options: Option, - hidden: Option, - doc_link: Option, + data: Option, + unique_id: Option, }, #[serde(rename_all = "camelCase")] Map { name: String, - default_value: Option, + data: Option, + unique_id: Option, type_prop: Box, - validations: Option>, - func_unique_id: Option, - inputs: Option>, - widget_kind: Option, - widget_options: Option, - hidden: Option, - doc_link: Option, map_key_funcs: Option>, }, #[serde(rename_all = "camelCase")] Number { name: String, - default_value: Option, - validations: Option>, - func_unique_id: Option, - inputs: Option>, - widget_kind: Option, - widget_options: Option, - hidden: Option, - doc_link: Option, + data: Option, + unique_id: Option, }, #[serde(rename_all = "camelCase")] Object { name: String, - default_value: Option, + data: Option, + unique_id: Option, entries: Vec, - validations: Option>, - func_unique_id: Option, - inputs: Option>, - widget_kind: Option, - widget_options: Option, - hidden: Option, - doc_link: Option, }, #[serde(rename_all = "camelCase")] String { name: String, - default_value: Option, - validations: Option>, - func_unique_id: Option, - inputs: Option>, - widget_kind: Option, - widget_options: Option, - hidden: Option, - doc_link: Option, + data: Option, + unique_id: Option, }, } @@ -145,12 +123,12 @@ pub enum PropSpecKind { String, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct PropSpecBuilder { default_value: Option, doc_link: Option, entries: Vec, - func_unique_id: Option, + func_unique_id: Option, hidden: bool, inputs: Vec, kind: Option, @@ -160,12 +138,37 @@ pub struct PropSpecBuilder { validations: Vec, widget_kind: Option, widget_options: Option, + unique_id: Option, + has_data: bool, +} + +impl Default for PropSpecBuilder { + fn default() -> Self { + Self { + default_value: None, + doc_link: None, + entries: vec![], + func_unique_id: None, + hidden: false, + inputs: vec![], + kind: None, + map_key_funcs: vec![], + name: None, + type_prop: None, + validations: vec![], + widget_kind: None, + widget_options: None, + unique_id: None, + has_data: true, + } + } } impl PropSpecBuilder { #[allow(unused_mut)] pub fn default_value(&mut self, value: serde_json::Value) -> &mut Self { self.default_value = Some(value); + self.has_data = true; self } @@ -199,6 +202,7 @@ impl PropSpecBuilder { #[allow(unused_mut)] pub fn validation(&mut self, value: impl Into) -> &mut Self { + self.has_data = true; self.validations.push(value.into()); self } @@ -210,13 +214,15 @@ impl PropSpecBuilder { } #[allow(unused_mut)] - pub fn func_unique_id(&mut self, value: FuncUniqueId) -> &mut Self { - self.func_unique_id = Some(value); + pub fn func_unique_id(&mut self, value: impl Into) -> &mut Self { + self.has_data = true; + self.func_unique_id = Some(value.into()); self } #[allow(unused_mut)] pub fn input(&mut self, value: impl Into) -> &mut Self { + self.has_data = true; self.inputs.push(value.into()); self } @@ -242,10 +248,21 @@ impl PropSpecBuilder { } pub fn map_key_func(&mut self, value: impl Into) -> &mut Self { + self.has_data = true; self.map_key_funcs.push(value.into()); self } + pub fn has_data(&mut self, value: impl Into) -> &mut Self { + self.has_data = value.into(); + self + } + + pub fn unique_id(&mut self, value: impl Into) -> &mut Self { + self.unique_id = Some(value.into()); + self + } + #[allow(unused_mut)] pub fn try_doc_link(&mut self, value: V) -> Result<&mut Self, V::Error> where @@ -270,7 +287,7 @@ impl PropSpecBuilder { let validations = self.validations.clone(); let inputs = self.inputs.clone(); - let func_unique_id = self.func_unique_id; + let func_unique_id = self.func_unique_id.to_owned(); let widget_kind = self.widget_kind; let widget_options = self.widget_options.to_owned(); let hidden = self.hidden; @@ -279,115 +296,132 @@ impl PropSpecBuilder { Ok(match self.kind { Some(kind) => match kind { PropSpecKind::String => PropSpec::String { - name, - default_value: match &self.default_value { - Some(serde_json::Value::String(s)) => Some(s.to_owned()), - Some(_) => { - return Err(SpecError::ValidationError( - "String prop must get a string as a default value".to_string(), - )); - } - None => None, + name: name.to_owned(), + unique_id: self.unique_id.to_owned(), + data: if self.has_data { + Some(PropSpecData { + name, + default_value: self.default_value.to_owned(), + validations: Some(validations), + func_unique_id, + inputs: Some(inputs), + widget_kind, + widget_options, + hidden: Some(hidden), + doc_link, + }) + } else { + None }, - validations: Some(validations), - func_unique_id, - inputs: Some(inputs), - widget_kind, - widget_options, - hidden: Some(hidden), - doc_link, }, PropSpecKind::Number => PropSpec::Number { - name, - default_value: match &self.default_value { - Some(value) => { - if value.is_i64() { - value.as_i64() - } else { - return Err(SpecError::ValidationError( - "Number props must get an i64 as a default value".to_string(), - )); - } - } - None => None, + name: name.to_owned(), + unique_id: self.unique_id.to_owned(), + data: if self.has_data { + Some(PropSpecData { + name, + default_value: self.default_value.to_owned(), + validations: Some(validations), + func_unique_id, + inputs: Some(inputs), + widget_kind, + widget_options, + hidden: Some(hidden), + doc_link, + }) + } else { + None }, - validations: Some(validations), - func_unique_id, - inputs: Some(inputs), - widget_kind, - widget_options, - hidden: Some(hidden), - doc_link, }, PropSpecKind::Boolean => PropSpec::Boolean { - name, - default_value: match &self.default_value { - Some(value) => { - if value.is_boolean() { - value.as_bool() - } else { - return Err(SpecError::ValidationError( - "Boolean props must get a bool as a default value".to_string(), - )); - } - } - None => None, + name: name.to_owned(), + unique_id: self.unique_id.to_owned(), + data: if self.has_data { + Some(PropSpecData { + name, + default_value: self.default_value.to_owned(), + validations: Some(validations), + func_unique_id, + inputs: Some(inputs), + widget_kind, + widget_options, + hidden: Some(hidden), + doc_link, + }) + } else { + None }, - validations: Some(validations), - func_unique_id, - inputs: Some(inputs), - widget_kind, - widget_options, - hidden: Some(hidden), - doc_link, }, PropSpecKind::Map => PropSpec::Map { - name, - // TODO: Validate these types - default_value: self.default_value.to_owned(), + name: name.to_owned(), + unique_id: self.unique_id.to_owned(), + data: if self.has_data { + Some(PropSpecData { + name, + default_value: self.default_value.to_owned(), + validations: Some(validations), + func_unique_id, + inputs: Some(inputs), + widget_kind, + widget_options, + hidden: Some(hidden), + doc_link, + }) + } else { + None + }, type_prop: match self.type_prop { Some(ref value) => Box::new(value.clone()), None => { return Err(UninitializedFieldError::from("type_prop").into()); } }, - validations: Some(validations), - func_unique_id, - inputs: Some(inputs), - widget_kind, - widget_options, - hidden: Some(hidden), - doc_link, map_key_funcs: Some(self.map_key_funcs.to_owned()), }, PropSpecKind::Array => PropSpec::Array { - name, - default_value: self.default_value.to_owned(), + name: name.to_owned(), + unique_id: self.unique_id.to_owned(), + data: if self.has_data { + Some(PropSpecData { + name, + default_value: self.default_value.to_owned(), + validations: Some(validations), + func_unique_id, + inputs: Some(inputs), + widget_kind, + widget_options, + hidden: Some(hidden), + doc_link, + }) + } else { + None + }, type_prop: match self.type_prop { Some(ref value) => Box::new(value.clone()), None => { return Err(UninitializedFieldError::from("type_prop").into()); } }, - validations: Some(validations), - func_unique_id, - inputs: Some(inputs), - widget_kind, - widget_options, - hidden: Some(hidden), - doc_link, }, PropSpecKind::Object => PropSpec::Object { - name, - default_value: self.default_value.to_owned(), + name: name.to_owned(), + unique_id: self.unique_id.to_owned(), + data: if self.has_data { + Some(PropSpecData { + name, + default_value: self.default_value.to_owned(), + validations: Some(validations), + func_unique_id, + inputs: Some(inputs), + widget_kind, + widget_options, + hidden: Some(hidden), + doc_link, + }) + } else { + None + }, entries: self.entries.clone(), - validations: Some(validations), - func_unique_id, - inputs: Some(inputs), - widget_kind, - widget_options, - hidden: Some(hidden), - doc_link, }, }, None => { diff --git a/lib/si-pkg/src/spec/schema.rs b/lib/si-pkg/src/spec/schema.rs index a1207b335f..73a91c6d8c 100644 --- a/lib/si-pkg/src/spec/schema.rs +++ b/lib/si-pkg/src/spec/schema.rs @@ -6,19 +6,43 @@ use super::{SchemaVariantSpec, SpecError}; #[derive(Builder, Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[builder(build_fn(error = "SpecError"))] -pub struct SchemaSpec { +pub struct SchemaSpecData { #[builder(setter(into))] pub name: String, #[builder(setter(into))] pub category: String, #[builder(setter(into, strip_option), default)] pub category_name: Option, - #[builder(setter(each(name = "variant", into)), default)] - pub variants: Vec, #[builder(setter(into), default)] pub ui_hidden: bool, } +impl SchemaSpecData { + #[must_use] + pub fn builder() -> SchemaSpecDataBuilder { + SchemaSpecDataBuilder::default() + } +} + +#[derive(Builder, Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[builder(build_fn(error = "SpecError"))] +pub struct SchemaSpec { + #[builder(setter(into))] + pub name: String, + #[builder(setter(into, strip_option), default)] + pub data: Option, + #[builder(setter(into, strip_option), default)] + #[serde(default)] + pub unique_id: Option, + #[builder(setter(into), default)] + #[serde(default)] + pub deleted: bool, + + #[builder(setter(each(name = "variant", into)), default)] + pub variants: Vec, +} + impl SchemaSpec { #[must_use] pub fn builder() -> SchemaSpecBuilder { diff --git a/lib/si-pkg/src/spec/si_prop_func.rs b/lib/si-pkg/src/spec/si_prop_func.rs index 8c03425aaa..48122e0f4e 100644 --- a/lib/si-pkg/src/spec/si_prop_func.rs +++ b/lib/si-pkg/src/spec/si_prop_func.rs @@ -2,7 +2,7 @@ use derive_builder::Builder; use serde::{Deserialize, Serialize}; use strum::{AsRefStr, Display, EnumIter, EnumString}; -use super::{AttrFuncInputSpec, FuncUniqueId, SpecError}; +use super::{AttrFuncInputSpec, SpecError}; /// SiPropFuncs track custom functions for for props created for all schema variants and not part /// of the domain tree (which varies for each variant). Currently these are the props under the @@ -46,7 +46,14 @@ pub struct SiPropFuncSpec { #[builder(setter(into))] pub kind: SiPropFuncSpecKind, #[builder(setter(into))] - pub func_unique_id: FuncUniqueId, + pub func_unique_id: String, + #[builder(setter(into), default)] + #[serde(default)] + pub unique_id: Option, + #[builder(setter(into), default)] + #[serde(default)] + pub deleted: bool, + #[builder(setter(each(name = "input"), into), default)] pub inputs: Vec, } diff --git a/lib/si-pkg/src/spec/socket.rs b/lib/si-pkg/src/spec/socket.rs index 434c803abc..25f357e4c4 100644 --- a/lib/si-pkg/src/spec/socket.rs +++ b/lib/si-pkg/src/spec/socket.rs @@ -2,7 +2,7 @@ use derive_builder::Builder; use serde::{Deserialize, Serialize}; use strum::{AsRefStr, Display, EnumIter, EnumString}; -use super::{AttrFuncInputSpec, FuncUniqueId, SpecError}; +use super::{AttrFuncInputSpec, SpecError}; #[remain::sorted] #[derive( @@ -49,9 +49,9 @@ pub enum SocketSpecArity { #[derive(Builder, Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[builder(build_fn(error = "SpecError"))] -pub struct SocketSpec { - #[builder(setter(into), default)] - pub func_unique_id: Option, +pub struct SocketSpecData { + #[builder(setter(into, strip_option), default)] + pub func_unique_id: Option, #[builder(setter(into))] pub kind: SocketSpecKind, @@ -62,11 +62,33 @@ pub struct SocketSpec { #[builder(setter(into), default)] pub arity: SocketSpecArity, + #[builder(setter(into), default)] + pub ui_hidden: bool, +} + +impl SocketSpecData { + pub fn builder() -> SocketSpecDataBuilder { + SocketSpecDataBuilder::default() + } +} + +#[derive(Builder, Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[builder(build_fn(error = "SpecError"))] +pub struct SocketSpec { + #[builder(setter(into))] + pub name: String, + + #[builder(setter(into, strip_option), default)] + #[serde(default)] + pub data: Option, + #[builder(setter(each(name = "input"), into), default)] pub inputs: Vec, #[builder(setter(into), default)] - pub ui_hidden: bool, + #[serde(default)] + pub unique_id: Option, } impl SocketSpec { diff --git a/lib/si-pkg/src/spec/validation.rs b/lib/si-pkg/src/spec/validation.rs index 36f9255f77..12c6489a47 100644 --- a/lib/si-pkg/src/spec/validation.rs +++ b/lib/si-pkg/src/spec/validation.rs @@ -2,8 +2,6 @@ use derive_builder::UninitializedFieldError; use serde::{Deserialize, Serialize}; use strum::{Display, EnumIter, EnumString}; -use object_tree::Hash; - use super::SpecError; #[remain::sorted] @@ -11,29 +9,77 @@ use super::SpecError; #[serde(tag = "kind", rename_all = "camelCase")] pub enum ValidationSpec { CustomValidation { - func_unique_id: Hash, + #[serde(alias = "funcUniqueId")] + func_unique_id: String, + #[serde(alias = "uniqueId")] + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, }, IntegerIsBetweenTwoIntegers { #[serde(alias = "lowerBound")] lower_bound: i64, #[serde(alias = "upperBound")] upper_bound: i64, + #[serde(alias = "uniqueId")] + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, + }, + IntegerIsNotEmpty { + #[serde(alias = "uniqueId")] + unique_id: Option, + deleted: bool, }, - IntegerIsNotEmpty, StringEquals { expected: String, + #[serde(alias = "uniqueId")] + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, }, StringHasPrefix { expected: String, + #[serde(alias = "uniqueId")] + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, }, StringInStringArray { expected: Vec, #[serde(alias = "displayExpected")] display_expected: bool, + #[serde(alias = "uniqueId")] + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, + }, + StringIsHexColor { + #[serde(alias = "uniqueId")] + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, + }, + StringIsNotEmpty { + #[serde(alias = "uniqueId")] + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, + }, + StringIsValidIpAddr { + #[serde(alias = "uniqueId")] + #[serde(default)] + unique_id: Option, + #[serde(default)] + deleted: bool, }, - StringIsHexColor, - StringIsNotEmpty, - StringIsValidIpAddr, } impl ValidationSpec { @@ -66,7 +112,9 @@ pub struct ValidationSpecBuilder { expected_string: Option, expected_string_array: Option>, display_expected: Option, - func_unique_id: Option, + func_unique_id: Option, + unique_id: Option, + deleted: bool, } impl ValidationSpecBuilder { @@ -100,8 +148,18 @@ impl ValidationSpecBuilder { self } - pub fn func_unique_id(&mut self, func_unique_id: Hash) -> &mut Self { - self.func_unique_id = Some(func_unique_id); + pub fn func_unique_id(&mut self, func_unique_id: impl Into) -> &mut Self { + self.func_unique_id = Some(func_unique_id.into()); + self + } + + pub fn unique_id(&mut self, unique_id: impl Into) -> &mut Self { + self.unique_id = Some(unique_id.into()); + self + } + + pub fn deleted(&mut self, deleted: bool) -> &mut Self { + self.deleted = deleted; self } @@ -116,15 +174,22 @@ impl ValidationSpecBuilder { upper_bound: self .upper_bound .ok_or(UninitializedFieldError::from("lower_bound"))?, + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, } } - ValidationSpecKind::IntegerIsNotEmpty => ValidationSpec::IntegerIsNotEmpty, + ValidationSpecKind::IntegerIsNotEmpty => ValidationSpec::IntegerIsNotEmpty { + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, + }, ValidationSpecKind::StringEquals => ValidationSpec::StringEquals { expected: self .expected_string .as_ref() .ok_or(UninitializedFieldError::from("expected_string"))? .to_string(), + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }, ValidationSpecKind::StringHasPrefix => ValidationSpec::StringHasPrefix { expected: self @@ -132,6 +197,8 @@ impl ValidationSpecBuilder { .as_ref() .ok_or(UninitializedFieldError::from("expected_string"))? .to_string(), + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }, ValidationSpecKind::StringInStringArray => ValidationSpec::StringInStringArray { display_expected: self @@ -139,16 +206,30 @@ impl ValidationSpecBuilder { .ok_or(UninitializedFieldError::from("display_expected"))?, expected: self .expected_string_array - .clone() + .to_owned() .ok_or(UninitializedFieldError::from("expected_string"))?, + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, + }, + ValidationSpecKind::StringIsValidIpAddr => ValidationSpec::StringIsValidIpAddr { + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, + }, + ValidationSpecKind::StringIsHexColor => ValidationSpec::StringIsHexColor { + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, + }, + ValidationSpecKind::StringIsNotEmpty => ValidationSpec::StringIsNotEmpty { + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }, - ValidationSpecKind::StringIsValidIpAddr => ValidationSpec::StringIsValidIpAddr, - ValidationSpecKind::StringIsHexColor => ValidationSpec::StringIsHexColor, - ValidationSpecKind::StringIsNotEmpty => ValidationSpec::StringIsNotEmpty, ValidationSpecKind::CustomValidation => ValidationSpec::CustomValidation { func_unique_id: self .func_unique_id + .to_owned() .ok_or(UninitializedFieldError::from("func_unique_id"))?, + unique_id: self.unique_id.to_owned(), + deleted: self.deleted, }, }, None => { diff --git a/lib/si-pkg/src/spec/variant.rs b/lib/si-pkg/src/spec/variant.rs index 4a3e794eae..94e04f1b20 100644 --- a/lib/si-pkg/src/spec/variant.rs +++ b/lib/si-pkg/src/spec/variant.rs @@ -1,12 +1,11 @@ -use crate::FuncUniqueId; use derive_builder::Builder; use serde::{Deserialize, Serialize}; use strum::{AsRefStr, Display, EnumIter, EnumString}; use url::Url; use super::{ - ActionFuncSpec, LeafFunctionSpec, PropSpec, PropSpecWidgetKind, SiPropFuncSpec, SocketSpec, - SpecError, + ActionFuncSpec, LeafFunctionSpec, PropSpec, PropSpecData, PropSpecWidgetKind, SiPropFuncSpec, + SocketSpec, SpecError, }; #[remain::sorted] @@ -60,7 +59,7 @@ pub enum SchemaVariantSpecPropRoot { #[derive(Builder, Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[builder(build_fn(error = "SpecError"))] -pub struct SchemaVariantSpec { +pub struct SchemaVariantSpecData { #[builder(setter(into))] pub name: String, #[builder(setter(into, strip_option), default)] @@ -70,15 +69,44 @@ pub struct SchemaVariantSpec { #[builder(setter(into), default)] pub component_type: SchemaVariantSpecComponentType, + #[builder(setter(into))] + pub func_unique_id: String, +} - #[builder(private, default = "Self::default_domain()")] - pub domain: PropSpec, +impl SchemaVariantSpecData { + pub fn builder() -> SchemaVariantSpecDataBuilder { + SchemaVariantSpecDataBuilder::default() + } +} - #[builder(private, default = "Self::default_resource_value()")] - pub resource_value: PropSpec, +impl SchemaVariantSpecDataBuilder { + #[allow(unused_mut)] + pub fn try_link(&mut self, value: V) -> Result<&mut Self, V::Error> + where + V: TryInto, + { + let converted: Url = value.try_into()?; + Ok(self.link(converted)) + } +} +#[derive(Builder, Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[builder(build_fn(error = "SpecError"))] +pub struct SchemaVariantSpec { #[builder(setter(into))] - pub func_unique_id: FuncUniqueId, + pub name: String, + + #[builder(setter(into, strip_option), default)] + pub data: Option, + + #[builder(setter(into, strip_option), default)] + #[serde(default)] + pub unique_id: Option, + + #[builder(setter(into), default)] + #[serde(default)] + pub deleted: bool, #[builder(setter(each(name = "action_func"), into), default)] pub action_funcs: Vec, @@ -91,6 +119,12 @@ pub struct SchemaVariantSpec { #[builder(setter(each(name = "si_prop_func"), into), default)] pub si_prop_funcs: Vec, + + #[builder(private, default = "Self::default_domain()")] + pub domain: PropSpec, + + #[builder(private, default = "Self::default_resource_value()")] + pub resource_value: PropSpec, } impl SchemaVariantSpec { @@ -100,45 +134,45 @@ impl SchemaVariantSpec { } impl SchemaVariantSpecBuilder { + // XXX: these need to take in a unique_id fn default_domain() -> PropSpec { PropSpec::Object { - validations: None, - default_value: None, name: "domain".to_string(), + unique_id: None, + data: Some(PropSpecData { + name: "domain".to_string(), + default_value: None, + func_unique_id: None, + inputs: None, + widget_kind: Some(PropSpecWidgetKind::Header), + widget_options: None, + hidden: Some(false), + validations: None, + doc_link: None, + }), entries: vec![], - func_unique_id: None, - inputs: None, - widget_kind: Some(PropSpecWidgetKind::Header), - widget_options: None, - hidden: Some(false), - doc_link: None, } } fn default_resource_value() -> PropSpec { PropSpec::Object { - validations: None, - default_value: None, name: "value".to_string(), + unique_id: None, + data: Some(PropSpecData { + name: "value".to_string(), + default_value: None, + func_unique_id: None, + inputs: None, + widget_kind: Some(PropSpecWidgetKind::Header), + widget_options: None, + hidden: Some(false), + validations: None, + doc_link: None, + }), entries: vec![], - func_unique_id: None, - inputs: None, - widget_kind: Some(PropSpecWidgetKind::Header), - widget_options: None, - hidden: Some(true), - doc_link: None, } } - #[allow(unused_mut)] - pub fn try_link(&mut self, value: V) -> Result<&mut Self, V::Error> - where - V: TryInto, - { - let converted: Url = value.try_into()?; - Ok(self.link(converted)) - } - pub fn domain_prop(&mut self, item: impl Into) -> &mut Self { self.prop(SchemaVariantSpecPropRoot::Domain, item) }