lib

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

commit 6dc9877f8ef5dcbc3101363562d8bc33a684d967
parent 419e6176bf98528f997ec7c99cda9da79e20eb69
Author: triesap <tyson@radroots.org>
Date:   Mon, 13 Apr 2026 05:57:30 +0000

sdk: add typed radrootsd listing publish path

Diffstat:
Mcrates/sdk/src/client.rs | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/sdk/src/lib.rs | 22++++++++++++----------
Mcrates/sdk/tests/radrootsd.rs | 122++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
3 files changed, 170 insertions(+), 45 deletions(-)

diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs @@ -34,6 +34,8 @@ use crate::{ ) ))] use core::time::Duration; +#[cfg(feature = "radrootsd-client")] +use radroots_events::kinds::KIND_LISTING; #[derive(Debug, Clone, PartialEq, Eq)] pub struct SdkPublishReceipt { @@ -174,6 +176,33 @@ impl core::fmt::Display for SdkPublishError { #[cfg(feature = "std")] impl std::error::Error for SdkPublishError {} +#[cfg(feature = "radrootsd-client")] +#[derive(Clone, PartialEq, Eq)] +pub struct SdkRadrootsdListingPublishOptions { + pub signer_session_id: String, + pub idempotency_key: Option<String>, +} + +#[cfg(feature = "radrootsd-client")] +impl SdkRadrootsdListingPublishOptions { + pub fn new(signer_session_id: impl Into<String>) -> Self { + Self { + signer_session_id: signer_session_id.into(), + idempotency_key: None, + } + } +} + +#[cfg(feature = "radrootsd-client")] +impl fmt::Debug for SdkRadrootsdListingPublishOptions { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug = f.debug_struct("SdkRadrootsdListingPublishOptions"); + debug.field("signer_session_id", &"<redacted>"); + debug.field("idempotency_key", &self.idempotency_key); + debug.finish() + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct RadrootsSdkClient { config: RadrootsSdkConfig, @@ -477,6 +506,48 @@ impl<'a> ListingClient<'a> { ) -> Result<SdkPublishReceipt, SdkPublishError> { self.client.publish_listing_via_radrootsd(request).await } + + #[cfg(feature = "radrootsd-client")] + pub async fn publish_listing_via_radrootsd( + &self, + listing_value: &listing::RadrootsListing, + options: &SdkRadrootsdListingPublishOptions, + ) -> Result<SdkPublishReceipt, SdkPublishError> { + let request = radrootsd::SdkRadrootsdListingPublishRequest { + listing: listing_value.clone(), + kind: Some(KIND_LISTING), + signer_session_id: options.signer_session_id.clone(), + signer_authority: None, + idempotency_key: options.idempotency_key.clone(), + }; + self.client.publish_listing_via_radrootsd(&request).await + } + + #[cfg(feature = "radrootsd-client")] + pub async fn publish_draft_via_radrootsd( + &self, + draft: listing::RadrootsListingDraft, + options: &SdkRadrootsdListingPublishOptions, + ) -> Result<SdkPublishReceipt, SdkPublishError> { + let parts = draft.into_wire_parts(); + let event = RadrootsNostrEvent { + id: String::new(), + author: String::new(), + created_at: 0, + kind: parts.kind, + tags: parts.tags, + content: parts.content, + sig: String::new(), + }; + let request = radrootsd::SdkRadrootsdListingPublishRequest::from_event( + &event, + options.signer_session_id.clone(), + None, + options.idempotency_key.clone(), + ) + .map_err(|err| SdkPublishError::Encode(err.to_string()))?; + self.client.publish_listing_via_radrootsd(&request).await + } } #[derive(Debug, Clone, Copy)] diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -25,6 +25,18 @@ pub mod listing; pub mod profile; pub mod trade; +#[cfg(feature = "radrootsd-client")] +pub use crate::adapters::radrootsd::{ + SdkRadrootsdBridgeJob, SdkRadrootsdBridgePublishResponse, SdkRadrootsdListingPublishRequest, + SdkRadrootsdSignerAuthority, +}; +#[cfg(feature = "radrootsd-client")] +pub use crate::client::SdkRadrootsdListingPublishOptions; +pub use crate::client::{ + FarmClient, ListingClient, ProfileClient, RadrootsSdkClient, SdkPublishError, + SdkPublishReceipt, SdkRadrootsdPublishReceipt, SdkRelayFailure, SdkRelayPublishReceipt, + SdkResolvedTransportTarget, SdkTransportReceipt, TradeClient, +}; pub use crate::config::{ NetworkConfig, RADROOTS_SDK_LOCAL_RADROOTSD_ENDPOINT, RADROOTS_SDK_LOCAL_RELAY_URL, RADROOTS_SDK_PRODUCTION_RADROOTSD_ENDPOINT, RADROOTS_SDK_PRODUCTION_RELAY_URL, @@ -32,16 +44,6 @@ pub use crate::config::{ RadrootsdAuth, RadrootsdConfig, RelayConfig, RetryPolicy, SdkConfigError, SdkEnvironment, SdkTransportMode, SignerConfig, }; -pub use crate::client::{ - FarmClient, ListingClient, ProfileClient, RadrootsSdkClient, SdkPublishError, - SdkPublishReceipt, SdkRadrootsdPublishReceipt, SdkRelayFailure, - SdkRelayPublishReceipt, SdkResolvedTransportTarget, SdkTransportReceipt, TradeClient, -}; -#[cfg(feature = "radrootsd-client")] -pub use crate::adapters::radrootsd::{ - SdkRadrootsdBridgeJob, SdkRadrootsdBridgePublishResponse, - SdkRadrootsdListingPublishRequest, SdkRadrootsdSignerAuthority, -}; pub use radroots_events::{ RadrootsNostrEvent, RadrootsNostrEventPtr, RadrootsNostrEventRef, farm::RadrootsFarm, diff --git a/crates/sdk/tests/radrootsd.rs b/crates/sdk/tests/radrootsd.rs @@ -13,8 +13,9 @@ use radroots_sdk::listing::{ use radroots_sdk::{ RadrootsNostrEvent, RadrootsSdkClient, RadrootsSdkConfig, RadrootsdAuth, RadrootsdConfig, SdkEnvironment, SdkPublishError, SdkRadrootsdBridgeJob, SdkRadrootsdBridgePublishResponse, - SdkRadrootsdListingPublishRequest, SdkRadrootsdPublishReceipt, SdkRadrootsdSignerAuthority, - SdkTransportMode, SdkTransportReceipt, SignerConfig, + SdkRadrootsdListingPublishOptions, SdkRadrootsdListingPublishRequest, + SdkRadrootsdPublishReceipt, SdkRadrootsdSignerAuthority, SdkTransportMode, SdkTransportReceipt, + SignerConfig, }; use serde_json::{Value, json}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -314,6 +315,14 @@ fn radrootsd_debug_redacts_signer_session_values() { assert!(!receipt_debug.contains("session-123")); assert!(receipt_debug.contains("<redacted>")); + + let options = SdkRadrootsdListingPublishOptions { + signer_session_id: "session-123".to_owned(), + idempotency_key: Some("idem-1".to_owned()), + }; + let options_debug = format!("{options:?}"); + assert!(!options_debug.contains("session-123")); + assert!(options_debug.contains("<redacted>")); } #[tokio::test] @@ -353,15 +362,15 @@ async fn radrootsd_listing_publish_accepts_sdk_built_draft() -> TestResult<()> { }; let client = RadrootsSdkClient::from_config(config)?; let draft = client.listing().build_draft(&sample_listing())?; - let event = sdk_event("seller", 1_720_000_000, draft); - let request = SdkRadrootsdListingPublishRequest::from_event( - &event, - "session-123", - None, - Some("idem-1".to_owned()), - )?; - - let receipt = client.listing().publish_via_radrootsd(&request).await?; + let options = SdkRadrootsdListingPublishOptions { + signer_session_id: "session-123".to_owned(), + idempotency_key: Some("idem-1".to_owned()), + }; + + let receipt = client + .listing() + .publish_draft_via_radrootsd(draft, &options) + .await?; let request_json = request_rx.await?; assert_eq!(request_json["method"], "bridge.listing.publish"); @@ -400,22 +409,77 @@ async fn radrootsd_listing_publish_accepts_sdk_built_draft() -> TestResult<()> { } #[tokio::test] +async fn radrootsd_listing_publish_accepts_typed_listing_value() -> TestResult<()> { + let (server, request_rx) = JsonRpcServer::spawn( + Some("Bearer sdk-secret"), + json!({ + "jsonrpc": "2.0", + "id": "radroots-sdk-listing-publish", + "result": { + "deduplicated": false, + "job": { + "job_id": "job-2", + "command": "bridge.listing.publish", + "status": "published", + "terminal": true, + "recovered_after_restart": false, + "signer_mode": "nip46_session:session-456", + "signer_session_id": "session-456", + "event_kind": 30402, + "event_id": "event-2", + "event_addr": "30402:seller:listing-2", + "relay_count": 1, + "acknowledged_relay_count": 1 + } + } + }), + ) + .await?; + + let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Production); + config.transport = SdkTransportMode::Radrootsd; + config.signer = SignerConfig::Nip46; + config.radrootsd = RadrootsdConfig { + endpoint: Some(server.endpoint().to_owned()), + auth: RadrootsdAuth::BearerToken("sdk-secret".to_owned()), + }; + let client = RadrootsSdkClient::from_config(config)?; + let mut options = SdkRadrootsdListingPublishOptions::new("session-456"); + options.idempotency_key = Some("idem-2".to_owned()); + + let receipt = client + .listing() + .publish_listing_via_radrootsd(&sample_listing(), &options) + .await?; + let request_json = request_rx.await?; + + assert_eq!(request_json["method"], "bridge.listing.publish"); + assert_eq!(request_json["params"]["signer_session_id"], "session-456"); + assert_eq!(request_json["params"]["idempotency_key"], "idem-2"); + assert_eq!(request_json["params"]["kind"], 30402); + assert_eq!( + request_json["params"]["listing"]["d_tag"], + "AAAAAAAAAAAAAAAAAAAAAg" + ); + + assert_eq!(receipt.transport, SdkTransportMode::Radrootsd); + assert_eq!(receipt.event_kind, Some(30402)); + assert_eq!(receipt.event_id, Some("event-2".to_owned())); + + Ok(()) +} + +#[tokio::test] async fn radrootsd_listing_publish_rejects_draft_only_signer_mode() -> TestResult<()> { let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Production); config.transport = SdkTransportMode::Radrootsd; config.signer = SignerConfig::DraftOnly; let client = RadrootsSdkClient::from_config(config)?; - let request = SdkRadrootsdListingPublishRequest { - listing: sample_listing(), - kind: None, - signer_session_id: "session-123".to_owned(), - signer_authority: None, - idempotency_key: None, - }; + let options = SdkRadrootsdListingPublishOptions::new("session-123"); let error = client .listing() - .publish_via_radrootsd(&request) + .publish_listing_via_radrootsd(&sample_listing(), &options) .await .expect_err("unsupported signer mode"); @@ -438,17 +502,11 @@ async fn radrootsd_listing_publish_rejects_local_identity_signer_mode() -> TestR config.transport = SdkTransportMode::Radrootsd; config.signer = SignerConfig::LocalIdentity; let client = RadrootsSdkClient::from_config(config)?; - let request = SdkRadrootsdListingPublishRequest { - listing: sample_listing(), - kind: None, - signer_session_id: "session-123".to_owned(), - signer_authority: None, - idempotency_key: None, - }; + let options = SdkRadrootsdListingPublishOptions::new("session-123"); let error = client .listing() - .publish_via_radrootsd(&request) + .publish_listing_via_radrootsd(&sample_listing(), &options) .await .expect_err("unsupported signer mode"); @@ -468,17 +526,11 @@ async fn radrootsd_listing_publish_rejects_local_identity_signer_mode() -> TestR #[tokio::test] async fn radrootsd_listing_publish_rejects_relay_transport_mode() -> TestResult<()> { let client = RadrootsSdkClient::from_config(RadrootsSdkConfig::production())?; - let request = SdkRadrootsdListingPublishRequest { - listing: sample_listing(), - kind: None, - signer_session_id: "session-123".to_owned(), - signer_authority: None, - idempotency_key: None, - }; + let options = SdkRadrootsdListingPublishOptions::new("session-123"); let error = client .listing() - .publish_via_radrootsd(&request) + .publish_listing_via_radrootsd(&sample_listing(), &options) .await .expect_err("unsupported transport");