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:
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}:")));
- }
-}