lib

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

commit 70773bfcdbcb6d10f55a2be9e2a0b1619f093220
parent dfcc32733075b2e6d0cb5214a237504540af6e4f
Author: triesap <tyson@radroots.org>
Date:   Thu,  7 May 2026 17:04:27 +0000

sdk: add farm relay publish

- expose farm local-identity relay publish APIs
- route farm drafts through shared relay receipt handling
- prove farm draft publish against an ACK relay
- cover farm transport and signer mode failures

Diffstat:
Mcrates/sdk/src/client.rs | 36++++++++++++++++++++++++++++++++++++
Mcrates/sdk/tests/relay_direct.rs | 127++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 162 insertions(+), 1 deletion(-)

diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs @@ -1976,6 +1976,42 @@ impl<'a> FarmClient<'a> { farm::build_draft(farm_value) } + #[cfg(all( + feature = "identity-models", + feature = "relay-client", + feature = "signing" + ))] + pub async fn publish_with_identity( + &self, + identity: &RadrootsIdentity, + farm_value: &farm::RadrootsFarm, + ) -> Result<SdkPublishReceipt, SdkPublishError> { + let parts = farm::build_draft(farm_value) + .map_err(|err| SdkPublishError::Encode(err.to_string()))?; + self.client + .publish_parts_via_relay_with_identity(identity, parts, "farm.publish_with_identity") + .await + } + + #[cfg(all( + feature = "identity-models", + feature = "relay-client", + feature = "signing" + ))] + pub async fn publish_draft_with_identity( + &self, + identity: &RadrootsIdentity, + draft: WireEventParts, + ) -> Result<SdkPublishReceipt, SdkPublishError> { + self.client + .publish_parts_via_relay_with_identity( + identity, + draft, + "farm.publish_draft_with_identity", + ) + .await + } + #[cfg(feature = "radrootsd-client")] pub async fn publish_farm_via_radrootsd( &self, diff --git a/crates/sdk/tests/relay_direct.rs b/crates/sdk/tests/relay_direct.rs @@ -10,7 +10,7 @@ use radroots_core::{ RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit, }; -use radroots_sdk::farm::RadrootsFarmRef; +use radroots_sdk::farm::{RadrootsFarm, RadrootsFarmLocation, RadrootsFarmRef}; use radroots_sdk::identity::RadrootsIdentity; use radroots_sdk::listing::{ RadrootsListing, RadrootsListingAvailability, RadrootsListingBin, @@ -177,6 +177,131 @@ fn sample_profile() -> RadrootsProfile { } } +fn sample_farm() -> RadrootsFarm { + RadrootsFarm { + d_tag: "AAAAAAAAAAAAAAAAAAAAAA".into(), + name: "North Farm".into(), + about: Some("Vegetable farm".into()), + website: None, + picture: None, + banner: None, + location: Some(RadrootsFarmLocation { + primary: Some("North Road".into()), + city: None, + region: None, + country: Some("US".into()), + gcs: None, + }), + tags: Some(vec!["vegetables".into()]), + } +} + +#[tokio::test] +async fn relay_direct_farm_publish_accepts_sdk_built_draft() -> TestResult<()> { + let relay = AckRelay::spawn().await?; + let identity = RadrootsIdentity::generate(); + let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Custom); + config.transport = SdkTransportMode::RelayDirect; + config.signer = SignerConfig::LocalIdentity; + config.relay = RelayConfig { + urls: vec![relay.url().to_owned()], + }; + let client = RadrootsSdkClient::from_config(config)?; + let draft = client.farm().build_draft(&sample_farm())?; + + let receipt = client + .farm() + .publish_draft_with_identity(&identity, draft) + .await?; + + assert_eq!(receipt.transport, SdkTransportMode::RelayDirect); + assert_eq!(receipt.event_kind, Some(30340)); + assert!(receipt.event_id.is_some()); + match receipt.transport_receipt { + SdkTransportReceipt::RelayDirect(relay_receipt) => { + assert_eq!( + receipt.event_id.as_deref(), + Some(relay_receipt.event_id.as_str()) + ); + assert_eq!(relay_receipt.event.kind, 30340); + assert_eq!(relay_receipt.event.author, identity.public_key_hex()); + assert_eq!( + relay_receipt.event.tags, + vec![ + vec!["d".to_owned(), "AAAAAAAAAAAAAAAAAAAAAA".to_owned()], + vec!["t".to_owned(), "vegetables".to_owned()] + ] + ); + assert_eq!(relay_receipt.target_relays, vec![relay.url().to_owned()]); + assert_eq!(relay_receipt.connected_relays, vec![relay.url().to_owned()]); + assert_eq!( + relay_receipt.acknowledged_relays, + vec![relay.url().to_owned()] + ); + assert!(relay_receipt.failed_relays.is_empty()); + } + SdkTransportReceipt::Radrootsd(_) => panic!("unexpected radrootsd receipt"), + } + + Ok(()) +} + +#[tokio::test] +async fn relay_direct_farm_publish_rejects_radrootsd_transport_mode() -> TestResult<()> { + let identity = RadrootsIdentity::generate(); + let mut config = RadrootsSdkConfig::production(); + config.transport = SdkTransportMode::Radrootsd; + config.signer = SignerConfig::LocalIdentity; + let client = RadrootsSdkClient::from_config(config)?; + + let error = client + .farm() + .publish_with_identity(&identity, &sample_farm()) + .await + .expect_err("unsupported transport"); + + assert!(matches!( + error, + SdkPublishError::UnsupportedTransport { + transport: SdkTransportMode::Radrootsd, + operation: "farm.publish_with_identity", + } + )); + + Ok(()) +} + +#[tokio::test] +async fn relay_direct_farm_publish_rejects_draft_only_signer_mode() -> TestResult<()> { + let relay = AckRelay::spawn().await?; + let identity = RadrootsIdentity::generate(); + let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Custom); + config.transport = SdkTransportMode::RelayDirect; + config.signer = SignerConfig::DraftOnly; + config.relay = RelayConfig { + urls: vec![relay.url().to_owned()], + }; + let client = RadrootsSdkClient::from_config(config)?; + + let error = client + .farm() + .publish_with_identity(&identity, &sample_farm()) + .await + .expect_err("unsupported signer mode"); + + assert!(matches!( + error, + SdkPublishError::UnsupportedSignerMode { + transport: SdkTransportMode::RelayDirect, + signer: SignerConfig::DraftOnly, + required: SignerConfig::LocalIdentity, + operation: "farm.publish_with_identity", + } + )); + + Ok(()) +} + #[tokio::test] async fn relay_direct_profile_publish_accepts_sdk_built_draft() -> TestResult<()> { let relay = AckRelay::spawn().await?;