lib

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

commit 7080a146f6742df7da89df40b86b17d090804c97
parent 7ac8a00e3a1bb9ed57c16de7c183303d13e35c63
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 02:23:48 -0700

trade: consolidate listing publish helpers

- remove the superseded public listing publish helper module
- validate listing workflow behavior through mutation draft prep
- keep archive unsupported in the single listing workflow surface
- validate with cargo fmt, check, and tests for radroots_trade

Diffstat:
Mcrates/trade/src/listing/mod.rs | 1-
Mcrates/trade/src/listing/mutation.rs | 46+++++++++++++++++++++++++++++++++++++++++-----
Dcrates/trade/src/listing/publish.rs | 177-------------------------------------------------------------------------------
3 files changed, 41 insertions(+), 183 deletions(-)

diff --git a/crates/trade/src/listing/mod.rs b/crates/trade/src/listing/mod.rs @@ -3,7 +3,6 @@ pub mod draft; pub mod model; pub mod mutation; pub mod price_ext; -pub mod publish; pub mod validation; use radroots_events::{RadrootsNostrEvent, kinds::is_listing_kind, listing::RadrootsListing}; diff --git a/crates/trade/src/listing/mutation.rs b/crates/trade/src/listing/mutation.rs @@ -130,13 +130,19 @@ mod tests { RadrootsCoreQuantityPrice, RadrootsCoreUnit, }; use radroots_events::{ + RadrootsNostrEvent, farm::RadrootsFarmRef, ids::{RadrootsDTag, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsPublicKey}, kinds::{KIND_LISTING, KIND_LISTING_DRAFT}, - listing::{RadrootsListing, RadrootsListingBin, RadrootsListingProduct}, + listing::{ + RadrootsListing, RadrootsListingAvailability, RadrootsListingBin, + RadrootsListingDeliveryMethod, RadrootsListingLocation, RadrootsListingProduct, + RadrootsListingStatus, + }, }; use crate::listing::draft::{RadrootsCanonicalListingDraft, RadrootsListingDraftDocumentV1}; + use crate::listing::validation::validate_listing_event; use super::{ LISTING_DRAFT_CONTRACT_ID, LISTING_PUBLISHED_CONTRACT_ID, RadrootsListingLifecycleState, @@ -198,10 +204,20 @@ mod tests { resource_area: None, plot: None, discounts: None, - inventory_available: None, - availability: None, - delivery_method: None, - location: None, + inventory_available: Some(RadrootsCoreDecimal::from(5u32)), + availability: Some(RadrootsListingAvailability::Status { + status: RadrootsListingStatus::Active, + }), + delivery_method: Some(RadrootsListingDeliveryMethod::Pickup), + location: Some(RadrootsListingLocation { + primary: "Farm".to_string(), + city: None, + region: None, + country: None, + lat: None, + lng: None, + geohash: None, + }), images: None, } } @@ -334,4 +350,24 @@ mod tests { assert_eq!(first.tags, second.tags); assert_eq!(first.content, second.content); } + + #[test] + fn build_listing_mutation_draft_output_validates_as_trade_listing() { + let publish = RadrootsListingMutation::publish(canonical_draft()); + let draft = build_listing_mutation_draft(&publish, 1_700_000_000).expect("draft"); + + let event = RadrootsNostrEvent { + id: String::new(), + author: draft.expected_pubkey.clone(), + created_at: draft.created_at, + kind: draft.kind, + tags: draft.tags, + content: draft.content, + sig: String::new(), + }; + let validated = validate_listing_event(&event).expect("validated listing"); + + assert_eq!(validated.seller_pubkey, SELLER); + assert!(validated.listing_addr.contains(&format!(":{SELLER}:"))); + } } diff --git a/crates/trade/src/listing/publish.rs b/crates/trade/src/listing/publish.rs @@ -1,177 +0,0 @@ -#![forbid(unsafe_code)] - -#[cfg(not(feature = "std"))] -use alloc::string::String; - -use radroots_events::RadrootsNostrEvent; -use radroots_events::kinds::{KIND_LISTING, KIND_LISTING_DRAFT, is_listing_kind}; -use radroots_events::listing::RadrootsListing; -use radroots_events_codec::listing::encode::to_wire_parts_with_kind; -use thiserror::Error; - -use crate::listing::validation::{RadrootsTradeListing, validate_listing_event}; - -#[derive(Debug, Error)] -pub enum RadrootsTradeListingPublishError { - #[error("listing kind must be {KIND_LISTING} or {KIND_LISTING_DRAFT}")] - InvalidKind, - #[error("invalid listing contract: {0}")] - InvalidContract(String), -} - -pub fn resolve_listing_kind(kind: Option<u32>) -> Result<u32, RadrootsTradeListingPublishError> { - let kind = kind.unwrap_or(KIND_LISTING); - if !is_listing_kind(kind) { - return Err(RadrootsTradeListingPublishError::InvalidKind); - } - Ok(kind) -} - -pub fn canonicalize_listing_for_seller( - mut listing: RadrootsListing, - seller_pubkey: &str, -) -> RadrootsListing { - if listing.farm.pubkey.trim().is_empty() { - listing.farm.pubkey = seller_pubkey.to_string(); - } - listing -} - -pub fn validate_listing_for_seller( - listing: RadrootsListing, - seller_pubkey: &str, - kind: u32, -) -> Result<RadrootsTradeListing, RadrootsTradeListingPublishError> { - let parts = to_wire_parts_with_kind(&listing, kind) - .map_err(|error| RadrootsTradeListingPublishError::InvalidContract(error.to_string()))?; - let canonical = RadrootsNostrEvent { - id: String::new(), - author: seller_pubkey.to_string(), - created_at: 0, - kind: parts.kind, - tags: parts.tags, - content: parts.content, - sig: String::new(), - }; - validate_listing_event(&canonical) - .map_err(|error| RadrootsTradeListingPublishError::InvalidContract(error.to_string())) -} - -#[cfg(test)] -mod tests { - use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, - RadrootsCoreQuantityPrice, RadrootsCoreUnit, - }; - use radroots_events::farm::RadrootsFarmRef; - use radroots_events::ids::{RadrootsDTag, RadrootsInventoryBinId}; - use radroots_events::listing::{ - RadrootsListing, RadrootsListingAvailability, RadrootsListingBin, - RadrootsListingDeliveryMethod, RadrootsListingLocation, RadrootsListingProduct, - }; - - use super::{ - canonicalize_listing_for_seller, resolve_listing_kind, validate_listing_for_seller, - }; - - const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - - fn d_tag(raw: &str) -> RadrootsDTag { - RadrootsDTag::parse(raw).expect("d tag") - } - - fn bin_id(raw: &str) -> RadrootsInventoryBinId { - RadrootsInventoryBinId::parse(raw).expect("bin id") - } - - fn base_listing() -> RadrootsListing { - RadrootsListing { - d_tag: d_tag("AAAAAAAAAAAAAAAAAAAAAg"), - published_at: None, - farm: RadrootsFarmRef { - pubkey: String::new(), - d_tag: "AAAAAAAAAAAAAAAAAAAAAA".into(), - }, - product: RadrootsListingProduct { - key: "coffee".into(), - title: "Coffee".into(), - category: "coffee".into(), - summary: Some("Single origin coffee".into()), - process: None, - lot: None, - location: None, - profile: None, - year: None, - }, - primary_bin_id: bin_id("bin-1"), - bins: vec![RadrootsListingBin { - bin_id: bin_id("bin-1"), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1000u32), - RadrootsCoreUnit::MassG, - ), - price_per_canonical_unit: RadrootsCoreQuantityPrice { - amount: RadrootsCoreMoney::new( - RadrootsCoreDecimal::from(20u32), - RadrootsCoreCurrency::USD, - ), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1u32), - RadrootsCoreUnit::MassG, - ), - }, - display_amount: None, - display_unit: None, - display_label: None, - display_price: None, - display_price_unit: None, - }], - resource_area: None, - plot: None, - discounts: None, - inventory_available: Some(RadrootsCoreDecimal::from(5u32)), - availability: Some(RadrootsListingAvailability::Status { - status: radroots_events::listing::RadrootsListingStatus::Active, - }), - delivery_method: Some(RadrootsListingDeliveryMethod::Pickup), - location: Some(RadrootsListingLocation { - primary: "Farm".into(), - city: None, - region: None, - country: None, - lat: None, - lng: None, - geohash: None, - }), - images: None, - } - } - - #[test] - fn resolve_listing_kind_accepts_supported_kinds() { - assert_eq!( - resolve_listing_kind(None).unwrap(), - radroots_events::kinds::KIND_LISTING - ); - assert_eq!( - resolve_listing_kind(Some(radroots_events::kinds::KIND_LISTING_DRAFT)).unwrap(), - radroots_events::kinds::KIND_LISTING_DRAFT - ); - } - - #[test] - fn canonicalize_listing_sets_missing_farm_pubkey() { - let listing = canonicalize_listing_for_seller(base_listing(), SELLER); - assert_eq!(listing.farm.pubkey, SELLER); - } - - #[test] - fn validate_listing_for_seller_returns_listing_addr() { - let listing = canonicalize_listing_for_seller(base_listing(), SELLER); - let validated = - validate_listing_for_seller(listing, SELLER, radroots_events::kinds::KIND_LISTING) - .expect("validated listing"); - assert_eq!(validated.seller_pubkey, SELLER); - assert!(validated.listing_addr.contains(&format!(":{SELLER}:"))); - } -}