lib

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

commit dfcc32733075b2e6d0cb5214a237504540af6e4f
parent 6527cc9fbd1748846498f504d8408095346eab38
Author: triesap <tyson@radroots.org>
Date:   Thu,  7 May 2026 17:01:11 +0000

sdk: add profile relay publish

- expose profile local-identity relay publish APIs
- reuse shared relay-direct signing and receipt handling
- prove profile draft publish against an ACK relay
- cover wrong transport and signer mode failures

Diffstat:
Mcrates/sdk/src/client.rs | 37+++++++++++++++++++++++++++++++++++++
Mcrates/sdk/tests/relay_direct.rs | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 166 insertions(+), 0 deletions(-)

diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs @@ -1880,6 +1880,43 @@ impl<'a> ProfileClient<'a> { profile::build_draft(profile_value, profile_type) } + #[cfg(all( + feature = "identity-models", + feature = "relay-client", + feature = "signing" + ))] + pub async fn publish_with_identity( + &self, + identity: &RadrootsIdentity, + profile_value: &RadrootsProfile, + profile_type: Option<RadrootsProfileType>, + ) -> Result<SdkPublishReceipt, SdkPublishError> { + let parts = profile::build_draft(profile_value, profile_type) + .map_err(|err| SdkPublishError::Encode(err.to_string()))?; + self.client + .publish_parts_via_relay_with_identity(identity, parts, "profile.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, + "profile.publish_draft_with_identity", + ) + .await + } + #[cfg(feature = "radrootsd-client")] pub async fn publish_profile_via_radrootsd( &self, diff --git a/crates/sdk/tests/relay_direct.rs b/crates/sdk/tests/relay_direct.rs @@ -17,6 +17,7 @@ use radroots_sdk::listing::{ RadrootsListingDeliveryMethod, RadrootsListingLocation, RadrootsListingProduct, RadrootsListingStatus, }; +use radroots_sdk::profile::{RadrootsProfile, RadrootsProfileType}; use radroots_sdk::{ RadrootsSdkClient, RadrootsSdkConfig, RelayConfig, SdkEnvironment, SdkPublishError, SdkTransportMode, SdkTransportReceipt, SignerConfig, @@ -161,6 +162,134 @@ fn sample_listing() -> RadrootsListing { } } +fn sample_profile() -> RadrootsProfile { + RadrootsProfile { + name: "north-farm".into(), + display_name: Some("North Farm".into()), + nip05: None, + about: Some("Farm profile".into()), + website: None, + picture: None, + banner: None, + lud06: None, + lud16: None, + bot: None, + } +} + +#[tokio::test] +async fn relay_direct_profile_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 + .profile() + .build_draft(&sample_profile(), Some(RadrootsProfileType::Farm))?; + + let receipt = client + .profile() + .publish_draft_with_identity(&identity, draft) + .await?; + + assert_eq!(receipt.transport, SdkTransportMode::RelayDirect); + assert_eq!(receipt.event_kind, Some(0)); + 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, 0); + assert_eq!(relay_receipt.event.author, identity.public_key_hex()); + assert_eq!( + relay_receipt.event.tags, + vec![vec!["t".to_owned(), "radroots:type:farm".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_profile_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 + .profile() + .publish_with_identity( + &identity, + &sample_profile(), + Some(RadrootsProfileType::Farm), + ) + .await + .expect_err("unsupported transport"); + + assert!(matches!( + error, + SdkPublishError::UnsupportedTransport { + transport: SdkTransportMode::Radrootsd, + operation: "profile.publish_with_identity", + } + )); + + Ok(()) +} + +#[tokio::test] +async fn relay_direct_profile_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 + .profile() + .publish_with_identity( + &identity, + &sample_profile(), + Some(RadrootsProfileType::Farm), + ) + .await + .expect_err("unsupported signer mode"); + + assert!(matches!( + error, + SdkPublishError::UnsupportedSignerMode { + transport: SdkTransportMode::RelayDirect, + signer: SignerConfig::DraftOnly, + required: SignerConfig::LocalIdentity, + operation: "profile.publish_with_identity", + } + )); + + Ok(()) +} + #[tokio::test] async fn relay_direct_listing_publish_accepts_sdk_built_draft() -> TestResult<()> { let relay = AckRelay::spawn().await?;