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:
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?;