// file: kb-app-demo-desktop/src/demo_execution_metadata_metaplex_token_metadata.rs // version: 1 //! Desktop adapters for Metaplex Token Metadata execution demo scenarios. /// UI-safe Metaplex scenario row. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload.ts" )] pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload { /// Stable scenario identifier. pub id: std::string::String, /// Operator-visible scenario label. pub label: std::string::String, /// Stable asset-family code. pub asset_family: std::string::String, /// Stable fixture graph code. pub fixture_kind: std::string::String, /// Required initial lifecycle state. pub initial_state: std::string::String, /// Expected terminal lifecycle state. pub resulting_state: std::string::String, /// Stable execution-mode code. pub mode: std::string::String, /// Ordered operation codes. pub operation_codes: std::vec::Vec, /// Whether a collection account is required. pub requires_collection: bool, /// Whether programmable authorization rules are required. pub requires_programmable_rules: bool, /// Whether postconditions are mandatory. pub requires_postcondition: bool, /// Current validation status from the canonical matrix. pub validation_status: std::string::String, /// Required evidence kinds. pub required_evidence: std::vec::Vec, /// Canonical account projections expected when materialization is enabled. pub materialization_targets: std::vec::Vec, } /// UI-safe result of one prepared Metaplex Create fixture. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload.ts" )] pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload { /// Fixture mint address. pub mint: std::string::String, /// Canonical metadata PDA. pub metadata: std::string::String, /// Canonical master-edition PDA. pub master_edition: std::string::String, /// Wallet authority. pub authority: std::string::String, /// Generated typed Create operation JSON. pub operation_json: std::string::String, /// Whether the mint account was newly created. pub mint_created: bool, } /// UI-safe prepared step for one coherent Metaplex Devnet scenario. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload.ts" )] pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload { /// Stable scenario identifier. pub scenario_id: std::string::String, /// Zero-based step index. pub step_index: usize, /// Stable operation name accepted by the executor JSON contract. pub operation: std::string::String, /// Operator-visible step label. pub label: std::string::String, /// Prepared typed operation JSON. pub operation_json: std::string::String, /// Automatically generated bounded preflight reads. pub preflight_reads_json: std::string::String, /// Automatically generated bounded postcondition reads. pub postcondition_reads_json: std::string::String, /// Whether this step can be simulated immediately. pub simulation_ready: bool, /// Whether this step can be submitted immediately. pub submission_ready: bool, /// Fixture state used to prepare the step. pub fixture_state: std::string::String, } /// Request for one real Devnet Metaplex execution from the desktop panel. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataRequest.ts" )] pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataRequest { /// Devnet profile selected by the operator. pub profile_name: std::string::String, /// Stable execution identifier shown in diagnostics. pub intent_id: std::string::String, /// Serialized typed current Metaplex operation. pub operation_json: std::string::String, /// Serialized bounded preflight read requests. pub preflight_reads_json: std::string::String, /// Serialized bounded postcondition read requests. pub postcondition_reads_json: std::string::String, /// Whether signing and submission are explicitly requested. pub submit: bool, /// Explicit operator confirmation for submission. pub operator_confirmed: bool, /// Materializes confirmed Metaplex account snapshots after submission. pub materialize_after_confirmation: bool, } /// UI-safe result of one real Devnet Metaplex execution. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload.ts" )] pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload { /// Selected profile name. pub profile_name: std::string::String, /// Exact cluster code. pub cluster: std::string::String, /// Genesis hash observed from RPC. pub genesis_hash: std::string::String, /// Wallet public key used by the runner. pub wallet_public_key: std::string::String, /// Wallet balance before execution. pub balance_lamports: std::string::String, /// Whether the real RPC simulation succeeded. pub simulation_success: bool, /// Optional submitted signature. pub transaction_signature: std::option::Option, /// Optional terminal confirmation status. pub confirmation_status: std::option::Option, /// Exact prepared plan as formatted JSON. pub plan_json: std::string::String, /// Stateful preflight as formatted JSON. pub preflight_json: std::string::String, /// Real RPC simulation as formatted JSON. pub simulation_json: std::string::String, /// Before/after state and readiness diagnostics as formatted JSON. pub evidence_json: std::string::String, /// Whether post-confirmation account materialization was requested. pub materialization_requested: bool, /// Number of canonical post-confirmation snapshots materialized. pub materialized_snapshot_count: usize, } /// Executes one real Devnet Metaplex simulation or explicitly confirmed submission. pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute( app_handle: tauri::AppHandle, state: tauri::State<'_, crate::AppState>, request: crate::DemoExecutionMetadataMetaplexTokenMetadataRequest, ) -> std::result::Result< crate::DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload, std::string::String, > { let acquire_result = state.demo_execution_solana_core_running().compare_exchange( false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire, ); if acquire_result.is_err() { return std::result::Result::Err("a Devnet execution is already running".to_string()); } let _run_guard = crate::DemoExecutionRunGuard { running: state.demo_execution_solana_core_running(), }; state .demo_execution_solana_core_cancel_requested() .store(false, std::sync::atomic::Ordering::Release); let profile = match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let mut execution_request = match kb_pipeline_demo_scenarios::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json( request.intent_id, request.operation_json.as_str(), ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; execution_request.preflight_reads = match parse_stateful_reads(request.preflight_reads_json.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; execution_request.postcondition_reads = match parse_stateful_reads(request.postcondition_reads_json.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; execution_request.submit = request.submit; execution_request.operator_confirmed = request.operator_confirmed; execution_request.materialize_after_confirmation = request.materialize_after_confirmation; if let std::result::Result::Err(error) = execution_request.validate() { return std::result::Result::Err(error.to_string()); } let observer = crate::DemoExecutionMetadataObserver { app_handle, cancel_requested: state.demo_execution_solana_core_cancel_requested(), }; let workspace_root = crate::workspace_root_dir(); let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_token_metadata( &http_pool, &profile, workspace_root.as_path(), &execution_request, &observer, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let transaction_signature = summary.send_result.as_ref().map(|value| return value.signature.0.clone()); let confirmation_status = summary.confirmation.as_ref().map(|value| return format!("{:?}", value.status)); let evidence_json = crate::pretty_json(&serde_json::json!({ "before": &summary.before, "readiness": &summary.readiness, "sendResult": &summary.send_result, "confirmation": &summary.confirmation, "after": &summary.after, "materializationRequested": summary.materialization_requested, "materializedSnapshots": &summary.materialized_snapshots })); return std::result::Result::Ok( crate::DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload { profile_name: summary.profile_name, cluster: format!("{:?}", summary.cluster).to_lowercase(), genesis_hash: summary.genesis_hash, wallet_public_key: summary.wallet.public_key, balance_lamports: summary.balance_lamports.to_string(), simulation_success: summary.simulation.success, transaction_signature, confirmation_status, plan_json: crate::pretty_json(&summary.plan), preflight_json: crate::pretty_json(&summary.stateful_preflight), simulation_json: crate::pretty_json(&summary.simulation), evidence_json, materialization_requested: summary.materialization_requested, materialized_snapshot_count: summary.materialized_snapshots.len(), }, ); } fn parse_stateful_reads( value: &str, ) -> std::result::Result< std::vec::Vec, std::string::String, > { if value.trim().is_empty() { return std::result::Result::Ok(std::vec::Vec::new()); } return match serde_json::from_str(value) { std::result::Result::Ok(parsed) => std::result::Result::Ok(parsed), std::result::Result::Err(error) => { std::result::Result::Err(format!("invalid Metaplex stateful reads JSON: {error}")) }, }; } /// Creates or reuses the Devnet mint fixture used by the named Create scenario. pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture( state: tauri::State<'_, crate::AppState>, profile_name: std::string::String, ) -> std::result::Result< crate::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload, std::string::String, > { let profile = match crate::select_devnet_profile(state.app_config(), profile_name.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let wallet_dir = if configured.is_absolute() { configured } else { crate::workspace_root_dir().join(configured) }; let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let workspace_root = crate::workspace_root_dir(); let summary = match kb_pipeline_demo_scenarios::prepare_metaplex_create_fixture( &http_pool, &profile, workspace_root.as_path(), &kb_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir), ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; return std::result::Result::Ok( crate::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload { mint: summary.mint, metadata: summary.metadata, master_edition: summary.master_edition, authority: summary.authority, operation_json: summary.operation_json, mint_created: summary.mint_created, }, ); } fn adapt_create_operation_json( operation_json: &str, asset_family: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily, ) -> std::result::Result { let mut value: serde_json::Value = match serde_json::from_str(operation_json) { std::result::Result::Ok(parsed) => parsed, std::result::Result::Err(error) => { return std::result::Result::Err(format!( "invalid generated Metaplex Create JSON: {error}" )); }, }; if matches!( asset_family, kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft | kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible ) { value["master_edition"] = serde_json::Value::Null; } let create = match value .get_mut("create_args") .and_then(|args| return args.get_mut("V1")) .and_then(serde_json::Value::as_object_mut) { std::option::Option::Some(found) => found, std::option::Option::None => { return std::result::Result::Err( "generated Metaplex Create JSON has no create_args.V1 object".to_string(), ); }, }; match asset_family { kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => { create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet NFT")); create.insert("symbol".to_string(), serde_json::json!("KHNFT")); create.insert("token_standard".to_string(), serde_json::json!("NonFungible")); create.insert("collection_details".to_string(), serde_json::Value::Null); create.insert("decimals".to_string(), serde_json::Value::Null); create.insert("print_supply".to_string(), serde_json::json!("Zero")); }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => { create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet SFT")); create.insert("symbol".to_string(), serde_json::json!("KHSFT")); create.insert("token_standard".to_string(), serde_json::json!("FungibleAsset")); create.insert("collection_details".to_string(), serde_json::Value::Null); create.insert("decimals".to_string(), serde_json::json!(0)); create.insert("print_supply".to_string(), serde_json::Value::Null); }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => { create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Fungible")); create.insert("symbol".to_string(), serde_json::json!("KHFUN")); create.insert("token_standard".to_string(), serde_json::json!("Fungible")); create.insert("collection_details".to_string(), serde_json::Value::Null); create.insert("decimals".to_string(), serde_json::json!(0)); create.insert("print_supply".to_string(), serde_json::Value::Null); }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => { create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Collection")); create.insert("symbol".to_string(), serde_json::json!("KHCOL")); create.insert("token_standard".to_string(), serde_json::json!("NonFungible")); create.insert("collection_details".to_string(), serde_json::json!({"V1": {"size": 0}})); create.insert("decimals".to_string(), serde_json::Value::Null); create.insert("print_supply".to_string(), serde_json::json!("Zero")); }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => { create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet pNFT")); create.insert("symbol".to_string(), serde_json::json!("KHPNFT")); create .insert("token_standard".to_string(), serde_json::json!("ProgrammableNonFungible")); create.insert("collection_details".to_string(), serde_json::Value::Null); create.insert("rule_set".to_string(), serde_json::Value::Null); create.insert("decimals".to_string(), serde_json::Value::Null); create.insert("print_supply".to_string(), serde_json::json!("Zero")); }, } return std::result::Result::Ok(crate::pretty_json(&value)); } /// Prepares one coherent scenario step and all of its stateful reads. pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_prepare_step( state: tauri::State<'_, crate::AppState>, profile_name: std::string::String, scenario_id: std::string::String, step_index: usize, previous_operation_json: std::option::Option, ) -> std::result::Result< crate::DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload, std::string::String, > { let scenario = kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios() .into_iter() .find(|candidate| return candidate.id == scenario_id); let scenario = match scenario { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(format!( "unknown Metaplex Devnet scenario `{scenario_id}`" )); }, }; let operation_code = match scenario.operation_codes.get(step_index) { std::option::Option::Some(value) => value.clone(), std::option::Option::None => { return std::result::Result::Err(format!( "scenario `{}` has no step at index {step_index}", scenario.id )); }, }; let operation_suffix = match operation_code.rsplit('.').next() { std::option::Option::Some(value) => value, std::option::Option::None => operation_code.as_str(), }; let operation = if operation_suffix == "update" { "update_as_update_authority_v2".to_string() } else { operation_suffix.to_string() }; if operation != "create" && operation != "update_as_update_authority_v2" { return std::result::Result::Err(format!( "scenario step `{operation}` is declared but its Devnet fixture builder is not implemented yet" )); } let (fixture_mint, fixture_metadata, fixture_master_edition, operation_json, fixture_state) = if operation == "create" { let fixture = match crate::demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture( state, profile_name, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let operation_json = match adapt_create_operation_json( fixture.operation_json.as_str(), scenario.asset_family, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; ( fixture.mint, fixture.metadata, fixture.master_edition, operation_json, if fixture.mint_created { "mint_prepared".to_string() } else { "mint_reused".to_string() }, ) } else { let previous = match previous_operation_json { std::option::Option::Some(value) if !value.trim().is_empty() => value, _ => { return std::result::Result::Err( "prepare and submit the previous NFT lifecycle step before continuing" .to_string(), ); }, }; let previous_value: serde_json::Value = match serde_json::from_str(previous.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(format!( "invalid previous scenario operation JSON: {error}" )); }, }; let string_field = |name: &str| -> std::result::Result< std::string::String, std::string::String, > { return match previous_value.get(name).and_then(serde_json::Value::as_str) { std::option::Option::Some(value) => std::result::Result::Ok(value.to_string()), std::option::Option::None => std::result::Result::Err(format!( "previous scenario step does not contain `{name}`" )), }; }; let mint = match string_field("mint") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let metadata = match string_field("metadata") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let authority = match string_field("authority") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let master_edition = match previous_value.get("master_edition").and_then(serde_json::Value::as_str) { std::option::Option::Some(value) => value.to_string(), std::option::Option::None => std::string::String::new(), }; let immutable_transition = scenario.asset_family == kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft && step_index == 2; let value = serde_json::json!({ "operation": "update_as_update_authority_v2", "authority": authority.clone(), "mint": mint.clone(), "metadata": metadata.clone(), "edition": null, "token": null, "authorization_rules_program": null, "authorization_rules": null, "update_args": { "AsUpdateAuthorityV2": { "new_update_authority": null, "data": null, "primary_sale_happened": if immutable_transition { serde_json::Value::Null } else { serde_json::json!(true) }, "is_mutable": if immutable_transition { serde_json::json!(false) } else { serde_json::Value::Null }, "collection": "None", "collection_details": "None", "uses": "None", "rule_set": "None", "token_standard": null, "authorization_data": null } } }); ( mint, metadata, master_edition, crate::pretty_json(&value), if immutable_transition { "metadata_immutable".to_string() } else { "metadata_created".to_string() }, ) }; let metadata_read = serde_json::json!({ "query_role": "http_queries", "account": fixture_metadata.clone(), "kind": { "kind": "metadata" }, "min_context_slot": null, "max_data_bytes": 65536 }); let edition_read = serde_json::json!({ "query_role": "http_queries", "account": fixture_master_edition.clone(), "kind": { "kind": "edition", "mint": fixture_mint.clone() }, "min_context_slot": null, "max_data_bytes": 65536 }); let preflight_reads = if operation == "create" { serde_json::json!([]) } else { serde_json::json!([metadata_read.clone()]) }; let create_reads_edition = matches!( scenario.asset_family, kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft | kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection | kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft ); let postcondition_reads = if operation == "create" && create_reads_edition { serde_json::json!([metadata_read, edition_read]) } else { serde_json::json!([metadata_read]) }; return std::result::Result::Ok( crate::DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload { scenario_id: scenario.id, step_index, operation: operation.clone(), label: if scenario.asset_family == kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft && step_index == 2 { "Étape 3 — rendre les metadata immutables".to_string() } else { format!("Étape {} — {}", step_index + 1, operation) }, operation_json, preflight_reads_json: crate::pretty_json(&preflight_reads), postcondition_reads_json: crate::pretty_json(&postcondition_reads), simulation_ready: true, submission_ready: true, fixture_state, }, ); } /// Returns current operation names accepted by automatic Devnet campaigns. pub(crate) fn demo_execution_metadata_metaplex_token_metadata_current_operations() -> std::vec::Vec { return kb_pipeline_demo_scenarios::metaplex_token_metadata_current_operation_names() .iter() .map(|value| return (*value).to_string()) .collect(); } /// Returns one generated typed JSON template for a named current operation. pub(crate) fn demo_execution_metadata_metaplex_token_metadata_operation_template( operation: std::string::String, ) -> std::result::Result { return match kb_pipeline_demo_scenarios::metaplex_token_metadata_current_operation_json_template( operation.as_str(), ) { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(error) => std::result::Result::Err(error.to_string()), }; } /// Returns the canonical Metaplex scenario inventory for the desktop panel. pub(crate) fn demo_execution_metadata_metaplex_token_metadata_scenarios() -> std::result::Result< std::vec::Vec, std::string::String, > { let matrix = match kb_pipeline_demo_scenarios::load_metaplex_token_metadata_validation_matrix() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let statuses = matrix .scenarios .into_iter() .map(|scenario| return (scenario.id, (scenario.status, scenario.required_evidence))) .collect::, ), >>(); let mut payloads = std::vec::Vec::new(); for scenario in kb_pipeline_demo_scenarios::metaplex_token_metadata_synthetic_scenarios() .into_iter() .chain(kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios()) { let status = statuses.get(scenario.id.as_str()); let validation_status = match status { std::option::Option::Some((value, _)) => validation_status_code(*value), std::option::Option::None => "not_run".to_string(), }; let required_evidence = match status { std::option::Option::Some((_, evidence)) => evidence.clone(), std::option::Option::None => std::vec::Vec::new(), }; payloads.push(crate::DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload { id: scenario.id, label: scenario.label, asset_family: asset_family_code(scenario.asset_family), fixture_kind: fixture_kind_code(scenario.fixture_kind), initial_state: fixture_state_code(scenario.initial_state), resulting_state: fixture_state_code(scenario.resulting_state), mode: scenario_mode_code(scenario.mode), operation_codes: scenario.operation_codes, requires_collection: scenario.requires_collection, requires_programmable_rules: scenario.requires_programmable_rules, requires_postcondition: scenario.requires_postcondition, validation_status, required_evidence, materialization_targets: scenario.materialization_targets, }); } return std::result::Result::Ok(payloads); } fn asset_family_code( value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily, ) -> std::string::String { return match value { kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => "nft".to_string(), kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => "sft".to_string(), kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => { "fungible".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => { "collection".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => { "programmable_nft".to_string() }, }; } fn fixture_kind_code( value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataFixtureKind, ) -> std::string::String { return format!("{value:?}").to_lowercase(); } fn fixture_state_code( value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataFixtureState, ) -> std::string::String { return format!("{value:?}").to_lowercase(); } fn scenario_mode_code( value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode, ) -> std::string::String { return match value { kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::Synthetic => { "synthetic".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSimulation => { "network_simulation".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSubmission => { "network_submission".to_string() }, }; } fn validation_status_code( value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus, ) -> std::string::String { return match value { kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::NotRun => { "not_run".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::SyntheticValidated => { "synthetic_validated".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Simulated => { "simulated".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Submitted => { "submitted".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Confirmed => { "confirmed".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Unavailable => { "unavailable".to_string() }, kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Failed => { "failed".to_string() }, }; } #[cfg(test)] mod tests { #[test] fn desktop_inventory_matches_reusable_scenarios_and_matrix() { let payloads = match crate::demo_execution_metadata_metaplex_token_metadata_scenarios() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { assert_eq!(std::option::Option::Some(error), std::option::Option::None); return; }, }; let expected_count = kb_pipeline_demo_scenarios::metaplex_token_metadata_synthetic_scenarios().len() + kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios().len(); assert_eq!(payloads.len(), expected_count); assert!(payloads.iter().all(|payload| return !payload.operation_codes.is_empty())); assert!(payloads.iter().all(|payload| return payload.requires_postcondition)); assert!( payloads .iter() .all(|payload| return payload.validation_status != "missing_from_matrix") ); assert_eq!( payloads .iter() .filter(|payload| return payload.mode == "network_simulation") .count(), 5 ); } #[test] fn empty_stateful_reads_are_accepted() { let parsed = super::parse_stateful_reads(" "); assert!(parsed.is_ok()); let values = match parsed { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { assert_eq!(std::option::Option::Some(error), std::option::Option::None); return; }, }; assert_eq!(values.len(), 0); } #[test] fn metadata_frontend_contains_every_required_runtime_control() { let source = std::include_str!("../frontend/demo_execution_metadata.html"); for id in [ "metadataProfileSelect", "metadataDevnetScenarioSelect", "metadataStepSelect", "prepareMetadataScenarioStepButton", "metadataExecutionLogOutput", "metadataProfileHelp", "simulateMetadataButton", "submitMetadataButton", ] { assert!(source.contains(format!("id=\"{id}\"").as_str())); } } #[test] fn programmable_and_collection_requirements_remain_explicit() { let payloads = match crate::demo_execution_metadata_metaplex_token_metadata_scenarios() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { assert_eq!(std::option::Option::Some(error), std::option::Option::None); return; }, }; assert!(payloads.iter().any(|payload| return payload.requires_collection)); assert!(payloads.iter().any(|payload| return payload.requires_programmable_rules)); } }