commit a91221f03664e4d45556268b4d5014c0be421e92
parent 0a33a01d2ba94c67d19e2677819b1f29882ec679
Author: triesap <tyson@radroots.org>
Date: Mon, 15 Jun 2026 16:48:45 -0700
sdk: split listing publish runtime requests
- replace the broad listing publish request with prepare and enqueue contracts
- return typed listing publish plans with event and address identifiers
- separate relay target selection from relay URL validation policy
- update runtime tests and examples for the product listing API
Diffstat:
8 files changed, 291 insertions(+), 119 deletions(-)
diff --git a/crates/sdk/examples/runtime_local.rs b/crates/sdk/examples/runtime_local.rs
@@ -16,7 +16,8 @@ use radroots_sdk::protocol::listing::{
RadrootsListing, RadrootsListingBin, RadrootsListingProduct,
};
use radroots_sdk::{
- ListingPublishRequest, OrderStatusRequest, PushOutboxRequest, RadrootsSdk, RadrootsSdkTimestamp,
+ ListingEnqueuePublishRequest, ListingPreparePublishRequest, OrderStatusRequest,
+ PushOutboxRequest, RadrootsSdk, RadrootsSdkTimestamp, SdkRelayTargetPolicy,
};
const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
@@ -79,13 +80,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.build()
.await?;
let actor = RadrootsActorContext::test(SELLER, [RadrootsActorRole::Seller])?;
- let request =
- ListingPublishRequest::new(sample_listing()).try_with_idempotency_key("example-1")?;
+ let listing = sample_listing();
+ let prepare_request = ListingPreparePublishRequest::new(actor.clone(), listing.clone());
+ let enqueue_request = ListingEnqueuePublishRequest::new(
+ actor,
+ listing,
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_idempotency_key("example-1")?;
- let prepared = sdk.listings().prepare_publish(&actor, request.clone())?;
+ let prepared = sdk.listings().prepare_publish(prepare_request)?;
let enqueue = sdk
.listings()
- .enqueue_publish(&actor, &FixtureSigner::new(SELLER), request)
+ .enqueue_publish(enqueue_request, &FixtureSigner::new(SELLER))
.await?;
let push = sdk
.sync()
@@ -99,7 +106,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.status(OrderStatusRequest::new("example-order-1"))
.await?;
- assert_eq!(prepared.listing_address, enqueue.listing_address);
+ assert_eq!(
+ prepared.public_listing_addr.as_str(),
+ enqueue.listing_address.as_str()
+ );
assert_eq!(push.attempted_events, 1);
assert!(!order_status.found);
Ok(())
diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs
@@ -55,7 +55,8 @@ pub use crate::error::{
};
#[cfg(feature = "runtime")]
pub use crate::listings_runtime::{
- ListingEnqueueReceipt, ListingPublishRequest, PreparedListingPublish,
+ ListingEnqueuePublishRequest, ListingEnqueueReceipt, ListingPreparePublishRequest,
+ ListingPublishPlan,
};
#[cfg(feature = "runtime")]
pub use crate::orders_runtime::{
@@ -75,7 +76,7 @@ pub use crate::runtime::{
#[cfg(feature = "runtime")]
pub use crate::runtime_targets::{
SDK_IDEMPOTENCY_KEY_MAX_LEN, SDK_RELAY_TARGET_MAX_COUNT, SdkIdempotencyKey,
- SdkRelayTargetPolicy, SdkRelayTargetSet,
+ SdkRelayTargetPolicy, SdkRelayTargetSet, SdkRelayUrlPolicy,
};
#[cfg(feature = "runtime")]
pub use crate::sync_runtime::{
diff --git a/crates/sdk/src/listings_runtime.rs b/crates/sdk/src/listings_runtime.rs
@@ -2,7 +2,7 @@
use crate::{
ListingsClient, RadrootsSdkError, RadrootsSdkEventReference, RadrootsSdkLocalMutationReceipt,
RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, SdkIdempotencyKey, SdkRelayTargetPolicy,
- SdkRelayTargetSet,
+ SdkRelayTargetSet, SdkRelayUrlPolicy,
};
#[cfg(feature = "runtime")]
use radroots_authority::{RadrootsActorContext, RadrootsEventSigner, sign_authorized_draft};
@@ -12,6 +12,7 @@ use radroots_event_store::RadrootsEventIngest;
use radroots_events::{
RadrootsNostrEvent,
draft::{RadrootsFrozenEventDraft, RadrootsSignedNostrEvent},
+ ids::{RadrootsEventId, RadrootsListingAddress},
listing::RadrootsListing,
};
#[cfg(feature = "runtime")]
@@ -27,37 +28,87 @@ const LISTING_PUBLISH_OPERATION_KIND: &str = "listing.publish.v1";
#[cfg(feature = "runtime")]
#[derive(Clone, Debug)]
-pub struct ListingPublishRequest {
- pub listing: RadrootsListing,
- pub target_relays: Option<SdkRelayTargetSet>,
- pub idempotency_key: Option<SdkIdempotencyKey>,
+pub struct ListingPreparePublishRequest {
+ pub actor: RadrootsActorContext,
+ pub document: RadrootsListingDraftDocumentV1,
+ pub created_at: Option<RadrootsSdkTimestamp>,
}
#[cfg(feature = "runtime")]
-impl ListingPublishRequest {
- pub fn new(listing: RadrootsListing) -> Self {
+impl ListingPreparePublishRequest {
+ pub fn new(actor: RadrootsActorContext, listing: RadrootsListing) -> Self {
Self {
- listing,
- target_relays: None,
- idempotency_key: None,
+ actor,
+ document: RadrootsListingDraftDocumentV1::new(listing),
+ created_at: None,
}
}
- pub fn with_target_relays(mut self, target_relays: SdkRelayTargetSet) -> Self {
- self.target_relays = Some(target_relays);
+ pub fn from_document(
+ actor: RadrootsActorContext,
+ document: RadrootsListingDraftDocumentV1,
+ ) -> Self {
+ Self {
+ actor,
+ document,
+ created_at: None,
+ }
+ }
+
+ pub fn with_created_at(mut self, created_at: RadrootsSdkTimestamp) -> Self {
+ self.created_at = Some(created_at);
self
}
+}
+
+#[cfg(feature = "runtime")]
+#[derive(Clone, Debug)]
+pub struct ListingEnqueuePublishRequest {
+ pub actor: RadrootsActorContext,
+ pub document: RadrootsListingDraftDocumentV1,
+ pub target_relays: SdkRelayTargetPolicy,
+ pub idempotency_key: Option<SdkIdempotencyKey>,
+ pub created_at: Option<RadrootsSdkTimestamp>,
+}
+
+#[cfg(feature = "runtime")]
+impl ListingEnqueuePublishRequest {
+ pub fn new(
+ actor: RadrootsActorContext,
+ listing: RadrootsListing,
+ target_relays: SdkRelayTargetPolicy,
+ ) -> Self {
+ Self::from_document(
+ actor,
+ RadrootsListingDraftDocumentV1::new(listing),
+ target_relays,
+ )
+ }
+
+ pub fn from_document(
+ actor: RadrootsActorContext,
+ document: RadrootsListingDraftDocumentV1,
+ target_relays: SdkRelayTargetPolicy,
+ ) -> Self {
+ Self {
+ actor,
+ document,
+ target_relays,
+ idempotency_key: None,
+ created_at: None,
+ }
+ }
pub fn try_with_target_relays<I, S>(
mut self,
target_relays: I,
- policy: SdkRelayTargetPolicy,
+ policy: SdkRelayUrlPolicy,
) -> Result<Self, RadrootsSdkError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
- self.target_relays = Some(SdkRelayTargetSet::new(target_relays, policy)?);
+ self.target_relays = SdkRelayTargetPolicy::try_explicit(target_relays, policy)?;
Ok(self)
}
@@ -73,13 +124,20 @@ impl ListingPublishRequest {
self.idempotency_key = Some(SdkIdempotencyKey::new(idempotency_key)?);
Ok(self)
}
+
+ pub fn with_created_at(mut self, created_at: RadrootsSdkTimestamp) -> Self {
+ self.created_at = Some(created_at);
+ self
+ }
}
#[cfg(feature = "runtime")]
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct PreparedListingPublish {
- pub draft: RadrootsFrozenEventDraft,
- pub listing_address: String,
+pub struct ListingPublishPlan {
+ pub public_listing_addr: RadrootsListingAddress,
+ pub draft_listing_addr: RadrootsListingAddress,
+ pub expected_event_id: RadrootsEventId,
+ pub frozen_draft: RadrootsFrozenEventDraft,
pub created_at: RadrootsSdkTimestamp,
}
@@ -94,51 +152,41 @@ pub struct ListingEnqueueReceipt {
impl<'sdk> ListingsClient<'sdk> {
pub fn prepare_publish(
&self,
- actor: &RadrootsActorContext,
- request: ListingPublishRequest,
- ) -> Result<PreparedListingPublish, RadrootsSdkError> {
- let created_at = self.sdk.now()?;
- let created_at_nostr = created_at.try_into_nostr_created_at()?;
- let canonical = canonical_listing_draft(actor, request.listing)?;
- let mutation = RadrootsListingMutation::publish(canonical);
- let listing_address = mutation.listing_addr()?.as_str().to_owned();
- let draft = build_listing_mutation_draft(&mutation, created_at_nostr)?;
- Ok(PreparedListingPublish {
- draft,
- listing_address,
- created_at,
- })
+ request: ListingPreparePublishRequest,
+ ) -> Result<ListingPublishPlan, RadrootsSdkError> {
+ let created_at = self.resolved_created_at(request.created_at)?;
+ listing_publish_plan(&request.actor, request.document, created_at)
}
pub async fn enqueue_publish<S>(
&self,
- actor: &RadrootsActorContext,
+ request: ListingEnqueuePublishRequest,
signer: &S,
- request: ListingPublishRequest,
) -> Result<ListingEnqueueReceipt, RadrootsSdkError>
where
S: RadrootsEventSigner + ?Sized,
{
- let target_relays = self.resolved_target_relays(&request)?;
+ let target_relays = self.resolved_target_relays(&request.target_relays)?;
let idempotency_key = request.idempotency_key.clone();
- let prepared = self.prepare_publish(actor, request)?;
- let signed_event = sign_authorized_draft(actor, signer, &prepared.draft)?;
+ let created_at = self.resolved_created_at(request.created_at)?;
+ let plan = listing_publish_plan(&request.actor, request.document, created_at)?;
+ let signed_event = sign_authorized_draft(&request.actor, signer, &plan.frozen_draft)?;
let idempotency_key = match idempotency_key {
Some(idempotency_key) => idempotency_key,
None => SdkIdempotencyKey::derive(
LISTING_PUBLISH_OPERATION_KIND,
- prepared.draft.expected_event_id.as_str(),
- prepared.draft.expected_pubkey.as_str(),
+ plan.frozen_draft.expected_event_id.as_str(),
+ plan.frozen_draft.expected_pubkey.as_str(),
target_relays.relays(),
)?,
};
- let observed_at_ms = i64::from(prepared.draft.created_at) * 1_000;
+ let observed_at_ms = i64::from(plan.frozen_draft.created_at) * 1_000;
let event = event_from_signed(&signed_event);
let ingest = RadrootsEventIngest::new(event, observed_at_ms)
.with_raw_json(signed_event.raw_json.clone());
let ingest_receipt = self.sdk._event_store.ingest_event(ingest).await?;
let outbox_input = signed_outbox_input(
- &prepared,
+ &plan,
signed_event.clone(),
target_relays.into_vec(),
idempotency_key,
@@ -158,7 +206,7 @@ impl<'sdk> ListingsClient<'sdk> {
)
})?;
Ok(ListingEnqueueReceipt {
- listing_address: prepared.listing_address,
+ listing_address: plan.public_listing_addr.into_string(),
local: RadrootsSdkLocalMutationReceipt {
event: RadrootsSdkEventReference {
event_id: signed_event.id,
@@ -178,11 +226,23 @@ impl<'sdk> ListingsClient<'sdk> {
fn resolved_target_relays(
&self,
- request: &ListingPublishRequest,
+ target_relays: &SdkRelayTargetPolicy,
) -> Result<SdkRelayTargetSet, RadrootsSdkError> {
- match request.target_relays.as_ref() {
- Some(target_relays) => Ok(target_relays.clone()),
- None => SdkRelayTargetSet::from_normalized_relays(self.sdk.relay_urls().to_vec()),
+ match target_relays {
+ SdkRelayTargetPolicy::Explicit(target_relays) => Ok(target_relays.clone()),
+ SdkRelayTargetPolicy::UseConfiguredRelays => {
+ SdkRelayTargetSet::from_normalized_relays(self.sdk.relay_urls().to_vec())
+ }
+ }
+ }
+
+ fn resolved_created_at(
+ &self,
+ created_at: Option<RadrootsSdkTimestamp>,
+ ) -> Result<RadrootsSdkTimestamp, RadrootsSdkError> {
+ match created_at {
+ Some(created_at) => Ok(created_at),
+ None => self.sdk.now(),
}
}
}
@@ -190,15 +250,39 @@ impl<'sdk> ListingsClient<'sdk> {
#[cfg(feature = "runtime")]
fn canonical_listing_draft(
actor: &RadrootsActorContext,
- listing: RadrootsListing,
+ document: RadrootsListingDraftDocumentV1,
) -> Result<RadrootsCanonicalListingDraft, RadrootsSdkError> {
- let document = RadrootsListingDraftDocumentV1::new(listing);
canonicalize_listing_draft(actor, document).map_err(Into::into)
}
#[cfg(feature = "runtime")]
+fn listing_publish_plan(
+ actor: &RadrootsActorContext,
+ document: RadrootsListingDraftDocumentV1,
+ created_at: RadrootsSdkTimestamp,
+) -> Result<ListingPublishPlan, RadrootsSdkError> {
+ let created_at_nostr = created_at.try_into_nostr_created_at()?;
+ let canonical = canonical_listing_draft(actor, document)?;
+ let public_listing_addr = canonical.public_listing_addr().clone();
+ let draft_listing_addr = canonical.draft_listing_addr().clone();
+ let mutation = RadrootsListingMutation::publish(canonical);
+ let frozen_draft = build_listing_mutation_draft(&mutation, created_at_nostr)?;
+ let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id.as_str())
+ .map_err(|error| RadrootsSdkError::InvalidRequest {
+ message: format!("listing publish draft produced invalid event id: {error}"),
+ })?;
+ Ok(ListingPublishPlan {
+ public_listing_addr,
+ draft_listing_addr,
+ expected_event_id,
+ frozen_draft,
+ created_at,
+ })
+}
+
+#[cfg(feature = "runtime")]
fn signed_outbox_input(
- prepared: &PreparedListingPublish,
+ plan: &ListingPublishPlan,
signed_event: RadrootsSignedNostrEvent,
target_relays: Vec<String>,
idempotency_key: SdkIdempotencyKey,
@@ -207,7 +291,7 @@ fn signed_outbox_input(
) -> RadrootsOutboxSignedOperationInput {
RadrootsOutboxSignedOperationInput::new(
LISTING_PUBLISH_OPERATION_KIND,
- prepared.draft.clone(),
+ plan.frozen_draft.clone(),
signed_event,
target_relays,
event_store_inserted,
diff --git a/crates/sdk/src/runtime.rs b/crates/sdk/src/runtime.rs
@@ -1,6 +1,6 @@
#[cfg(feature = "runtime")]
use crate::{
- ListingsClient, OrdersClient, RadrootsSdkError, SdkRelayTargetPolicy, SdkRelayTargetSet,
+ ListingsClient, OrdersClient, RadrootsSdkError, SdkRelayTargetSet, SdkRelayUrlPolicy,
SyncClient, error::RadrootsSdkRecoveryAction,
};
#[cfg(feature = "runtime")]
@@ -89,7 +89,7 @@ pub struct RadrootsSdkBuilder {
storage: RadrootsSdkStorageConfig,
clock: RadrootsSdkClock,
relay_urls: Vec<String>,
- relay_target_policy: SdkRelayTargetPolicy,
+ relay_url_policy: SdkRelayUrlPolicy,
}
#[cfg(feature = "runtime")]
@@ -99,7 +99,7 @@ impl Default for RadrootsSdkBuilder {
storage: RadrootsSdkStorageConfig::Memory,
clock: RadrootsSdkClock::System,
relay_urls: Vec::new(),
- relay_target_policy: SdkRelayTargetPolicy::Public,
+ relay_url_policy: SdkRelayUrlPolicy::Public,
}
}
}
@@ -131,15 +131,15 @@ impl RadrootsSdkBuilder {
self
}
- pub fn relay_target_policy(mut self, policy: SdkRelayTargetPolicy) -> Self {
- self.relay_target_policy = policy;
+ pub fn relay_url_policy(mut self, policy: SdkRelayUrlPolicy) -> Self {
+ self.relay_url_policy = policy;
self
}
pub async fn build(self) -> Result<RadrootsSdk, RadrootsSdkError> {
let storage = open_storage(&self.storage).await?;
let relay_urls =
- SdkRelayTargetSet::from_configured_relays(&self.relay_urls, self.relay_target_policy)?;
+ SdkRelayTargetSet::from_configured_relays(&self.relay_urls, self.relay_url_policy)?;
Ok(RadrootsSdk {
_event_store: storage.event_store,
_outbox: storage.outbox,
diff --git a/crates/sdk/src/runtime_targets.rs b/crates/sdk/src/runtime_targets.rs
@@ -8,12 +8,12 @@ pub const SDK_RELAY_TARGET_MAX_COUNT: usize = 20;
pub const SDK_IDEMPOTENCY_KEY_MAX_LEN: usize = 256;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum SdkRelayTargetPolicy {
+pub enum SdkRelayUrlPolicy {
Public,
Localhost,
}
-impl SdkRelayTargetPolicy {
+impl SdkRelayUrlPolicy {
fn relay_transport_policy(self) -> RadrootsRelayUrlPolicy {
match self {
Self::Public => RadrootsRelayUrlPolicy::Public,
@@ -23,12 +23,35 @@ impl SdkRelayTargetPolicy {
}
#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum SdkRelayTargetPolicy {
+ Explicit(SdkRelayTargetSet),
+ UseConfiguredRelays,
+}
+
+impl SdkRelayTargetPolicy {
+ pub fn explicit(targets: SdkRelayTargetSet) -> Self {
+ Self::Explicit(targets)
+ }
+
+ pub fn try_explicit<I, S>(
+ relays: I,
+ url_policy: SdkRelayUrlPolicy,
+ ) -> Result<Self, RadrootsSdkError>
+ where
+ I: IntoIterator<Item = S>,
+ S: AsRef<str>,
+ {
+ Ok(Self::Explicit(SdkRelayTargetSet::new(relays, url_policy)?))
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SdkRelayTargetSet {
relays: Vec<String>,
}
impl SdkRelayTargetSet {
- pub fn new<I, S>(relays: I, policy: SdkRelayTargetPolicy) -> Result<Self, RadrootsSdkError>
+ pub fn new<I, S>(relays: I, policy: SdkRelayUrlPolicy) -> Result<Self, RadrootsSdkError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
@@ -58,7 +81,7 @@ impl SdkRelayTargetSet {
pub(crate) fn from_configured_relays<I, S>(
relays: I,
- policy: SdkRelayTargetPolicy,
+ policy: SdkRelayUrlPolicy,
) -> Result<Vec<String>, RadrootsSdkError>
where
I: IntoIterator<Item = S>,
@@ -161,7 +184,7 @@ struct SdkIdempotencyDerivationInput<'a> {
fn normalized_relay_url(
value: &str,
- policy: SdkRelayTargetPolicy,
+ policy: SdkRelayUrlPolicy,
) -> Result<String, RadrootsSdkError> {
let relay = RadrootsRelayUrl::parse(value, policy.relay_transport_policy())
.map_err(|error| invalid_request(format!("invalid relay target: {error}")))?;
diff --git a/crates/sdk/tests/listings_runtime.rs b/crates/sdk/tests/listings_runtime.rs
@@ -18,8 +18,9 @@ use radroots_events::{
};
use radroots_outbox::{RadrootsOutbox, RadrootsOutboxEventState};
use radroots_sdk::{
- ListingPublishRequest, RadrootsSdk, RadrootsSdkError, RadrootsSdkRecoveryAction,
- RadrootsSdkTimestamp, SdkRelayTargetPolicy, SdkRelayTargetSet,
+ ListingEnqueuePublishRequest, ListingPreparePublishRequest, RadrootsSdk, RadrootsSdkError,
+ RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, SdkRelayTargetPolicy, SdkRelayTargetSet,
+ SdkRelayUrlPolicy,
};
const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
@@ -92,6 +93,10 @@ fn actor() -> RadrootsActorContext {
RadrootsActorContext::test(SELLER, [RadrootsActorRole::Seller]).expect("actor")
}
+fn non_seller_actor() -> RadrootsActorContext {
+ RadrootsActorContext::test(SELLER, [RadrootsActorRole::Buyer]).expect("actor")
+}
+
fn listing(d_tag: &str, title: &str) -> RadrootsListing {
RadrootsListing {
d_tag: RadrootsDTag::parse(d_tag).expect("d tag"),
@@ -160,14 +165,19 @@ async fn directory_sdk() -> (tempfile::TempDir, RadrootsSdk) {
#[tokio::test]
async fn prepare_publish_is_side_effect_free() {
let (_tempdir, sdk) = directory_sdk().await;
- let request = ListingPublishRequest::new(listing(LISTING_A_D_TAG, "Coffee"));
- let prepared = sdk
- .listings()
- .prepare_publish(&actor(), request)
- .expect("prepared");
+ let request = ListingPreparePublishRequest::new(actor(), listing(LISTING_A_D_TAG, "Coffee"));
+ let prepared = sdk.listings().prepare_publish(request).expect("prepared");
- assert_eq!(prepared.draft.kind, KIND_LISTING);
+ assert_eq!(prepared.frozen_draft.kind, KIND_LISTING);
assert_eq!(prepared.created_at.unix_seconds(), 1_700_000_000);
+ assert_eq!(
+ prepared.expected_event_id,
+ prepared.frozen_draft.expected_event_id
+ );
+ assert_eq!(
+ prepared.public_listing_addr.as_str(),
+ format!("{KIND_LISTING}:{SELLER}:{LISTING_A_D_TAG}")
+ );
let paths = sdk.storage_paths().expect("paths");
let event_store = RadrootsEventStore::open_file(&paths.event_store_path)
@@ -175,7 +185,7 @@ async fn prepare_publish_is_side_effect_free() {
.expect("event store");
assert!(
event_store
- .get_event(prepared.draft.expected_event_id.as_str())
+ .get_event(prepared.expected_event_id.as_str())
.await
.expect("event lookup")
.is_none()
@@ -193,24 +203,45 @@ async fn prepare_publish_is_side_effect_free() {
}
#[tokio::test]
+async fn prepare_publish_rejects_non_seller_actor() {
+ let (_tempdir, sdk) = directory_sdk().await;
+ let request =
+ ListingPreparePublishRequest::new(non_seller_actor(), listing(LISTING_B_D_TAG, "Coffee"));
+
+ let error = sdk
+ .listings()
+ .prepare_publish(request)
+ .expect_err("non seller");
+
+ assert!(matches!(error, RadrootsSdkError::ListingDraft { .. }));
+}
+
+#[tokio::test]
async fn enqueue_publish_stores_event_and_queues_signed_outbox_without_publish() {
let (_tempdir, sdk) = directory_sdk().await;
- let request = ListingPublishRequest::new(listing(LISTING_B_D_TAG, "Coffee"))
- .try_with_idempotency_key("idem-b")
- .expect("idempotency key");
+ let request = ListingEnqueuePublishRequest::new(
+ actor(),
+ listing(LISTING_B_D_TAG, "Coffee"),
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_idempotency_key("idem-b")
+ .expect("idempotency key");
let prepared = sdk
.listings()
- .prepare_publish(&actor(), request.clone())
+ .prepare_publish(ListingPreparePublishRequest::new(
+ actor(),
+ listing(LISTING_B_D_TAG, "Coffee"),
+ ))
.expect("prepared");
let receipt = sdk
.listings()
- .enqueue_publish(&actor(), &FixtureSigner::new(SELLER), request)
+ .enqueue_publish(request, &FixtureSigner::new(SELLER))
.await
.expect("enqueue");
assert_eq!(
receipt.local.event.event_id,
- prepared.draft.expected_event_id
+ prepared.expected_event_id.as_str()
);
assert_eq!(receipt.local.event.kind, KIND_LISTING);
assert!(receipt.local.stored);
@@ -244,10 +275,14 @@ async fn enqueue_publish_stores_event_and_queues_signed_outbox_without_publish()
#[tokio::test]
async fn enqueue_publish_returns_sanitized_signer_errors() {
let (_tempdir, sdk) = directory_sdk().await;
- let request = ListingPublishRequest::new(listing(LISTING_C_D_TAG, "Coffee"));
+ let request = ListingEnqueuePublishRequest::new(
+ actor(),
+ listing(LISTING_C_D_TAG, "Coffee"),
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ );
let error = sdk
.listings()
- .enqueue_publish(&actor(), &FixtureSigner::new(OTHER), request)
+ .enqueue_publish(request, &FixtureSigner::new(OTHER))
.await
.expect_err("signer error");
let message = error.to_string();
@@ -260,20 +295,28 @@ async fn enqueue_publish_returns_sanitized_signer_errors() {
#[tokio::test]
async fn enqueue_publish_reports_partial_local_mutation_after_outbox_conflict() {
let (_tempdir, sdk) = directory_sdk().await;
- let first = ListingPublishRequest::new(listing(LISTING_D_D_TAG, "Coffee"))
- .try_with_idempotency_key("idem-d")
- .expect("idempotency key");
+ let first = ListingEnqueuePublishRequest::new(
+ actor(),
+ listing(LISTING_D_D_TAG, "Coffee"),
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_idempotency_key("idem-d")
+ .expect("idempotency key");
sdk.listings()
- .enqueue_publish(&actor(), &FixtureSigner::new(SELLER), first)
+ .enqueue_publish(first, &FixtureSigner::new(SELLER))
.await
.expect("first enqueue");
- let second = ListingPublishRequest::new(listing(LISTING_E_D_TAG, "Changed"))
- .try_with_idempotency_key("idem-d")
- .expect("idempotency key");
+ let second = ListingEnqueuePublishRequest::new(
+ actor(),
+ listing(LISTING_E_D_TAG, "Changed"),
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_idempotency_key("idem-d")
+ .expect("idempotency key");
let error = sdk
.listings()
- .enqueue_publish(&actor(), &FixtureSigner::new(SELLER), second)
+ .enqueue_publish(second, &FixtureSigner::new(SELLER))
.await
.expect_err("partial");
@@ -290,22 +333,30 @@ async fn enqueue_publish_reports_partial_local_mutation_after_outbox_conflict()
#[tokio::test]
async fn enqueue_publish_derives_order_independent_idempotency_key() {
let (_tempdir, sdk) = directory_sdk().await;
- let first = ListingPublishRequest::new(listing(LISTING_F_D_TAG, "Coffee"))
- .try_with_target_relays([RELAY_B, RELAY, RELAY], SdkRelayTargetPolicy::Public)
- .expect("first target relays");
- let second = ListingPublishRequest::new(listing(LISTING_F_D_TAG, "Coffee")).with_target_relays(
- SdkRelayTargetSet::new([RELAY, RELAY_B], SdkRelayTargetPolicy::Public)
- .expect("second target relays"),
+ let first = ListingEnqueuePublishRequest::new(
+ actor(),
+ listing(LISTING_F_D_TAG, "Coffee"),
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_target_relays([RELAY_B, RELAY, RELAY], SdkRelayUrlPolicy::Public)
+ .expect("first target relays");
+ let second = ListingEnqueuePublishRequest::new(
+ actor(),
+ listing(LISTING_F_D_TAG, "Coffee"),
+ SdkRelayTargetPolicy::explicit(
+ SdkRelayTargetSet::new([RELAY, RELAY_B], SdkRelayUrlPolicy::Public)
+ .expect("second target relays"),
+ ),
);
let first_receipt = sdk
.listings()
- .enqueue_publish(&actor(), &FixtureSigner::new(SELLER), first)
+ .enqueue_publish(first, &FixtureSigner::new(SELLER))
.await
.expect("first enqueue");
let second_receipt = sdk
.listings()
- .enqueue_publish(&actor(), &FixtureSigner::new(SELLER), second)
+ .enqueue_publish(second, &FixtureSigner::new(SELLER))
.await
.expect("second enqueue");
diff --git a/crates/sdk/tests/runtime_foundation.rs b/crates/sdk/tests/runtime_foundation.rs
@@ -3,7 +3,7 @@
use radroots_sdk::{
RadrootsSdk, RadrootsSdkClock, RadrootsSdkError, RadrootsSdkRecoveryAction,
RadrootsSdkStorageConfig, RadrootsSdkTimestamp, SDK_IDEMPOTENCY_KEY_MAX_LEN,
- SDK_RELAY_TARGET_MAX_COUNT, SdkIdempotencyKey, SdkRelayTargetPolicy, SdkRelayTargetSet,
+ SDK_RELAY_TARGET_MAX_COUNT, SdkIdempotencyKey, SdkRelayTargetSet, SdkRelayUrlPolicy,
};
#[tokio::test]
@@ -52,7 +52,7 @@ async fn sdk_builder_rejects_ws_relay_without_localhost_policy() {
#[tokio::test]
async fn sdk_builder_allows_only_local_ws_targets_with_localhost_policy() {
let sdk = RadrootsSdk::builder()
- .relay_target_policy(SdkRelayTargetPolicy::Localhost)
+ .relay_url_policy(SdkRelayUrlPolicy::Localhost)
.relay_url("ws://localhost:8080")
.relay_url("ws://127.0.0.1:8081")
.relay_url("ws://[::1]:8082")
@@ -63,7 +63,7 @@ async fn sdk_builder_allows_only_local_ws_targets_with_localhost_policy() {
assert_eq!(sdk.relay_urls().len(), 3);
let result = RadrootsSdk::builder()
- .relay_target_policy(SdkRelayTargetPolicy::Localhost)
+ .relay_url_policy(SdkRelayUrlPolicy::Localhost)
.relay_url("ws://relay.example.com")
.build()
.await;
@@ -149,7 +149,7 @@ fn relay_target_set_validates_normalizes_dedupes_sorts_and_caps() {
"wss://relay-a.example.com",
"wss://relay-a.example.com",
],
- SdkRelayTargetPolicy::Public,
+ SdkRelayUrlPolicy::Public,
)
.expect("targets");
@@ -162,7 +162,7 @@ fn relay_target_set_validates_normalizes_dedupes_sorts_and_caps() {
);
assert!(matches!(
- SdkRelayTargetSet::new(Vec::<String>::new(), SdkRelayTargetPolicy::Public),
+ SdkRelayTargetSet::new(Vec::<String>::new(), SdkRelayUrlPolicy::Public),
Err(RadrootsSdkError::InvalidRequest { .. })
));
@@ -170,7 +170,7 @@ fn relay_target_set_validates_normalizes_dedupes_sorts_and_caps() {
.map(|index| format!("wss://relay-{index}.example.com"))
.collect::<Vec<_>>();
assert!(matches!(
- SdkRelayTargetSet::new(too_many, SdkRelayTargetPolicy::Public),
+ SdkRelayTargetSet::new(too_many, SdkRelayUrlPolicy::Public),
Err(RadrootsSdkError::InvalidRequest { .. })
));
}
diff --git a/crates/sdk/tests/sync_runtime.rs b/crates/sdk/tests/sync_runtime.rs
@@ -17,9 +17,9 @@ use radroots_events::{
use radroots_outbox::{RadrootsOutbox, RadrootsOutboxEventState, RadrootsOutboxOperationInput};
use radroots_relay_transport::{RadrootsMockRelayPublishAdapter, RadrootsRelayOutcome};
use radroots_sdk::{
- ListingPublishRequest, PUSH_OUTBOX_DEFAULT_LIMIT, PUSH_OUTBOX_MAX_LIMIT, PushOutboxEventState,
- PushOutboxRelayOutcomeKind, PushOutboxRequest, RadrootsSdk, RadrootsSdkError,
- RadrootsSdkTimestamp, SdkRelayTargetPolicy,
+ ListingEnqueuePublishRequest, ListingPreparePublishRequest, PUSH_OUTBOX_DEFAULT_LIMIT,
+ PUSH_OUTBOX_MAX_LIMIT, PushOutboxEventState, PushOutboxRelayOutcomeKind, PushOutboxRequest,
+ RadrootsSdk, RadrootsSdkError, RadrootsSdkTimestamp, SdkRelayTargetPolicy, SdkRelayUrlPolicy,
};
const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
@@ -157,11 +157,14 @@ async fn directory_sdk(relays: &[&str]) -> (tempfile::TempDir, RadrootsSdk) {
async fn enqueue_listing(sdk: &RadrootsSdk, d_tag: &str, title: &str, relays: &[&str]) -> i64 {
sdk.listings()
.enqueue_publish(
- &actor(),
+ ListingEnqueuePublishRequest::new(
+ actor(),
+ listing(d_tag, title),
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_target_relays(relays, SdkRelayUrlPolicy::Public)
+ .expect("relay targets"),
&FixtureSigner::new(SELLER),
- ListingPublishRequest::new(listing(d_tag, title))
- .try_with_target_relays(relays, SdkRelayTargetPolicy::Public)
- .expect("relay targets"),
)
.await
.expect("enqueue")
@@ -331,10 +334,10 @@ async fn push_outbox_does_not_claim_unsigned_outbox_work() {
let (_tempdir, sdk) = directory_sdk(&[RELAY_A]).await;
let prepared = sdk
.listings()
- .prepare_publish(
- &actor(),
- ListingPublishRequest::new(listing(LISTING_C_D_TAG, "Unsigned")),
- )
+ .prepare_publish(ListingPreparePublishRequest::new(
+ actor(),
+ listing(LISTING_C_D_TAG, "Unsigned"),
+ ))
.expect("prepared");
let outbox = RadrootsOutbox::open_file(&sdk.storage_paths().expect("paths").outbox_path)
.await
@@ -342,7 +345,7 @@ async fn push_outbox_does_not_claim_unsigned_outbox_work() {
let unsigned = outbox
.enqueue_operation(RadrootsOutboxOperationInput::new(
"listing.publish.v1",
- prepared.draft,
+ prepared.frozen_draft,
vec![RELAY_A.to_owned()],
1_700_000_000_000,
))