From 8cb9e7a91338770faaa6e53fd575c56a312c8b66 Mon Sep 17 00:00:00 2001 From: Zachary Hamm Date: Thu, 31 Aug 2023 10:31:25 -0500 Subject: [PATCH] feat(dal,sdf): change_set aware export of workspaces Adds the ability for the nodes in our package graph to be empty of data but still have child nodes. Adds the ability to track deleted objects when exporting workspaces. Import is currently unchanged and only supports "modules", which are a closure of the state of the active change_set+head for specific schema variants. --- lib/dal/examples/dal-pkg-export/main.rs | 25 +- lib/dal/src/attribute/prototype.rs | 15 +- lib/dal/src/builtins/schema.rs | 5 + .../builtins/schema/test_exclusive_fallout.rs | 98 +- .../schema/test_exclusive_starfield.rs | 136 +- lib/dal/src/func/intrinsics.rs | 71 +- lib/dal/src/pkg.rs | 22 +- lib/dal/src/pkg/export.rs | 1776 ++++++++++------- lib/dal/src/pkg/import.rs | 289 +-- lib/dal/src/prop_tree.rs | 4 + ...context_and_func_backend_response_type.sql | 7 +- .../prop/tree_for_all_schema_variants.sql | 7 +- lib/dal/src/schema/variant.rs | 2 +- lib/dal/src/schema/variant/definition.rs | 395 +--- lib/dal/src/visibility.rs | 13 +- .../tests/integration_test/internal/pkg.rs | 367 +++- .../src/server/service/change_set.rs | 43 - lib/sdf-server/src/server/service/pkg.rs | 9 +- .../src/server/service/pkg/export_pkg.rs | 25 +- .../src/server/service/variant_definition.rs | 2 +- .../variant_definition/exec_variant_def.rs | 34 +- lib/si-pkg/src/lib.rs | 14 +- lib/si-pkg/src/node/action_func.rs | 22 +- lib/si-pkg/src/node/attr_func_input.rs | 111 +- lib/si-pkg/src/node/func.rs | 175 +- lib/si-pkg/src/node/func_argument.rs | 14 +- lib/si-pkg/src/node/leaf_function.rs | 22 +- lib/si-pkg/src/node/map_key_func.rs | 15 +- lib/si-pkg/src/node/mod.rs | 51 +- lib/si-pkg/src/node/package.rs | 12 +- lib/si-pkg/src/node/prop.rs | 556 ++---- lib/si-pkg/src/node/schema.rs | 81 +- lib/si-pkg/src/node/schema_variant.rs | 118 +- lib/si-pkg/src/node/si_prop_func.rs | 21 +- lib/si-pkg/src/node/socket.rs | 101 +- lib/si-pkg/src/node/validation.rs | 73 +- lib/si-pkg/src/pkg.rs | 32 +- lib/si-pkg/src/pkg/action_func.rs | 27 +- lib/si-pkg/src/pkg/attr_func_input.rs | 116 +- lib/si-pkg/src/pkg/change_set.rs | 22 +- lib/si-pkg/src/pkg/func.rs | 170 +- lib/si-pkg/src/pkg/leaf_function.rs | 24 +- lib/si-pkg/src/pkg/map_key_func.rs | 10 +- lib/si-pkg/src/pkg/prop.rs | 240 +-- lib/si-pkg/src/pkg/schema.rs | 79 +- lib/si-pkg/src/pkg/si_prop_func.rs | 25 +- lib/si-pkg/src/pkg/socket.rs | 89 +- lib/si-pkg/src/pkg/validation.rs | 4 +- lib/si-pkg/src/pkg/variant.rs | 208 +- lib/si-pkg/src/spec.rs | 9 +- lib/si-pkg/src/spec/action_func.rs | 12 +- lib/si-pkg/src/spec/attr_func_input.rs | 66 +- lib/si-pkg/src/spec/change_set.rs | 4 +- lib/si-pkg/src/spec/func.rs | 86 +- lib/si-pkg/src/spec/leaf_function.rs | 12 +- lib/si-pkg/src/spec/map_key_func.rs | 4 +- lib/si-pkg/src/spec/prop.rs | 306 +-- lib/si-pkg/src/spec/schema.rs | 30 +- lib/si-pkg/src/spec/si_prop_func.rs | 11 +- lib/si-pkg/src/spec/socket.rs | 32 +- lib/si-pkg/src/spec/validation.rs | 111 +- lib/si-pkg/src/spec/variant.rs | 102 +- 62 files changed, 3874 insertions(+), 2688 deletions(-) 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) }