lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 7ac8a00e3a1bb9ed57c16de7c183303d13e35c63
parent 6d8c91c87d355c835dd3912b42d9e998729d1cc2
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 02:20:55 -0700

trade: build listing mutation drafts

- build frozen drafts for listing publish, update, and save-draft mutations
- map listing mutations to stable v1 contract IDs and kinds
- keep archive mutations explicitly unsupported
- validate with cargo fmt, check, and tests for radroots_trade

Diffstat:
Mcrates/trade/src/listing/mod.rs | 2++
Mcrates/trade/src/listing/mutation.rs | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 106 insertions(+), 2 deletions(-)

diff --git a/crates/trade/src/listing/mod.rs b/crates/trade/src/listing/mod.rs @@ -12,6 +12,8 @@ pub use self::draft::{ RadrootsCanonicalListingDraft, RadrootsListingDraftDocumentV1, RadrootsListingDraftError, canonicalize_listing_draft, }; +#[cfg(feature = "serde_json")] +pub use self::mutation::build_listing_mutation_draft; pub use self::mutation::{ RadrootsListingLifecycleState, RadrootsListingMutation, RadrootsListingMutationError, }; diff --git a/crates/trade/src/listing/mutation.rs b/crates/trade/src/listing/mutation.rs @@ -1,6 +1,19 @@ #![forbid(unsafe_code)] +#[cfg(all(feature = "serde_json", not(feature = "std")))] +use alloc::string::{String, ToString}; + +#[cfg(all(feature = "serde_json", feature = "std"))] +use std::string::{String, ToString}; + use radroots_events::ids::RadrootsListingAddress; +#[cfg(feature = "serde_json")] +use radroots_events::{ + draft::{RadrootsDraftError, RadrootsFrozenEventDraft}, + kinds::{KIND_LISTING, KIND_LISTING_DRAFT}, +}; +#[cfg(feature = "serde_json")] +use radroots_events_codec::{listing::encode::to_wire_parts_with_kind, wire::to_frozen_draft}; use thiserror::Error; use crate::listing::draft::RadrootsCanonicalListingDraft; @@ -36,8 +49,17 @@ pub enum RadrootsListingLifecycleState { pub enum RadrootsListingMutationError { #[error("listing mutation is not supported")] UnsupportedMutation, + #[cfg(feature = "serde_json")] + #[error("failed to encode listing mutation: {0}")] + EncodeListing(String), + #[cfg(feature = "serde_json")] + #[error("failed to build listing mutation draft: {0}")] + FrozenDraft(RadrootsDraftError), } +const LISTING_PUBLISHED_CONTRACT_ID: &str = "radroots.listing.published.v1"; +const LISTING_DRAFT_CONTRACT_ID: &str = "radroots.listing.draft.v1"; + impl RadrootsListingMutation { pub fn publish(draft: RadrootsCanonicalListingDraft) -> Self { Self::Publish { draft } @@ -79,6 +101,28 @@ impl RadrootsListingMutation { } } +#[cfg(feature = "serde_json")] +pub fn build_listing_mutation_draft( + mutation: &RadrootsListingMutation, + created_at: u32, +) -> Result<RadrootsFrozenEventDraft, RadrootsListingMutationError> { + let (draft, kind, contract_id) = match mutation { + RadrootsListingMutation::Publish { draft } | RadrootsListingMutation::Update { draft } => { + (draft, KIND_LISTING, LISTING_PUBLISHED_CONTRACT_ID) + } + RadrootsListingMutation::SaveDraft { draft } => { + (draft, KIND_LISTING_DRAFT, LISTING_DRAFT_CONTRACT_ID) + } + RadrootsListingMutation::Archive { .. } => { + return Err(RadrootsListingMutationError::UnsupportedMutation); + } + }; + let parts = to_wire_parts_with_kind(&draft.document.listing, kind) + .map_err(|error| RadrootsListingMutationError::EncodeListing(error.to_string()))?; + to_frozen_draft(parts, contract_id, draft.seller_pubkey.as_str(), created_at) + .map_err(RadrootsListingMutationError::FrozenDraft) +} + #[cfg(test)] mod tests { use radroots_core::{ @@ -88,14 +132,15 @@ mod tests { use radroots_events::{ farm::RadrootsFarmRef, ids::{RadrootsDTag, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsPublicKey}, - kinds::KIND_LISTING_DRAFT, + kinds::{KIND_LISTING, KIND_LISTING_DRAFT}, listing::{RadrootsListing, RadrootsListingBin, RadrootsListingProduct}, }; use crate::listing::draft::{RadrootsCanonicalListingDraft, RadrootsListingDraftDocumentV1}; use super::{ - RadrootsListingLifecycleState, RadrootsListingMutation, RadrootsListingMutationError, + LISTING_DRAFT_CONTRACT_ID, LISTING_PUBLISHED_CONTRACT_ID, RadrootsListingLifecycleState, + RadrootsListingMutation, RadrootsListingMutationError, build_listing_mutation_draft, }; const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; @@ -232,4 +277,61 @@ mod tests { RadrootsListingMutationError::UnsupportedMutation ); } + + #[test] + fn build_listing_mutation_draft_maps_publish_and_update_to_published_listing() { + let publish = RadrootsListingMutation::publish(canonical_draft()); + let update = RadrootsListingMutation::update(canonical_draft()); + + let publish_draft = build_listing_mutation_draft(&publish, 1_700_000_000).expect("draft"); + let update_draft = build_listing_mutation_draft(&update, 1_700_000_000).expect("draft"); + + assert_eq!(publish_draft.kind, KIND_LISTING); + assert_eq!(publish_draft.contract_id, LISTING_PUBLISHED_CONTRACT_ID); + assert_eq!(publish_draft.expected_pubkey, SELLER); + assert_eq!(publish_draft.created_at, 1_700_000_000); + assert_eq!(update_draft.kind, KIND_LISTING); + assert_eq!(update_draft.contract_id, LISTING_PUBLISHED_CONTRACT_ID); + assert_eq!(update_draft.expected_pubkey, SELLER); + } + + #[test] + fn build_listing_mutation_draft_maps_save_draft_to_listing_draft() { + let save_draft = RadrootsListingMutation::save_draft(canonical_draft()); + + let draft = build_listing_mutation_draft(&save_draft, 1_700_000_000).expect("draft"); + + assert_eq!(draft.kind, KIND_LISTING_DRAFT); + assert_eq!(draft.contract_id, LISTING_DRAFT_CONTRACT_ID); + assert_eq!(draft.expected_pubkey, SELLER); + assert_eq!(draft.created_at, 1_700_000_000); + } + + #[test] + fn build_listing_mutation_draft_rejects_archive() { + let archive = RadrootsListingMutation::archive( + RadrootsListingAddress::parse(format!( + "{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" + )) + .expect("listing address"), + ); + + assert_eq!( + build_listing_mutation_draft(&archive, 1_700_000_000).unwrap_err(), + RadrootsListingMutationError::UnsupportedMutation + ); + } + + #[test] + fn build_listing_mutation_draft_event_id_is_stable_for_fixed_input() { + let publish = RadrootsListingMutation::publish(canonical_draft()); + + let first = build_listing_mutation_draft(&publish, 1_700_000_000).expect("draft"); + let second = build_listing_mutation_draft(&publish, 1_700_000_000).expect("draft"); + + assert_eq!(first.expected_event_id, second.expected_event_id); + assert_eq!(first.expected_event_id.len(), 64); + assert_eq!(first.tags, second.tags); + assert_eq!(first.content, second.content); + } }