commit 4a5e94fd492c0d1b870cf45d262f0d340625ea13
parent 70cfc205200e56f45dbfa76b983b50b7db13bbb2
Author: triesap <tyson@radroots.org>
Date: Mon, 3 Aug 2026 10:12:14 +0000
sdk: align package manifest and module root
- declare the final required and optional lower package graph
- replace legacy feature aliases with the governed vocabulary
- establish the eleven-module SDK ownership skeleton
- verify default and all-feature package boundaries
Diffstat:
14 files changed, 141 insertions(+), 2528 deletions(-)
diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml
@@ -6,236 +6,80 @@ edition.workspace = true
authors.workspace = true
rust-version.workspace = true
license.workspace = true
-description = "Public Rust SDK for Radroots"
+description = "Host-neutral asynchronous client engine for Radroots"
repository.workspace = true
homepage.workspace = true
readme = "README.md"
+autotests = false
+autoexamples = false
[lib]
name = "radroots_sdk"
[features]
-default = ["std", "serde", "serde_json", "identity-models"]
-std = [
- "radroots_event/std",
- "radroots_event_codec/std",
- "radroots_trade/std",
-]
-serde = [
- "dep:serde",
- "radroots_event/serde",
- "radroots_trade/serde",
-]
-serde_json = [
- "dep:serde_json",
- "serde",
- "nostr",
- "radroots_event_codec/json",
- "radroots_trade/serde_json",
-]
-nostr = []
-knowledge = [
- "serde",
- "serde_json",
- "radroots_event/knowledge",
- "radroots_event_codec/manifests",
-]
-identity-models = [
- "radroots_identity/std",
-]
-signing = [
- "dep:nostr",
+default = ["memory"]
+memory = ["radroots_storage/memory"]
+sqlite = ["dep:radroots_storage_sqlite"]
+sync = ["dep:radroots_sync"]
+nostr = [
+ "sync",
"dep:radroots_nostr",
- "dep:radroots_protocol",
- "nostr",
- "radroots_nostr/std",
- "radroots_protocol/std",
- "radroots_signing/std",
-]
-transport-nostr-client = [
- "signing",
- "std",
- "serde_json",
- "dep:radroots_transport_nostr",
- "radroots_transport_nostr/client",
-]
-radrootsd-execution = [
- "std",
- "serde_json",
- "dep:futures",
- "dep:radroots_protocol",
- "dep:radroots_transport",
"dep:radroots_transport_nostr",
- "dep:reqwest",
- "radroots_protocol/serde",
- "radroots_protocol/std",
- "radroots_transport/serde",
- "radroots_transport_nostr/std",
]
-signer-adapters = [
- "identity-models",
- "local-signer",
- "signing",
- "std",
+nip46 = [
+ "nostr",
"dep:radroots_nostr_connect",
- "dep:radroots_nostr_signer",
- "dep:tokio",
- "radroots_nostr/events",
]
-runtime = [
- "std",
- "serde_json",
- "dep:tokio",
- "dep:base64",
- "dep:hex",
- "dep:radroots_event_store",
- "dep:radroots_geocoder",
- "dep:radroots_outbox",
- "dep:radroots_protected_store",
- "dep:radroots_protocol",
- "dep:radroots_transport",
- "dep:radroots_transport_nostr",
- "dep:radroots_transport_reticulum",
- "dep:radroots_runtime_paths",
- "dep:radroots_secret_vault",
- "dep:sha2",
- "dep:sqlx",
- "dep:uuid",
- "radroots_signing/std",
- "radroots_event_store/sqlite",
- "radroots_event_store/runtime-tokio",
- "radroots_outbox/sqlite",
- "radroots_outbox/runtime-tokio",
- "radroots_protected_store/std",
- "radroots_protocol/std",
- "radroots_secret_vault/std",
- "radroots_transport/serde",
- "radroots_transport_nostr/std",
- "radroots_transport_nostr/storage",
- "radroots_transport_nostr/runtime-tokio",
-]
-local-signer = [
- "runtime",
- "signing",
+local-signing = [
"dep:radroots_secrets",
"radroots_nostr/signing",
]
-transport-nostr-runtime = [
- "runtime",
- "dep:radroots_nostr",
- "radroots_transport_nostr/client",
-]
-local-runtime = [
- "std",
- "serde",
- "serde_json",
- "runtime",
- "signer-adapters",
- "transport-nostr-runtime",
- "transport-nostr-client",
+radrootsd = [
+ "sync",
+ "dep:reqwest",
]
-local-runtime-radrootsd-execution = [
- "std",
- "serde",
- "serde_json",
- "runtime",
- "signer-adapters",
- "radrootsd-execution",
+geonames = ["dep:radroots_geonames"]
+knowledge = [
+ "radroots_event/knowledge",
+ "radroots_event_codec/knowledge",
]
-test-fixture-geonames-asset = [
- "runtime",
- "radroots_geocoder/test-fixture-geonames-asset",
+native = ["sqlite", "sync", "local-signing"]
+full = [
+ "native",
+ "nostr",
+ "nip46",
+ "radrootsd",
+ "geonames",
+ "knowledge",
]
[dependencies]
-base64 = { workspace = true, optional = true }
-futures = { workspace = true, optional = true }
-radroots_event_store = { workspace = true, optional = true, default-features = false }
+radroots_core = { workspace = true, default-features = false }
radroots_event = { workspace = true, default-features = false }
radroots_event_codec = { workspace = true, default-features = false }
-radroots_geocoder = { workspace = true, optional = true }
-radroots_outbox = { workspace = true, optional = true, default-features = false }
-radroots_protocol = { workspace = true, optional = true, default-features = false }
-radroots_protected_store = { workspace = true, optional = true, default-features = false }
-radroots_transport = { workspace = true, optional = true, default-features = false }
-radroots_transport_nostr = { workspace = true, optional = true, default-features = false }
-radroots_transport_reticulum = { workspace = true, optional = true, default-features = false }
-radroots_runtime_paths = { workspace = true, optional = true, default-features = false }
-radroots_secret_vault = { workspace = true, optional = true, default-features = false }
-radroots_secrets = { workspace = true, optional = true, default-features = false }
+radroots_identity = { workspace = true, default-features = false }
+radroots_protocol = { workspace = true, default-features = false }
radroots_signing = { workspace = true, default-features = false }
+radroots_storage = { workspace = true, default-features = false }
radroots_trade = { workspace = true, default-features = false }
-radroots_identity = { workspace = true, default-features = false }
+radroots_transport = { workspace = true, default-features = false }
+
+radroots_geonames = { workspace = true, optional = true, default-features = false }
radroots_nostr = { workspace = true, optional = true, default-features = false }
-radroots_nostr_connect = { workspace = true, optional = true }
-radroots_nostr_signer = { workspace = true, optional = true, default-features = false }
+radroots_nostr_connect = { workspace = true, optional = true, default-features = false }
+radroots_secrets = { workspace = true, optional = true, default-features = false }
+radroots_storage_sqlite = { workspace = true, optional = true, default-features = false }
+radroots_sync = { workspace = true, optional = true, default-features = false }
+radroots_transport_nostr = { workspace = true, optional = true, default-features = false }
+
reqwest = { workspace = true, optional = true, default-features = false, features = [
"json",
"rustls-tls",
] }
-hex = { workspace = true, optional = true }
-nostr = { workspace = true, optional = true }
-serde = { workspace = true, optional = true, default-features = false, features = [
- "derive",
- "alloc",
-] }
-serde_json = { workspace = true, optional = true, default-features = false, features = [
- "alloc",
-] }
-sha2 = { workspace = true, optional = true }
-sqlx = { version = "0.9.0", optional = true, default-features = false, features = [
- "runtime-tokio",
- "sqlite-bundled",
-] }
-tokio = { workspace = true, optional = true, features = ["macros", "rt", "sync", "time"] }
-uuid = { workspace = true, optional = true }
-
-[[example]]
-name = "sdk_v1_listing_prepare"
-path = "examples/sdk_v1_listing_prepare.rs"
-required-features = ["runtime"]
-
-[[example]]
-name = "sdk_v1_knowledge_prepare"
-path = "examples/sdk_v1_knowledge_prepare.rs"
-required-features = ["knowledge"]
-
-[[example]]
-name = "sdk_v1_local_enqueue_and_mock_sync"
-path = "examples/sdk_v1_local_enqueue_and_mock_sync.rs"
-required-features = ["runtime", "signer-adapters"]
-
-[[example]]
-name = "sdk_v1_myc_nip46_signer_setup"
-path = "examples/sdk_v1_myc_nip46_signer_setup.rs"
-required-features = ["runtime", "signer-adapters"]
-[[example]]
-name = "runtime_local"
-path = "examples/runtime_local.rs"
-required-features = ["local-runtime"]
-
-[dev-dependencies]
-futures = { workspace = true }
-nostr = { workspace = true }
-radroots_core = { workspace = true, default-features = false, features = [
- "std",
-] }
-radroots_signing = { workspace = true, default-features = false, features = ["std"] }
-radroots_replica_store = { workspace = true, default-features = false, features = [
- "native",
-] }
-radroots_replica_schema = { workspace = true }
-radroots_replica_sync = { workspace = true, features = ["std", "legacy-ingest"] }
-radroots_sql_core = { workspace = true, features = ["native"] }
-radroots_nostr = { workspace = true, default-features = false, features = [
- "std",
- "events",
- "signing",
-] }
-tempfile = { workspace = true }
-tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
-tokio-tungstenite = "0.26.2"
+[[test]]
+name = "package_boundary"
+path = "tests/package_boundary.rs"
[lints]
workspace = true
diff --git a/crates/sdk/src/capability.rs b/crates/sdk/src/capability.rs
@@ -0,0 +1 @@
+//! Client capability reporting.
diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs
@@ -0,0 +1 @@
+//! Client construction and lifecycle.
diff --git a/crates/sdk/src/diagnostics.rs b/crates/sdk/src/diagnostics.rs
@@ -0,0 +1 @@
+//! Redacted client diagnostics.
diff --git a/crates/sdk/src/error.rs b/crates/sdk/src/error.rs
@@ -1,1349 +1 @@
-#[cfg(feature = "runtime")]
-use std::{fmt, path::PathBuf};
-
-#[cfg(feature = "runtime")]
-use crate::privacy::{PrivacyPreflightStatus, ProductSensitivityField};
-#[cfg(feature = "runtime")]
-use crate::transport::ReticulumBehavior;
-#[cfg(feature = "runtime")]
-use radroots_event::trade::validation::OperationalListingValidationError;
-#[cfg(feature = "runtime")]
-use serde_json::{Value, json};
-
-#[cfg(feature = "runtime")]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case")]
-#[non_exhaustive]
-pub enum RadrootsSdkErrorClass {
- Authorization,
- Clock,
- Configuration,
- Request,
- Storage,
- Transport,
- Unsupported,
-}
-
-#[cfg(feature = "runtime")]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case")]
-#[non_exhaustive]
-pub enum RadrootsSdkRecoveryAction {
- InspectLocalStores,
- InspectGeoNamesAsset,
- RetryOperationWithSameIdempotencyKey,
- ConfigureTransportTargets,
- ConfigureGeoNamesCache,
- ConfigureSigner,
- FixRequest,
- SelectAuthorizedActor,
- CompleteSignerAuthentication,
- RetryAfterTransportFailure,
- RetryGeoNamesDownload,
- EnableRequiredFeature,
-}
-
-#[cfg(feature = "runtime")]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case")]
-#[non_exhaustive]
-pub enum RadrootsSdkGeoNamesErrorKind {
- Configuration,
- Download,
- Cache,
- Integrity,
- Schema,
- Lookup,
-}
-
-#[cfg(feature = "runtime")]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case")]
-#[non_exhaustive]
-pub enum RadrootsSdkListingValidationErrorKind {
- InvalidKind,
- InvalidProfile,
- MissingListingId,
- ListingEventNotFound,
- ListingEventFetchFailed,
- ParseError,
- InvalidSeller,
- MissingFarmProfile,
- MissingFarmRecord,
- MissingTitle,
- MissingDescription,
- MissingProductType,
- MissingBins,
- MissingPrimaryBin,
- InvalidBin,
- MissingPrice,
- InvalidPrice,
- MissingInventory,
- InvalidInventory,
- MissingAvailability,
- MissingLocation,
- MissingLocationLocality,
- MissingLocationGeohash,
- InvalidLocationGeohash,
- MissingDeliveryMethod,
-}
-
-#[cfg(feature = "runtime")]
-impl From<&OperationalListingValidationError> for RadrootsSdkListingValidationErrorKind {
- fn from(error: &OperationalListingValidationError) -> Self {
- match error {
- OperationalListingValidationError::InvalidKind { .. } => Self::InvalidKind,
- OperationalListingValidationError::InvalidProfile => Self::InvalidProfile,
- OperationalListingValidationError::MissingListingId => Self::MissingListingId,
- OperationalListingValidationError::ListingEventNotFound { .. } => {
- Self::ListingEventNotFound
- }
- OperationalListingValidationError::ListingEventFetchFailed { .. } => {
- Self::ListingEventFetchFailed
- }
- OperationalListingValidationError::ParseError { .. } => Self::ParseError,
- OperationalListingValidationError::InvalidSeller => Self::InvalidSeller,
- OperationalListingValidationError::MissingFarmProfile => Self::MissingFarmProfile,
- OperationalListingValidationError::MissingFarmRecord => Self::MissingFarmRecord,
- OperationalListingValidationError::MissingTitle => Self::MissingTitle,
- OperationalListingValidationError::MissingDescription => Self::MissingDescription,
- OperationalListingValidationError::MissingProductType => Self::MissingProductType,
- OperationalListingValidationError::MissingBins => Self::MissingBins,
- OperationalListingValidationError::MissingPrimaryBin => Self::MissingPrimaryBin,
- OperationalListingValidationError::InvalidBin => Self::InvalidBin,
- OperationalListingValidationError::MissingPrice => Self::MissingPrice,
- OperationalListingValidationError::InvalidPrice => Self::InvalidPrice,
- OperationalListingValidationError::MissingInventory => Self::MissingInventory,
- OperationalListingValidationError::InvalidInventory => Self::InvalidInventory,
- OperationalListingValidationError::MissingAvailability => Self::MissingAvailability,
- OperationalListingValidationError::MissingLocation => Self::MissingLocation,
- OperationalListingValidationError::MissingLocationLocality => {
- Self::MissingLocationLocality
- }
- OperationalListingValidationError::MissingLocationGeohash => {
- Self::MissingLocationGeohash
- }
- OperationalListingValidationError::InvalidLocationGeohash => {
- Self::InvalidLocationGeohash
- }
- OperationalListingValidationError::MissingDeliveryMethod => Self::MissingDeliveryMethod,
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case")]
-#[non_exhaustive]
-pub enum RadrootsSdkTradeErrorKind {
- InvalidEnvelope,
- InvalidCommandBody,
- PrivateArtifactMissing,
- PrivateArtifactCommitmentMismatch,
- PrivateArtifactAcknowledgementMissing,
- TradeNotFound,
- QueryLimitInvalid,
- CursorInvalid,
-}
-
-#[cfg(feature = "runtime")]
-impl RadrootsSdkTradeErrorKind {
- pub fn code(self) -> &'static str {
- match self {
- Self::InvalidEnvelope => "trade_invalid_envelope",
- Self::InvalidCommandBody => "trade_invalid_command_body",
- Self::PrivateArtifactMissing => "trade_private_artifact_missing",
- Self::PrivateArtifactCommitmentMismatch => "trade_private_artifact_commitment_mismatch",
- Self::PrivateArtifactAcknowledgementMissing => {
- "trade_private_artifact_acknowledgement_missing"
- }
- Self::TradeNotFound => "trade_not_found",
- Self::QueryLimitInvalid => "trade_query_limit_invalid",
- Self::CursorInvalid => "trade_cursor_invalid",
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-const RECOVERY_INSPECT_LOCAL_STORES: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::InspectLocalStores];
-#[cfg(feature = "runtime")]
-const RECOVERY_INSPECT_GEONAMES_ASSET: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::InspectGeoNamesAsset];
-#[cfg(feature = "runtime")]
-const RECOVERY_RETRY_OPERATION_WITH_SAME_IDEMPOTENCY_KEY: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::RetryOperationWithSameIdempotencyKey];
-#[cfg(feature = "runtime")]
-const RECOVERY_CONFIGURE_TRANSPORT_TARGETS: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::ConfigureTransportTargets];
-#[cfg(feature = "runtime")]
-const RECOVERY_CONFIGURE_GEONAMES_CACHE: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::ConfigureGeoNamesCache];
-#[cfg(feature = "runtime")]
-const RECOVERY_CONFIGURE_SIGNER: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::ConfigureSigner];
-#[cfg(feature = "runtime")]
-const RECOVERY_FIX_REQUEST: &[RadrootsSdkRecoveryAction] = &[RadrootsSdkRecoveryAction::FixRequest];
-#[cfg(feature = "runtime")]
-const RECOVERY_SELECT_AUTHORIZED_ACTOR: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::SelectAuthorizedActor];
-#[cfg(feature = "runtime")]
-const RECOVERY_COMPLETE_SIGNER_AUTHENTICATION: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::CompleteSignerAuthentication];
-#[cfg(feature = "runtime")]
-const RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::RetryAfterTransportFailure];
-#[cfg(feature = "runtime")]
-const RECOVERY_RETRY_GEONAMES_DOWNLOAD: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::RetryGeoNamesDownload];
-#[cfg(feature = "runtime")]
-const RECOVERY_ENABLE_REQUIRED_FEATURE: &[RadrootsSdkRecoveryAction] =
- &[RadrootsSdkRecoveryAction::EnableRequiredFeature];
-
-#[cfg(feature = "runtime")]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
-pub struct RadrootsSdkErrorCatalogEntry {
- pub code: &'static str,
- pub class: RadrootsSdkErrorClass,
- pub retryable: bool,
- pub recovery_actions: &'static [RadrootsSdkRecoveryAction],
-}
-
-#[cfg(feature = "runtime")]
-impl RadrootsSdkErrorCatalogEntry {
- const fn new(
- code: &'static str,
- class: RadrootsSdkErrorClass,
- retryable: bool,
- recovery_actions: &'static [RadrootsSdkRecoveryAction],
- ) -> Self {
- Self {
- code,
- class,
- retryable,
- recovery_actions,
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-pub const RADROOTS_SDK_ERROR_CATALOG: &[RadrootsSdkErrorCatalogEntry] = &[
- RadrootsSdkErrorCatalogEntry::new(
- "io",
- RadrootsSdkErrorClass::Storage,
- true,
- RECOVERY_INSPECT_LOCAL_STORES,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "clock_before_unix_epoch",
- RadrootsSdkErrorClass::Clock,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "timestamp_out_of_range",
- RadrootsSdkErrorClass::Clock,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "unauthorized_actor",
- RadrootsSdkErrorClass::Authorization,
- false,
- RECOVERY_SELECT_AUTHORIZED_ACTOR,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "signer_pubkey_mismatch",
- RadrootsSdkErrorClass::Authorization,
- false,
- RECOVERY_SELECT_AUTHORIZED_ACTOR,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "signer_unavailable",
- RadrootsSdkErrorClass::Configuration,
- false,
- RECOVERY_CONFIGURE_SIGNER,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "signer_request_rejected",
- RadrootsSdkErrorClass::Authorization,
- false,
- RECOVERY_SELECT_AUTHORIZED_ACTOR,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "signer_request_timed_out",
- RadrootsSdkErrorClass::Transport,
- true,
- RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "signer_auth_challenge_pending",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_COMPLETE_SIGNER_AUTHENTICATION,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "signer_transport",
- RadrootsSdkErrorClass::Transport,
- true,
- RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "signer_protocol",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "signer_returned_event_drift",
- RadrootsSdkErrorClass::Authorization,
- false,
- RECOVERY_CONFIGURE_SIGNER,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "empty_transport_targets",
- RadrootsSdkErrorClass::Configuration,
- false,
- RECOVERY_CONFIGURE_TRANSPORT_TARGETS,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "transport_target_limit_exceeded",
- RadrootsSdkErrorClass::Configuration,
- false,
- RECOVERY_CONFIGURE_TRANSPORT_TARGETS,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "invalid_relay_url",
- RadrootsSdkErrorClass::Configuration,
- false,
- RECOVERY_CONFIGURE_TRANSPORT_TARGETS,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "idempotency_conflict",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_RETRY_OPERATION_WITH_SAME_IDEMPOTENCY_KEY,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "trade_invalid_envelope",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "trade_invalid_command_body",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "trade_private_artifact_missing",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "trade_private_artifact_commitment_mismatch",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "trade_private_artifact_acknowledgement_missing",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "trade_not_found",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "trade_query_limit_invalid",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "trade_cursor_invalid",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "privacy_preflight",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "product_sync_unsupported",
- RadrootsSdkErrorClass::Unsupported,
- false,
- RECOVERY_ENABLE_REQUIRED_FEATURE,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "reticulum_transport_unavailable",
- RadrootsSdkErrorClass::Unsupported,
- false,
- RECOVERY_CONFIGURE_TRANSPORT_TARGETS,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "reticulum_transport_deferred",
- RadrootsSdkErrorClass::Unsupported,
- false,
- RECOVERY_CONFIGURE_TRANSPORT_TARGETS,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "product_sync_transport_setup_failure",
- RadrootsSdkErrorClass::Transport,
- true,
- RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "authority",
- RadrootsSdkErrorClass::Authorization,
- false,
- RECOVERY_SELECT_AUTHORIZED_ACTOR,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "event_store",
- RadrootsSdkErrorClass::Storage,
- true,
- RECOVERY_INSPECT_LOCAL_STORES,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "invalid_request",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "unsupported_profile_schema",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_INSPECT_LOCAL_STORES,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "listing_edit",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "listing_validation",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "listing_mutation",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "outbox",
- RadrootsSdkErrorClass::Storage,
- true,
- RECOVERY_INSPECT_LOCAL_STORES,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "private_store",
- RadrootsSdkErrorClass::Storage,
- true,
- RECOVERY_INSPECT_LOCAL_STORES,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "studio_store",
- RadrootsSdkErrorClass::Storage,
- true,
- RECOVERY_INSPECT_LOCAL_STORES,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "geonames_configuration",
- RadrootsSdkErrorClass::Configuration,
- false,
- RECOVERY_CONFIGURE_GEONAMES_CACHE,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "geonames_download",
- RadrootsSdkErrorClass::Transport,
- true,
- RECOVERY_RETRY_GEONAMES_DOWNLOAD,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "geonames_cache",
- RadrootsSdkErrorClass::Storage,
- true,
- RECOVERY_INSPECT_GEONAMES_ASSET,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "geonames_integrity",
- RadrootsSdkErrorClass::Storage,
- false,
- RECOVERY_INSPECT_GEONAMES_ASSET,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "geonames_schema",
- RadrootsSdkErrorClass::Storage,
- false,
- RECOVERY_INSPECT_GEONAMES_ASSET,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "geonames_lookup",
- RadrootsSdkErrorClass::Request,
- false,
- RECOVERY_FIX_REQUEST,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "transport",
- RadrootsSdkErrorClass::Transport,
- true,
- RECOVERY_RETRY_AFTER_TRANSPORT_FAILURE,
- ),
- RadrootsSdkErrorCatalogEntry::new(
- "projection",
- RadrootsSdkErrorClass::Storage,
- true,
- RECOVERY_INSPECT_LOCAL_STORES,
- ),
-];
-
-#[cfg(feature = "runtime")]
-pub fn radroots_sdk_error_catalog() -> &'static [RadrootsSdkErrorCatalogEntry] {
- RADROOTS_SDK_ERROR_CATALOG
-}
-
-#[cfg(feature = "runtime")]
-#[derive(Debug)]
-#[non_exhaustive]
-pub enum RadrootsSdkError {
- Io {
- path: PathBuf,
- message: String,
- },
- ClockBeforeUnixEpoch,
- TimestampOutOfRange {
- value: u64,
- },
- UnauthorizedActor {
- operation: String,
- reason: String,
- },
- SignerPubkeyMismatch {
- operation: String,
- expected_pubkey_prefix: String,
- signer_pubkey_prefix: String,
- },
- SignerUnavailable {
- mode: String,
- reason: String,
- },
- SignerRequestRejected {
- mode: String,
- reason: String,
- },
- SignerRequestTimedOut {
- mode: String,
- },
- SignerAuthChallengePending {
- mode: String,
- auth_url: Option<String>,
- },
- SignerTransport {
- mode: String,
- reason: String,
- },
- SignerProtocol {
- mode: String,
- reason: String,
- },
- SignerReturnedEventDrift {
- operation: String,
- reason: String,
- },
- EmptyTransportTargets {
- operation: String,
- },
- TransportTargetLimitExceeded {
- max: usize,
- actual: usize,
- },
- InvalidRelayUrl {
- url: String,
- reason: String,
- },
- IdempotencyConflict {
- operation_kind: String,
- expected_pubkey_prefix: String,
- existing_digest_prefix: String,
- new_digest_prefix: String,
- },
- Trade {
- kind: RadrootsSdkTradeErrorKind,
- operation: String,
- message: String,
- },
- PrivacyPreflight {
- operation: String,
- status: PrivacyPreflightStatus,
- fields: Vec<ProductSensitivityField>,
- },
- ProductSyncUnsupported {
- operation: &'static str,
- required_feature: &'static str,
- },
- ReticulumTransportUnavailable {
- operation: String,
- endpoint_uri: String,
- behavior: ReticulumBehavior,
- },
- ProductSyncTransportSetupFailure {
- message: String,
- },
- Authority {
- message: String,
- },
- EventStore {
- message: String,
- },
- InvalidRequest {
- message: String,
- },
- UnsupportedProfileSchema {
- path: PathBuf,
- message: String,
- },
- ListingEdit {
- message: String,
- },
- ListingValidation {
- kind: RadrootsSdkListingValidationErrorKind,
- message: String,
- },
- ListingMutation {
- message: String,
- },
- Outbox {
- message: String,
- },
- PrivateStore {
- message: String,
- },
- StudioStore {
- message: String,
- },
- GeoNames {
- kind: RadrootsSdkGeoNamesErrorKind,
- message: String,
- },
- Transport {
- message: String,
- },
- Projection {
- message: String,
- },
-}
-
-#[cfg(feature = "runtime")]
-impl RadrootsSdkError {
- pub fn code(&self) -> &'static str {
- match self {
- Self::Io { .. } => "io",
- Self::ClockBeforeUnixEpoch => "clock_before_unix_epoch",
- Self::TimestampOutOfRange { .. } => "timestamp_out_of_range",
- Self::UnauthorizedActor { .. } => "unauthorized_actor",
- Self::SignerPubkeyMismatch { .. } => "signer_pubkey_mismatch",
- Self::SignerUnavailable { .. } => "signer_unavailable",
- Self::SignerRequestRejected { .. } => "signer_request_rejected",
- Self::SignerRequestTimedOut { .. } => "signer_request_timed_out",
- Self::SignerAuthChallengePending { .. } => "signer_auth_challenge_pending",
- Self::SignerTransport { .. } => "signer_transport",
- Self::SignerProtocol { .. } => "signer_protocol",
- Self::SignerReturnedEventDrift { .. } => "signer_returned_event_drift",
- Self::EmptyTransportTargets { .. } => "empty_transport_targets",
- Self::TransportTargetLimitExceeded { .. } => "transport_target_limit_exceeded",
- Self::InvalidRelayUrl { .. } => "invalid_relay_url",
- Self::IdempotencyConflict { .. } => "idempotency_conflict",
- Self::Trade { kind, .. } => kind.code(),
- Self::PrivacyPreflight { .. } => "privacy_preflight",
- Self::ProductSyncUnsupported { .. } => "product_sync_unsupported",
- Self::ReticulumTransportUnavailable { behavior, .. } => match behavior {
- ReticulumBehavior::RejectDeliveryAttempts => "reticulum_transport_unavailable",
- ReticulumBehavior::DeferDeliveryPlans => "reticulum_transport_deferred",
- },
- Self::ProductSyncTransportSetupFailure { .. } => "product_sync_transport_setup_failure",
- Self::Authority { .. } => "authority",
- Self::EventStore { .. } => "event_store",
- Self::InvalidRequest { .. } => "invalid_request",
- Self::UnsupportedProfileSchema { .. } => "unsupported_profile_schema",
- Self::ListingEdit { .. } => "listing_edit",
- Self::ListingValidation { .. } => "listing_validation",
- Self::ListingMutation { .. } => "listing_mutation",
- Self::Outbox { .. } => "outbox",
- Self::PrivateStore { .. } => "private_store",
- Self::StudioStore { .. } => "studio_store",
- Self::GeoNames { kind, .. } => match kind {
- RadrootsSdkGeoNamesErrorKind::Configuration => "geonames_configuration",
- RadrootsSdkGeoNamesErrorKind::Download => "geonames_download",
- RadrootsSdkGeoNamesErrorKind::Cache => "geonames_cache",
- RadrootsSdkGeoNamesErrorKind::Integrity => "geonames_integrity",
- RadrootsSdkGeoNamesErrorKind::Schema => "geonames_schema",
- RadrootsSdkGeoNamesErrorKind::Lookup => "geonames_lookup",
- },
- Self::Transport { .. } => "transport",
- Self::Projection { .. } => "projection",
- }
- }
-
- pub fn class(&self) -> RadrootsSdkErrorClass {
- match self {
- Self::Io { .. }
- | Self::EventStore { .. }
- | Self::Outbox { .. }
- | Self::PrivateStore { .. }
- | Self::StudioStore { .. }
- | Self::Projection { .. } => RadrootsSdkErrorClass::Storage,
- Self::GeoNames { kind, .. } => match kind {
- RadrootsSdkGeoNamesErrorKind::Configuration => RadrootsSdkErrorClass::Configuration,
- RadrootsSdkGeoNamesErrorKind::Download => RadrootsSdkErrorClass::Transport,
- RadrootsSdkGeoNamesErrorKind::Cache
- | RadrootsSdkGeoNamesErrorKind::Integrity
- | RadrootsSdkGeoNamesErrorKind::Schema => RadrootsSdkErrorClass::Storage,
- RadrootsSdkGeoNamesErrorKind::Lookup => RadrootsSdkErrorClass::Request,
- },
- Self::ClockBeforeUnixEpoch | Self::TimestampOutOfRange { .. } => {
- RadrootsSdkErrorClass::Clock
- }
- Self::UnauthorizedActor { .. }
- | Self::SignerPubkeyMismatch { .. }
- | Self::SignerRequestRejected { .. }
- | Self::SignerReturnedEventDrift { .. }
- | Self::Authority { .. } => RadrootsSdkErrorClass::Authorization,
- Self::SignerUnavailable { .. } => RadrootsSdkErrorClass::Configuration,
- Self::EmptyTransportTargets { .. }
- | Self::TransportTargetLimitExceeded { .. }
- | Self::InvalidRelayUrl { .. } => RadrootsSdkErrorClass::Configuration,
- Self::IdempotencyConflict { .. }
- | Self::Trade { .. }
- | Self::PrivacyPreflight { .. }
- | Self::SignerProtocol { .. }
- | Self::SignerAuthChallengePending { .. }
- | Self::InvalidRequest { .. }
- | Self::UnsupportedProfileSchema { .. }
- | Self::ListingEdit { .. }
- | Self::ListingValidation { .. }
- | Self::ListingMutation { .. } => RadrootsSdkErrorClass::Request,
- Self::ProductSyncUnsupported { .. } | Self::ReticulumTransportUnavailable { .. } => {
- RadrootsSdkErrorClass::Unsupported
- }
- Self::ProductSyncTransportSetupFailure { .. }
- | Self::Transport { .. }
- | Self::SignerRequestTimedOut { .. }
- | Self::SignerTransport { .. } => RadrootsSdkErrorClass::Transport,
- }
- }
-
- pub fn retryable(&self) -> bool {
- matches!(
- self,
- Self::Io { .. }
- | Self::ProductSyncTransportSetupFailure { .. }
- | Self::EventStore { .. }
- | Self::Outbox { .. }
- | Self::PrivateStore { .. }
- | Self::StudioStore { .. }
- | Self::GeoNames {
- kind: RadrootsSdkGeoNamesErrorKind::Cache
- | RadrootsSdkGeoNamesErrorKind::Download,
- ..
- }
- | Self::Transport { .. }
- | Self::SignerRequestTimedOut { .. }
- | Self::SignerTransport { .. }
- | Self::Projection { .. }
- )
- }
-
- pub fn recovery_actions(&self) -> Vec<RadrootsSdkRecoveryAction> {
- match self {
- Self::Io { .. }
- | Self::EventStore { .. }
- | Self::Outbox { .. }
- | Self::PrivateStore { .. }
- | Self::StudioStore { .. }
- | Self::Projection { .. } => vec![RadrootsSdkRecoveryAction::InspectLocalStores],
- Self::GeoNames { kind, .. } => match kind {
- RadrootsSdkGeoNamesErrorKind::Configuration => {
- vec![RadrootsSdkRecoveryAction::ConfigureGeoNamesCache]
- }
- RadrootsSdkGeoNamesErrorKind::Download => {
- vec![RadrootsSdkRecoveryAction::RetryGeoNamesDownload]
- }
- RadrootsSdkGeoNamesErrorKind::Cache
- | RadrootsSdkGeoNamesErrorKind::Integrity
- | RadrootsSdkGeoNamesErrorKind::Schema => {
- vec![RadrootsSdkRecoveryAction::InspectGeoNamesAsset]
- }
- RadrootsSdkGeoNamesErrorKind::Lookup => vec![RadrootsSdkRecoveryAction::FixRequest],
- },
- Self::UnauthorizedActor { .. }
- | Self::SignerPubkeyMismatch { .. }
- | Self::SignerRequestRejected { .. }
- | Self::Authority { .. } => vec![RadrootsSdkRecoveryAction::SelectAuthorizedActor],
- Self::SignerUnavailable { .. } | Self::SignerReturnedEventDrift { .. } => {
- vec![RadrootsSdkRecoveryAction::ConfigureSigner]
- }
- Self::EmptyTransportTargets { .. }
- | Self::TransportTargetLimitExceeded { .. }
- | Self::InvalidRelayUrl { .. } => {
- vec![RadrootsSdkRecoveryAction::ConfigureTransportTargets]
- }
- Self::IdempotencyConflict { .. } => {
- vec![RadrootsSdkRecoveryAction::RetryOperationWithSameIdempotencyKey]
- }
- Self::Trade { .. } => vec![RadrootsSdkRecoveryAction::FixRequest],
- Self::PrivacyPreflight { .. } => vec![RadrootsSdkRecoveryAction::FixRequest],
- Self::UnsupportedProfileSchema { .. } => {
- vec![RadrootsSdkRecoveryAction::InspectLocalStores]
- }
- Self::ProductSyncUnsupported { .. } => {
- vec![RadrootsSdkRecoveryAction::EnableRequiredFeature]
- }
- Self::ReticulumTransportUnavailable { .. } => {
- vec![RadrootsSdkRecoveryAction::ConfigureTransportTargets]
- }
- Self::ProductSyncTransportSetupFailure { .. } | Self::Transport { .. } => {
- vec![RadrootsSdkRecoveryAction::RetryAfterTransportFailure]
- }
- Self::SignerRequestTimedOut { .. } | Self::SignerTransport { .. } => {
- vec![RadrootsSdkRecoveryAction::RetryAfterTransportFailure]
- }
- Self::SignerAuthChallengePending { .. } => {
- vec![RadrootsSdkRecoveryAction::CompleteSignerAuthentication]
- }
- Self::ClockBeforeUnixEpoch
- | Self::TimestampOutOfRange { .. }
- | Self::SignerProtocol { .. }
- | Self::InvalidRequest { .. }
- | Self::ListingEdit { .. }
- | Self::ListingValidation { .. }
- | Self::ListingMutation { .. } => vec![RadrootsSdkRecoveryAction::FixRequest],
- }
- }
-
- pub fn detail_json(&self) -> Value {
- let detail = match self {
- Self::Io { path, message } => {
- json!({ "path": path.display().to_string(), "message": message })
- }
- Self::ClockBeforeUnixEpoch => json!({}),
- Self::TimestampOutOfRange { value } => json!({ "value": value }),
- Self::UnauthorizedActor { operation, reason } => {
- json!({ "operation": operation, "reason": reason })
- }
- Self::SignerPubkeyMismatch {
- operation,
- expected_pubkey_prefix,
- signer_pubkey_prefix,
- } => json!({
- "operation": operation,
- "expected_pubkey_prefix": expected_pubkey_prefix,
- "signer_pubkey_prefix": signer_pubkey_prefix
- }),
- Self::SignerUnavailable { mode, reason }
- | Self::SignerRequestRejected { mode, reason }
- | Self::SignerTransport { mode, reason }
- | Self::SignerProtocol { mode, reason } => {
- json!({ "mode": mode, "reason": reason })
- }
- Self::SignerRequestTimedOut { mode } => json!({ "mode": mode }),
- Self::SignerAuthChallengePending { mode, auth_url } => {
- json!({ "mode": mode, "auth_url": auth_url })
- }
- Self::SignerReturnedEventDrift { operation, reason } => {
- json!({ "operation": operation, "reason": reason })
- }
- Self::EmptyTransportTargets { operation } => json!({ "operation": operation }),
- Self::TransportTargetLimitExceeded { max, actual } => {
- json!({ "max": max, "actual": actual })
- }
- Self::InvalidRelayUrl { url, reason } => json!({ "url": url, "reason": reason }),
- Self::IdempotencyConflict {
- operation_kind,
- expected_pubkey_prefix,
- existing_digest_prefix,
- new_digest_prefix,
- } => json!({
- "operation_kind": operation_kind,
- "expected_pubkey_prefix": expected_pubkey_prefix,
- "existing_digest_prefix": existing_digest_prefix,
- "new_digest_prefix": new_digest_prefix
- }),
- Self::Trade {
- kind,
- operation,
- message,
- } => json!({
- "kind": kind,
- "operation": operation,
- "message": message
- }),
- Self::PrivacyPreflight {
- operation,
- status,
- fields,
- } => json!({
- "operation": operation,
- "status": status,
- "fields": fields
- }),
- Self::ProductSyncUnsupported {
- operation,
- required_feature,
- } => json!({ "operation": operation, "required_feature": required_feature }),
- Self::ReticulumTransportUnavailable {
- operation,
- endpoint_uri,
- behavior,
- } => json!({
- "operation": operation,
- "endpoint_uri": endpoint_uri,
- "behavior": behavior.as_str()
- }),
- Self::ListingValidation { kind, message } => {
- json!({ "kind": kind, "message": message })
- }
- Self::ProductSyncTransportSetupFailure { message }
- | Self::Authority { message }
- | Self::EventStore { message }
- | Self::InvalidRequest { message }
- | Self::ListingEdit { message }
- | Self::ListingMutation { message }
- | Self::Outbox { message }
- | Self::PrivateStore { message }
- | Self::StudioStore { message }
- | Self::Transport { message }
- | Self::Projection { message } => json!({ "message": message }),
- Self::UnsupportedProfileSchema { path, message } => {
- json!({ "path": path.display().to_string(), "message": message })
- }
- Self::GeoNames { kind, message } => json!({ "kind": kind, "message": message }),
- };
- json!({
- "code": self.code(),
- "class": self.class(),
- "retryable": self.retryable(),
- "message": self.to_string(),
- "recovery_actions": self.recovery_actions(),
- "detail": detail
- })
- }
-
- pub(crate) fn empty_transport_targets(operation: impl Into<String>) -> Self {
- Self::EmptyTransportTargets {
- operation: operation.into(),
- }
- }
-
- pub(crate) fn transport_target_limit_exceeded(max: usize, actual: usize) -> Self {
- Self::TransportTargetLimitExceeded { max, actual }
- }
-
- pub(crate) fn invalid_relay_url(url: impl Into<String>, reason: impl Into<String>) -> Self {
- Self::InvalidRelayUrl {
- url: redacted_relay_url(url.into()),
- reason: reason.into(),
- }
- }
-
- pub(crate) fn missing_geonames_config() -> Self {
- Self::GeoNames {
- kind: RadrootsSdkGeoNamesErrorKind::Configuration,
- message: "GeoNames cache root is not configured".to_owned(),
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-impl fmt::Display for RadrootsSdkError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Io { path, message } => {
- write!(f, "sdk storage path `{}` failed: {message}", path.display())
- }
- Self::ClockBeforeUnixEpoch => f.write_str("sdk clock is before the Unix epoch"),
- Self::TimestampOutOfRange { value } => {
- write!(
- f,
- "sdk timestamp {value} exceeds Nostr u32 created_at range"
- )
- }
- Self::UnauthorizedActor { operation, reason } => {
- write!(f, "sdk unauthorized actor for {operation}: {reason}")
- }
- Self::SignerPubkeyMismatch {
- operation,
- expected_pubkey_prefix,
- signer_pubkey_prefix,
- } => write!(
- f,
- "sdk signer pubkey mismatch for {operation}: expected_pubkey_prefix={expected_pubkey_prefix}, signer_pubkey_prefix={signer_pubkey_prefix}"
- ),
- Self::SignerUnavailable { mode, reason } => {
- write!(f, "sdk {mode} signer unavailable: {reason}")
- }
- Self::SignerRequestRejected { mode, reason } => {
- write!(f, "sdk {mode} signer rejected request: {reason}")
- }
- Self::SignerRequestTimedOut { mode } => {
- write!(f, "sdk {mode} signer request timed out")
- }
- Self::SignerAuthChallengePending { mode, auth_url } => match auth_url {
- Some(auth_url) => {
- write!(f, "sdk {mode} signer requires authentication at {auth_url}")
- }
- None => write!(f, "sdk {mode} signer requires authentication"),
- },
- Self::SignerTransport { mode, reason } => {
- write!(f, "sdk {mode} signer transport error: {reason}")
- }
- Self::SignerProtocol { mode, reason } => {
- write!(f, "sdk {mode} signer protocol error: {reason}")
- }
- Self::SignerReturnedEventDrift { operation, reason } => {
- write!(
- f,
- "sdk signer returned event drift for {operation}: {reason}"
- )
- }
- Self::EmptyTransportTargets { operation } => {
- write!(f, "sdk empty transport targets for {operation}")
- }
- Self::TransportTargetLimitExceeded { max, actual } => {
- write!(
- f,
- "sdk transport target limit exceeded: max={max}, actual={actual}"
- )
- }
- Self::InvalidRelayUrl { url, reason } => {
- write!(f, "sdk invalid relay URL `{url}`: {reason}")
- }
- Self::IdempotencyConflict {
- operation_kind,
- expected_pubkey_prefix,
- existing_digest_prefix,
- new_digest_prefix,
- } => write!(
- f,
- "sdk idempotency conflict for {operation_kind}: expected_pubkey_prefix={expected_pubkey_prefix}, existing_digest_prefix={existing_digest_prefix}, new_digest_prefix={new_digest_prefix}"
- ),
- Self::Trade {
- kind,
- operation,
- message,
- } => write!(f, "sdk trade error for {operation} ({kind:?}): {message}"),
- Self::PrivacyPreflight {
- operation,
- status,
- fields,
- } => write!(
- f,
- "sdk privacy preflight failed for {operation}: status={status:?}, field_count={}",
- fields.len()
- ),
- Self::ProductSyncUnsupported {
- operation,
- required_feature,
- } => write!(
- f,
- "sdk product sync operation {operation} requires feature `{required_feature}`"
- ),
- Self::ReticulumTransportUnavailable {
- operation,
- endpoint_uri,
- behavior,
- } => write!(
- f,
- "sdk product sync operation {operation} cannot deliver through Reticulum endpoint `{endpoint_uri}` with behavior `{}`",
- behavior.as_str()
- ),
- Self::ProductSyncTransportSetupFailure { message } => {
- write!(f, "sdk product sync transport setup failed: {message}")
- }
- Self::Authority { message } => write!(f, "sdk authority error: {message}"),
- Self::EventStore { message } => write!(f, "sdk event store error: {message}"),
- Self::InvalidRequest { message } => write!(f, "sdk invalid request: {message}"),
- Self::UnsupportedProfileSchema { path, message } => write!(
- f,
- "sdk unsupported profile schema at `{}`: {message}",
- path.display()
- ),
- Self::ListingEdit { message } => write!(f, "sdk listing edit error: {message}"),
- Self::ListingValidation { kind, message } => {
- write!(f, "sdk listing validation error ({kind:?}): {message}")
- }
- Self::ListingMutation { message } => {
- write!(f, "sdk listing mutation error: {message}")
- }
- Self::Outbox { message } => write!(f, "sdk outbox error: {message}"),
- Self::PrivateStore { message } => write!(f, "sdk private store error: {message}"),
- Self::StudioStore { message } => write!(f, "sdk studio store error: {message}"),
- Self::GeoNames { kind, message } => {
- write!(f, "sdk GeoNames {kind:?} error: {message}")
- }
- Self::Transport { message } => {
- write!(f, "sdk transport error: {message}")
- }
- Self::Projection { message } => write!(f, "sdk projection error: {message}"),
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-impl std::error::Error for RadrootsSdkError {}
-
-#[cfg(feature = "runtime")]
-impl From<radroots_signing::Error> for RadrootsSdkError {
- fn from(error: radroots_signing::Error) -> Self {
- use radroots_signing::error::Kind;
-
- match error.kind() {
- Kind::AuthorizationDenied => Self::UnauthorizedActor {
- operation: "event signing".to_owned(),
- reason: "actor or signer is not authorized for the frozen draft".to_owned(),
- },
- Kind::SignerCapabilityMissing => Self::SignerUnavailable {
- mode: "configured".to_owned(),
- reason: error.to_string(),
- },
- Kind::SignerUnavailable => Self::SignerUnavailable {
- mode: "configured".to_owned(),
- reason: error.to_string(),
- },
- Kind::SignerRejected => Self::SignerRequestRejected {
- mode: "configured".to_owned(),
- reason: error.to_string(),
- },
- Kind::SignerTimeout | Kind::DeadlineExceeded => Self::SignerRequestTimedOut {
- mode: "configured".to_owned(),
- },
- Kind::SignerCancelled => Self::SignerRequestRejected {
- mode: "configured".to_owned(),
- reason: error.to_string(),
- },
- Kind::SignerOutputInvalid => Self::SignerReturnedEventDrift {
- operation: "event signing".to_owned(),
- reason: error.to_string(),
- },
- Kind::InvalidArgument | Kind::InternalError => Self::Authority {
- message: error.to_string(),
- },
- _ => Self::Authority {
- message: error.to_string(),
- },
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-impl From<radroots_event_store::RadrootsEventStoreError> for RadrootsSdkError {
- fn from(error: radroots_event_store::RadrootsEventStoreError) -> Self {
- Self::EventStore {
- message: error.to_string(),
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-impl From<radroots_geocoder::GeocoderError> for RadrootsSdkError {
- fn from(error: radroots_geocoder::GeocoderError) -> Self {
- let kind = match &error {
- radroots_geocoder::GeocoderError::InvalidAssetUrl { .. }
- | radroots_geocoder::GeocoderError::InvalidAssetHost { .. } => {
- RadrootsSdkGeoNamesErrorKind::Configuration
- }
- radroots_geocoder::GeocoderError::AssetDownload { .. } => {
- RadrootsSdkGeoNamesErrorKind::Download
- }
- radroots_geocoder::GeocoderError::Io(_)
- | radroots_geocoder::GeocoderError::Sqlite(_)
- | radroots_geocoder::GeocoderError::SqliteConnectionLockUnavailable
- | radroots_geocoder::GeocoderError::AssetLockUnavailable { .. } => {
- RadrootsSdkGeoNamesErrorKind::Cache
- }
- radroots_geocoder::GeocoderError::InvalidAssetSchema { .. } => {
- RadrootsSdkGeoNamesErrorKind::Schema
- }
- radroots_geocoder::GeocoderError::InvalidAssetLength { .. }
- | radroots_geocoder::GeocoderError::InvalidAssetSha256 { .. }
- | radroots_geocoder::GeocoderError::InvalidAssetSqlite { .. }
- | radroots_geocoder::GeocoderError::InvalidAssetIntegrity { .. } => {
- RadrootsSdkGeoNamesErrorKind::Integrity
- }
- radroots_geocoder::GeocoderError::CountryCenterNotFound { .. } => {
- RadrootsSdkGeoNamesErrorKind::Lookup
- }
- };
- Self::GeoNames {
- kind,
- message: error.to_string(),
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-impl From<radroots_trade::operational_listing::RadrootsOperationalListingEditError>
- for RadrootsSdkError
-{
- fn from(
- error: radroots_trade::operational_listing::RadrootsOperationalListingEditError,
- ) -> Self {
- match error {
- radroots_trade::operational_listing::RadrootsOperationalListingEditError::InvalidModel(error) => Self::ListingValidation {
- kind: RadrootsSdkListingValidationErrorKind::from(&error),
- message: error.to_string(),
- },
- error => Self::ListingEdit {
- message: error.to_string(),
- },
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-impl From<radroots_trade::operational_listing::RadrootsOperationalListingMutationError>
- for RadrootsSdkError
-{
- fn from(
- error: radroots_trade::operational_listing::RadrootsOperationalListingMutationError,
- ) -> Self {
- Self::ListingMutation {
- message: error.to_string(),
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-impl From<radroots_outbox::RadrootsOutboxError> for RadrootsSdkError {
- fn from(error: radroots_outbox::RadrootsOutboxError) -> Self {
- match error {
- radroots_outbox::RadrootsOutboxError::EmptyDeliveryTargets => {
- Self::empty_transport_targets("outbox enqueue")
- }
- radroots_outbox::RadrootsOutboxError::IdempotencyConflict {
- operation_kind,
- expected_pubkey,
- existing_digest,
- new_digest,
- ..
- } => Self::IdempotencyConflict {
- operation_kind,
- expected_pubkey_prefix: redacted_prefix(expected_pubkey.as_str()),
- existing_digest_prefix: redacted_prefix(existing_digest.as_str()),
- new_digest_prefix: redacted_prefix(new_digest.as_str()),
- },
- error => Self::Outbox {
- message: error.to_string(),
- },
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-impl From<radroots_transport::RadrootsTransportError> for RadrootsSdkError {
- fn from(error: radroots_transport::RadrootsTransportError) -> Self {
- match error {
- radroots_transport::RadrootsTransportError::EmptyTargetSet => {
- Self::empty_transport_targets("transport target set")
- }
- error => Self::Transport {
- message: error.to_string(),
- },
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-impl From<radroots_transport_nostr::RadrootsRelayTransportError> for RadrootsSdkError {
- fn from(error: radroots_transport_nostr::RadrootsRelayTransportError) -> Self {
- match error {
- radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlParse {
- url,
- reason,
- } => Self::invalid_relay_url(url, reason),
- radroots_transport_nostr::RadrootsRelayTransportError::WsRequiresLocalhostPolicy {
- url,
- } => Self::invalid_relay_url(url, "ws relay URL requires localhost policy"),
- radroots_transport_nostr::RadrootsRelayTransportError::UnsupportedRelayScheme {
- url,
- scheme,
- } => Self::invalid_relay_url(url, format!("unsupported scheme `{scheme}`")),
- radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlUserinfo { url } => {
- Self::invalid_relay_url(url, "relay URL must not include userinfo")
- }
- radroots_transport_nostr::RadrootsRelayTransportError::EmptyRelayHost { url } => {
- Self::invalid_relay_url(url, "relay URL must include a host")
- }
- radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlQueryOrFragment {
- url,
- } => Self::invalid_relay_url(url, "relay URL must not include query or fragment"),
- radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlForbiddenDestination {
- url,
- reason,
- } => Self::invalid_relay_url(url, reason),
- radroots_transport_nostr::RadrootsRelayTransportError::RelayUrlResolvedForbiddenDestination {
- url,
- address,
- reason,
- } => Self::invalid_relay_url(
- url,
- format!("relay URL resolved to forbidden address `{address}`: {reason}"),
- ),
- radroots_transport_nostr::RadrootsRelayTransportError::EmptyTargetSet => {
- Self::empty_transport_targets("nostr relay publish")
- }
- #[cfg(feature = "runtime")]
- radroots_transport_nostr::RadrootsRelayTransportError::Outbox(error) => error.into(),
- error => Self::Transport {
- message: error.to_string(),
- },
- }
- }
-}
-
-#[cfg(feature = "runtime")]
-fn redacted_prefix(value: &str) -> String {
- value.chars().take(12).collect()
-}
-
-#[cfg(feature = "runtime")]
-fn redacted_relay_url(value: String) -> String {
- let redacted = redact_query_or_fragment(value.as_str());
- let Some((scheme, rest)) = redacted.split_once("://") else {
- return redacted;
- };
- let authority = rest.split('/').next().unwrap_or(rest);
- let Some((_, after_userinfo)) = authority.rsplit_once('@') else {
- return redacted;
- };
- let path = rest.strip_prefix(authority).unwrap_or_default();
- format!("{scheme}://<redacted>@{after_userinfo}{path}")
-}
-
-#[cfg(feature = "runtime")]
-fn redact_query_or_fragment(value: &str) -> String {
- let Some((index, marker)) = value.char_indices().find_map(|(index, character)| {
- matches!(character, '?' | '#').then_some((index, character))
- }) else {
- return value.to_owned();
- };
- format!("{}{}<redacted>", &value[..index], marker)
-}
-
-#[cfg(test)]
-#[path = "../tests/unit/error_tests.rs"]
-mod tests;
+//! SDK-owned error boundary.
diff --git a/crates/sdk/src/farm.rs b/crates/sdk/src/farm.rs
@@ -1,9 +1 @@
-pub use radroots_event::farm::*;
-
-pub use radroots_event_codec::encode::EventEncodeError;
-
-use radroots_event::wire::Nip01EventWireParts;
-
-pub fn build_draft(farm: &Farm) -> Result<Nip01EventWireParts, EventEncodeError> {
- radroots_event_codec::encode::farm::to_wire_parts(farm)
-}
+//! Farm product operations.
diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs
@@ -1,201 +1,18 @@
-#![cfg_attr(not(feature = "std"), no_std)]
-#![forbid(unsafe_code)]
+//! Host-neutral asynchronous client engine for Radroots.
+//!
+//! This checkpoint establishes the final package and module ownership
+//! boundary. Client behavior is introduced through the ordered SDK refactor.
-#[cfg(not(feature = "std"))]
-extern crate alloc;
+#![forbid(unsafe_code)]
-#[cfg(feature = "runtime")]
-mod actor_json;
-#[cfg(any(
- feature = "radrootsd-execution",
- feature = "transport-nostr-client",
- feature = "signer-adapters"
-))]
-pub mod adapters;
-#[cfg(feature = "runtime")]
-mod error;
-#[cfg(feature = "runtime")]
-mod farm;
-#[cfg(feature = "runtime")]
-mod farms_runtime;
-#[cfg(all(test, feature = "runtime", feature = "signer-adapters"))]
-#[path = "../tests/support/fixture_signer.rs"]
-pub(crate) mod fixture_signer;
-#[cfg(feature = "runtime")]
-mod geonames;
-#[cfg(feature = "runtime")]
-mod idempotency;
-#[cfg(feature = "identity-models")]
-pub mod identity;
-#[cfg(feature = "knowledge")]
-pub mod knowledge;
-#[cfg(feature = "runtime")]
-mod listings_runtime;
-#[cfg(feature = "runtime")]
-mod privacy;
-#[cfg(feature = "runtime")]
-mod private_store;
-#[cfg(feature = "runtime")]
-mod product_clients;
-#[cfg(feature = "runtime")]
-mod runtime;
-#[cfg(all(test, feature = "runtime"))]
-#[path = "../tests/support/serializer_failure.rs"]
-pub(crate) mod serializer_failure;
-#[cfg(all(feature = "runtime", feature = "signer-adapters"))]
-mod signer_provider;
-#[cfg(feature = "runtime")]
-mod studio_store;
-#[cfg(feature = "runtime")]
-mod sync_runtime;
-#[cfg(feature = "runtime")]
-mod trade_runtime;
-#[cfg(feature = "runtime")]
+pub mod capability;
+pub mod client;
+pub mod diagnostics;
+pub mod error;
+pub mod farm;
+pub mod listing;
+pub mod signing;
+pub mod storage;
+pub mod sync;
+pub mod trade;
pub mod transport;
-#[cfg(feature = "runtime")]
-mod workflow_runtime;
-
-#[cfg(feature = "runtime")]
-pub use crate::error::{
- RADROOTS_SDK_ERROR_CATALOG, RadrootsSdkError, RadrootsSdkErrorCatalogEntry,
- RadrootsSdkErrorClass, RadrootsSdkGeoNamesErrorKind, RadrootsSdkListingValidationErrorKind,
- RadrootsSdkRecoveryAction, RadrootsSdkTradeErrorKind, radroots_sdk_error_catalog,
-};
-#[cfg(feature = "runtime")]
-pub use crate::farms_runtime::{
- FARM_PUBLISH_OPERATION_KIND, FarmEnqueuePublishRequest, FarmEnqueueReceipt,
- FarmPreparePublishRequest, FarmPrivateLocationClearReceipt, FarmPrivateLocationClearRequest,
- FarmPrivateLocationInput, FarmPrivateLocationLookupCandidate, FarmPrivateLocationLookupReceipt,
- FarmPrivateLocationReceipt, FarmPrivateLocationSetRequest, FarmPrivateLocationSetResult,
- FarmPrivateLocationUpsertRequest, FarmPublishPlan, SdkExactLocation, SdkPublicLocality,
-};
-#[cfg(feature = "runtime")]
-pub use crate::geonames::{
- GEONAMES_1_0_ASSET, GEONAMES_ASSET_BYTE_SIZE, GEONAMES_ASSET_FILE_NAME, GEONAMES_ASSET_HOST,
- GEONAMES_ASSET_SHA256, GEONAMES_ASSET_URL, GEONAMES_ASSET_VERSION, GeoNamesAssetFetcher,
- GeoNamesAssetSpec, GeoNamesAssetState, GeoNamesAssetStatus, GeoNamesBlockingHttpFetcher,
- Geocoder, GeocoderCountryListResult, GeocoderError, GeocoderLocalityCandidate,
- GeocoderLocalityInput, GeocoderLocalityLookup, GeocoderLocalityQuery, GeocoderPoint,
- GeocoderReverseOptions, GeocoderReverseResult, GeocoderStructuredLocalityQuery,
- RadrootsGeoNamesConfig,
-};
-#[cfg(feature = "runtime")]
-pub use crate::idempotency::{
- SDK_IDEMPOTENCY_KEY_MAX_LEN, SdkIdempotencyKey, SdkTradeIdempotencyRecord,
-};
-#[cfg(feature = "knowledge")]
-pub use crate::knowledge::{
- AddressableRef, DraftError, EventDraft, EventEnvelope, EventRef, KIND_FILE_METADATA,
- KIND_KNOWLEDGE_CLAIM, KIND_KNOWLEDGE_FIELD_REPORT, KIND_KNOWLEDGE_RELATION,
- KIND_KNOWLEDGE_REVIEW, KIND_KNOWLEDGE_SOURCE, KIND_WIKI_ARTICLE, KIND_WIKI_MERGE_REQUEST,
- KIND_WIKI_REDIRECT, KNOWLEDGE_CLAIM_CONTRACT_ID, KNOWLEDGE_FIELD_REPORT_CONTRACT_ID,
- KNOWLEDGE_RELATION_CONTRACT_ID, KNOWLEDGE_REVIEW_CONTRACT_ID, KNOWLEDGE_SOURCE_CONTRACT_ID,
- KnowledgeChangeProposal, KnowledgeCitationSpan, KnowledgeClaim, KnowledgeCodec,
- KnowledgeDraftBuilder, KnowledgeEventBuilder, KnowledgeFieldContext, KnowledgeFieldReport,
- KnowledgeLocation, KnowledgeLocationPrecision, KnowledgeNodeRef, KnowledgeObservation,
- KnowledgeObservationValue, KnowledgeRelation, KnowledgeReview, KnowledgeReviewScope,
- KnowledgeReviewScore, KnowledgeReviewTarget, KnowledgeSource, Nip01EventWireParts,
- RADROOTS_CONTRIBUTION_ATTESTATION_SCHEMA, RADROOTS_EVIDENCE_BOUNTY_SCHEMA,
- RADROOTS_KNOWLEDGE_CHANGE_PROPOSAL_SCHEMA, RADROOTS_KNOWLEDGE_CLAIM_SCHEMA,
- RADROOTS_KNOWLEDGE_CONTRACT_MANIFEST_SCHEMA_VERSION, RADROOTS_KNOWLEDGE_FIELD_REPORT_SCHEMA,
- RADROOTS_KNOWLEDGE_RELATION_SCHEMA, RADROOTS_KNOWLEDGE_REVIEW_SCHEMA,
- RADROOTS_KNOWLEDGE_SCHEMA_VERSION, RADROOTS_KNOWLEDGE_SOURCE_SCHEMA,
- RADROOTS_WIKI_D_TAG_MAX_LEN, RadrootsContractValidatedEvent, RadrootsDecodeError,
- RadrootsDecodedEvent, RadrootsEncodeError, RadrootsIdVerifiedEvent,
- RadrootsKnowledgeBuilderError, RadrootsKnowledgeClaimBuilder,
- RadrootsKnowledgeContractManifest, RadrootsKnowledgeContractManifestEntry,
- RadrootsKnowledgeFieldReportBuilder, RadrootsKnowledgeManifestCodecSupport,
- RadrootsKnowledgeManifestDiscriminator, RadrootsKnowledgeManifestTagContract,
- RadrootsKnowledgeRelationBuilder, RadrootsKnowledgeReviewBuilder,
- RadrootsKnowledgeSourceBuilder, RadrootsNip01VerificationError, RadrootsSdkKnowledgeError,
- RadrootsSignatureVerifiedEvent, RadrootsWikiArticleBuilder, RadrootsWikiMergeRequestBuilder,
- RadrootsWikiRedirectBuilder, RightsAssertion, WIKI_ARTICLE_CONTRACT_ID,
- WIKI_MERGE_REQUEST_CONTRACT_ID, WIKI_REDIRECT_CONTRACT_ID, WikiArticle, WikiArticleVersionRef,
- WikiDTagError, WikiMergeRequest, WikiRedirect, build_knowledge_claim_event,
- build_knowledge_field_report_event, build_knowledge_relation_event,
- build_knowledge_review_event, build_knowledge_source_event, build_wiki_article_event,
- build_wiki_merge_request_event, build_wiki_redirect_event, contract_manifest,
- contract_manifest_json, contract_manifest_sha256, normalize_wiki_d_tag,
- prepare_knowledge_claim_draft, prepare_knowledge_field_report_draft,
- prepare_knowledge_relation_draft, prepare_knowledge_review_draft,
- prepare_knowledge_source_draft, prepare_wiki_article_draft, prepare_wiki_merge_request_draft,
- prepare_wiki_redirect_draft, validate_wiki_d_tag, verify_and_decode_radroots_event,
-};
-#[cfg(feature = "runtime")]
-pub use crate::listings_runtime::{
- LISTING_PUBLISH_OPERATION_KIND, ListingEnqueuePublishRequest, ListingEnqueueReceipt,
- ListingPreparePublishRequest, ListingPublishPlan, SdkMutationState,
-};
-#[cfg(feature = "runtime")]
-pub use crate::privacy::{
- PrivacyPreflightConfirmation, PrivacyPreflightReceipt, PrivacyPreflightStatus,
- ProductSensitivityField,
-};
-#[cfg(feature = "runtime")]
-pub use crate::product_clients::{
- FarmsClient, GeoNamesClient, ListingsClient, MarketClient, SyncClient, TradesClient,
-};
-#[cfg(feature = "runtime")]
-pub use crate::runtime::{
- BackupReceipt, BackupRequest, IntegrityReceipt, IntegrityRequest, RadrootsClient,
- RadrootsClientBuilder, RadrootsSdkClock, RadrootsSdkStorageConfig, RadrootsSdkStoragePaths,
- RadrootsSdkTimestamp, RestoreArchive, RestoreReceipt, RestoreRequest, SdkBackupKeyReference,
- SdkBackupManifest, SdkBackupManifestKind, SdkBackupMemberHash, SdkBackupMemberHashes,
- SdkBackupRecoveryManifest, SdkBackupState, SdkBackupVerification, SdkEventStoreStorageStatus,
- SdkOutboxStorageStatus, SdkPrivateStoreStorageStatus, SdkRestoreFinalization, SdkRestoreState,
- SdkSqliteStoreStatus, SdkSqliteWalCheckpointReceipt, SdkSqliteWalStatus, SdkStorageKind,
- SdkStudioStoreStorageStatus, StorageCheckpointReceipt, StorageCheckpointRequest,
- StorageStatusReceipt, StorageStatusRequest,
-};
-#[cfg(all(feature = "runtime", feature = "signer-adapters"))]
-#[doc(hidden)]
-pub use crate::signer_provider::{
- RADROOTS_SDK_MYC_NIP46_DEFAULT_REQUEST_TIMEOUT_MS,
- RADROOTS_SDK_MYC_NIP46_PRODUCT_SIGN_EVENT_KINDS, RadrootsSdkLocalKeySigner,
- RadrootsSdkLocalSignerCapability, RadrootsSdkMycNip46RequestPolicy, RadrootsSdkMycNip46Signer,
- RadrootsSdkNip46ClientKey, RadrootsSdkNip46Transport, RadrootsSdkNip46TransportFuture,
- RadrootsSdkSignReceipt, RadrootsSdkSignRequest, RadrootsSdkSignerCapability,
- RadrootsSdkSignerMode, RadrootsSdkSignerProgress, RadrootsSdkSignerProgressSink,
- RadrootsSdkSignerProvider, RadrootsSdkSignerState, RadrootsSdkSignerStatus,
- radroots_sdk_myc_nip46_product_permission_strings, radroots_sdk_myc_nip46_product_permissions,
-};
-#[cfg(feature = "runtime")]
-pub use crate::sync_runtime::{
- PUSH_OUTBOX_DEFAULT_CLAIM_TTL_MS, PUSH_OUTBOX_DEFAULT_LIMIT,
- PUSH_OUTBOX_DEFAULT_NEXT_ATTEMPT_DELAY_MS, PUSH_OUTBOX_MAX_LIMIT, PushOutboxEventReceipt,
- PushOutboxEventState, PushOutboxReceipt, PushOutboxRequest, PushOutboxTargetOutcomeKind,
- PushOutboxTargetReceipt, PushOutboxTransportOutcomeKind, ReticulumTryNowRequest,
- SYNC_PROJECTION_REFRESH_DEFAULT_LIMIT, SYNC_PROJECTION_REFRESH_MAX_LIMIT, SdkRelayAuthPolicy,
- SyncEventStoreStatus, SyncOutboxStatus, SyncProjectionRefreshReceipt,
- SyncProjectionRefreshRequest, SyncStatusReceipt, SyncStatusRequest, SyncStatusSource,
- SyncTransportOperationCapabilitiesSummary, SyncTransportProfileSummary,
- SyncTransportStatusSummary, SyncTransportTargetSummary,
-};
-#[cfg(feature = "runtime")]
-pub use crate::trade_runtime::{
- CancelTradeRequest, DecideCandidateRequest, EvidenceRefreshReceipt, EvidenceView,
- GetTradeRequest, InspectEvidenceRequest, ListTradesRequest, Page, ProposeRevisionRequest,
- RefreshTradeEvidenceRequest, ResumeOperationRequest, SubmitProposalRequest,
- TRADE_CANCEL_OPERATION_KIND, TRADE_DECIDE_CANDIDATE_OPERATION_KIND,
- TRADE_PROPOSE_REVISION_OPERATION_KIND, TRADE_QUERY_DEFAULT_LIMIT, TRADE_QUERY_MAX_LIMIT,
- TRADE_RESUME_OPERATION_KIND, TRADE_RUNTIME_CAPABILITY_API_VERSION,
- TRADE_RUNTIME_PRIVATE_STORAGE_PROFILE_ID, TRADE_RUNTIME_PROTOCOL_PROFILE_ID,
- TRADE_RUNTIME_STORAGE_PROFILE_ID, TRADE_RUNTIME_WIRE_PROFILE_ID,
- TRADE_SUBMIT_PROPOSAL_OPERATION_KIND, TradeCommandLifecycleState, TradeCommandReceipt,
- TradeCommandService, TradeCoreMvpCapabilityReport, TradeListAnyOf, TradeListFilter,
- TradeListSort, TradeOptionalIntegrationCapabilityReport, TradePrivateArtifactDeleteReceipt,
- TradePrivateArtifactDeleteRequest, TradePrivateArtifactKind, TradePrivateArtifactOpenReceipt,
- TradePrivateArtifactOpenRequest, TradePrivateArtifactSealReceipt,
- TradePrivateArtifactSealRequest, TradePrivateTermsAvailabilityView,
- TradeProtocolCapabilityReport, TradeQueryService, TradeRuntimeCapabilityReport,
- TradeStatusView, TradeStorageCapabilityReport, TradeSummaryView,
-};
-#[cfg(feature = "runtime")]
-pub use crate::transport::{
- MeshScopeId, MultiTargetProfile, NostrProfile, NostrRelayUrlPolicy, PublishMode,
- RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryTargetStatus,
- RadrootsTransportOutcome, RadrootsTransportSatisfactionClass, RadrootsTransportTargetReceipt,
- RadrootsdExecutionAuth, RadrootsdExecutionProfile, ReticulumAgentEndpoint, ReticulumBehavior,
- ReticulumProfile, SDK_TRANSPORT_TARGET_MAX_COUNT, SatisfactionPolicy, TargetPolicy, TargetSet,
- TransportId, TransportProfile, TransportReceipt,
-};
diff --git a/crates/sdk/src/listing.rs b/crates/sdk/src/listing.rs
@@ -0,0 +1 @@
+//! Listing product operations.
diff --git a/crates/sdk/src/signing.rs b/crates/sdk/src/signing.rs
@@ -0,0 +1 @@
+//! Signing capability composition.
diff --git a/crates/sdk/src/storage.rs b/crates/sdk/src/storage.rs
@@ -0,0 +1 @@
+//! Storage capability composition.
diff --git a/crates/sdk/src/sync.rs b/crates/sdk/src/sync.rs
@@ -0,0 +1 @@
+//! Synchronization capability composition.
diff --git a/crates/sdk/src/trade.rs b/crates/sdk/src/trade.rs
@@ -0,0 +1 @@
+//! Trade commands and queries.
diff --git a/crates/sdk/src/transport.rs b/crates/sdk/src/transport.rs
@@ -1,773 +1 @@
-use crate::RadrootsSdkError;
-use radroots_transport::target::{TargetFingerprint, TargetScope, TargetSet as TransportTargetSet};
-use radroots_transport::{
- RadrootsTransportCapabilityAvailability, RadrootsTransportCapabilityMaturity,
- RadrootsTransportImplementationState, RadrootsTransportSatisfactionPolicy,
- RadrootsTransportStatus, Target,
-};
-use radroots_transport_nostr::{RadrootsRelayUrlPolicy, RelayUrl};
-use radroots_transport_reticulum::{
- RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID,
- RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
-};
-use serde::ser::{SerializeStruct, Serializer};
-use std::collections::BTreeSet;
-
-pub use radroots_transport::{
- RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryTargetStatus,
- RadrootsTransportOutcome, RadrootsTransportSatisfactionClass, RadrootsTransportTargetReceipt,
- TransportId,
-};
-
-pub const SDK_TRANSPORT_TARGET_MAX_COUNT: usize = 20;
-pub const RETICULUM_AGENT_ENDPOINT_PREFIX: &str = "reticulum-agent:";
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case")]
-#[non_exhaustive]
-pub enum PublishMode {
- DryRun,
- EnqueueOnly,
- EnqueueAndPublish,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case")]
-#[non_exhaustive]
-pub enum SatisfactionPolicy {
- NoWait,
- AnyAccepted,
- AllAccepted,
- QuorumAccepted {
- threshold: u16,
- },
- AnyDelivered,
- AllDelivered,
- QuorumDelivered {
- threshold: u16,
- },
- RequiredAcceptedTargets {
- target_fingerprints: Vec<TargetFingerprint>,
- },
- RequiredDeliveredTargets {
- target_fingerprints: Vec<TargetFingerprint>,
- },
-}
-
-impl SatisfactionPolicy {
- pub fn quorum_accepted(threshold: u16) -> Result<Self, RadrootsSdkError> {
- validate_satisfaction_threshold(threshold)?;
- Ok(Self::QuorumAccepted { threshold })
- }
-
- pub fn quorum_delivered(threshold: u16) -> Result<Self, RadrootsSdkError> {
- validate_satisfaction_threshold(threshold)?;
- Ok(Self::QuorumDelivered { threshold })
- }
-
- pub fn required_accepted_targets<I, S>(targets: I) -> Result<Self, RadrootsSdkError>
- where
- I: IntoIterator<Item = S>,
- S: AsRef<str>,
- {
- Ok(Self::RequiredAcceptedTargets {
- target_fingerprints: required_target_fingerprints(targets)?,
- })
- }
-
- pub fn required_delivered_targets<I, S>(targets: I) -> Result<Self, RadrootsSdkError>
- where
- I: IntoIterator<Item = S>,
- S: AsRef<str>,
- {
- Ok(Self::RequiredDeliveredTargets {
- target_fingerprints: required_target_fingerprints(targets)?,
- })
- }
-
- pub fn is_no_wait(&self) -> bool {
- matches!(self, Self::NoWait)
- }
-
- pub(crate) fn transport_satisfaction_policy(
- &self,
- ) -> Result<RadrootsTransportSatisfactionPolicy, RadrootsSdkError> {
- Ok(match self {
- Self::NoWait => RadrootsTransportSatisfactionPolicy::no_wait(),
- Self::AnyAccepted => RadrootsTransportSatisfactionPolicy::any_accepted(),
- Self::AllAccepted => RadrootsTransportSatisfactionPolicy::all_accepted(),
- Self::QuorumAccepted { threshold } => {
- RadrootsTransportSatisfactionPolicy::quorum_accepted(*threshold)
- }
- Self::AnyDelivered => RadrootsTransportSatisfactionPolicy::any_delivered(),
- Self::AllDelivered => RadrootsTransportSatisfactionPolicy::all_delivered(),
- Self::QuorumDelivered { threshold } => {
- RadrootsTransportSatisfactionPolicy::quorum_delivered(*threshold)
- }
- Self::RequiredAcceptedTargets {
- target_fingerprints,
- } => RadrootsTransportSatisfactionPolicy::required_targets(
- RadrootsTransportSatisfactionClass::Accepted,
- target_fingerprints.clone(),
- )?,
- Self::RequiredDeliveredTargets {
- target_fingerprints,
- } => RadrootsTransportSatisfactionPolicy::required_targets(
- RadrootsTransportSatisfactionClass::Delivered,
- target_fingerprints.clone(),
- )?,
- })
- }
-}
-
-fn validate_satisfaction_threshold(threshold: u16) -> Result<(), RadrootsSdkError> {
- if threshold == 0 {
- return Err(RadrootsSdkError::InvalidRequest {
- message: "satisfaction policy threshold must require at least one target".to_owned(),
- });
- }
- Ok(())
-}
-
-fn required_target_fingerprints<I, S>(
- targets: I,
-) -> Result<Vec<TargetFingerprint>, RadrootsSdkError>
-where
- I: IntoIterator<Item = S>,
- S: AsRef<str>,
-{
- let fingerprints = targets
- .into_iter()
- .map(|target| TargetFingerprint::parse(target.as_ref()))
- .collect::<Result<Vec<_>, _>>()?;
- RadrootsTransportSatisfactionPolicy::required_targets(
- RadrootsTransportSatisfactionClass::Accepted,
- fingerprints.clone(),
- )?;
- Ok(fingerprints)
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case")]
-#[non_exhaustive]
-pub enum NostrRelayUrlPolicy {
- Public,
- Localhost,
-}
-
-impl NostrRelayUrlPolicy {
- pub(crate) fn nostr_transport_policy(self) -> RadrootsRelayUrlPolicy {
- match self {
- Self::Public => RadrootsRelayUrlPolicy::Public,
- Self::Localhost => RadrootsRelayUrlPolicy::Localhost,
- }
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-#[non_exhaustive]
-pub enum TargetPolicy {
- Explicit(TargetSet),
- DefaultProfile,
- LocalOnly,
- MeshScope(MeshScopeId),
-}
-
-impl TargetPolicy {
- pub fn explicit(targets: TargetSet) -> Self {
- Self::Explicit(targets)
- }
-
- pub fn try_nostr_relays<I, S>(
- relays: I,
- url_policy: NostrRelayUrlPolicy,
- ) -> Result<Self, RadrootsSdkError>
- where
- I: IntoIterator<Item = S>,
- S: AsRef<str>,
- {
- Ok(Self::Explicit(TargetSet::nostr_relays(relays, url_policy)?))
- }
-
- pub fn default_profile() -> Self {
- Self::DefaultProfile
- }
-
- pub fn local_only() -> Self {
- Self::LocalOnly
- }
-
- pub fn mesh_scope(scope: MeshScopeId) -> Self {
- Self::MeshScope(scope)
- }
-}
-
-impl serde::Serialize for TargetPolicy {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- match self {
- Self::Explicit(targets) => {
- let mut state = serializer.serialize_struct("TargetPolicy", 3)?;
- state.serialize_field("kind", "explicit")?;
- state.serialize_field("targets", targets.targets())?;
- state.serialize_field("canonical_targets", targets.canonical_targets())?;
- state.end()
- }
- Self::DefaultProfile => {
- let mut state = serializer.serialize_struct("TargetPolicy", 1)?;
- state.serialize_field("kind", "default_profile")?;
- state.end()
- }
- Self::LocalOnly => {
- let mut state = serializer.serialize_struct("TargetPolicy", 1)?;
- state.serialize_field("kind", "local_only")?;
- state.end()
- }
- Self::MeshScope(scope) => {
- let mut state = serializer.serialize_struct("TargetPolicy", 2)?;
- state.serialize_field("kind", "mesh_scope")?;
- state.serialize_field("scope", scope)?;
- state.end()
- }
- }
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(transparent)]
-pub struct MeshScopeId(TargetScope);
-
-impl MeshScopeId {
- pub fn parse(raw: impl AsRef<str>) -> Result<Self, RadrootsSdkError> {
- Ok(Self(TargetScope::parse(raw)?))
- }
-
- pub fn local_reticulum() -> Self {
- Self(TargetScope::parse(RADROOTS_RETICULUM_SCOPE_ID).expect("Reticulum scope"))
- }
-
- pub fn as_str(&self) -> &str {
- self.0.as_str()
- }
-
- pub(crate) fn transport_scope(&self) -> TargetScope {
- self.0.clone()
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct TargetSet {
- targets: Vec<Target>,
- canonical_targets: Vec<String>,
-}
-
-impl TargetSet {
- pub fn nostr_relays<I, S>(
- relays: I,
- policy: NostrRelayUrlPolicy,
- ) -> Result<Self, RadrootsSdkError>
- where
- I: IntoIterator<Item = S>,
- S: AsRef<str>,
- {
- let mut targets = Vec::new();
- for relay in relays {
- let normalized = normalized_nostr_relay_url(relay.as_ref(), policy)?;
- targets.push(Target::new(TransportId::NOSTR, normalized)?);
- }
- Self::from_transport_targets(targets)
- }
-
- pub fn transport_targets(targets: Vec<Target>) -> Result<Self, RadrootsSdkError> {
- Self::from_transport_targets(targets)
- }
-
- pub fn targets(&self) -> &[Target] {
- self.targets.as_slice()
- }
-
- pub fn canonical_targets(&self) -> &[String] {
- self.canonical_targets.as_slice()
- }
-
- pub fn transport_target_set(&self) -> Result<TransportTargetSet, RadrootsSdkError> {
- Ok(TransportTargetSet::new(self.targets.clone())?)
- }
-
- pub fn nostr_relay_urls(&self) -> Vec<String> {
- self.targets
- .iter()
- .filter(|target| target.kind() == &TransportId::NOSTR)
- .map(|target| target.uri().as_str().to_owned())
- .collect()
- }
-
- pub fn into_targets(self) -> Vec<Target> {
- self.targets
- }
-
- pub fn len(&self) -> usize {
- self.targets.len()
- }
-
- pub fn is_empty(&self) -> bool {
- self.targets.is_empty()
- }
-
- fn from_transport_targets(targets: Vec<Target>) -> Result<Self, RadrootsSdkError> {
- if targets.is_empty() {
- return Err(RadrootsSdkError::empty_transport_targets(
- "sdk transport target set",
- ));
- }
- if targets.len() > SDK_TRANSPORT_TARGET_MAX_COUNT {
- return Err(RadrootsSdkError::transport_target_limit_exceeded(
- SDK_TRANSPORT_TARGET_MAX_COUNT,
- targets.len(),
- ));
- }
- for target in &targets {
- if target.kind() == &TransportId::RETICULUM
- && target.uri().as_str() != RADROOTS_RETICULUM_ENDPOINT_URI
- {
- return Err(RadrootsSdkError::InvalidRequest {
- message: format!(
- "Reticulum endpoint must be {RADROOTS_RETICULUM_ENDPOINT_URI}"
- ),
- });
- }
- }
- TransportTargetSet::new(targets.clone())?;
- let canonical_targets = targets
- .iter()
- .map(|target| target.fingerprint().to_string())
- .collect::<BTreeSet<_>>()
- .into_iter()
- .collect();
- Ok(Self {
- targets,
- canonical_targets,
- })
- }
-}
-
-impl serde::Serialize for TargetSet {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- let mut state = serializer.serialize_struct("TargetSet", 2)?;
- state.serialize_field("targets", self.targets())?;
- state.serialize_field("canonical_targets", self.canonical_targets())?;
- state.end()
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
-pub struct NostrProfile {
- target_set: TargetSet,
-}
-
-impl NostrProfile {
- pub fn new<I, S>(relays: I, policy: NostrRelayUrlPolicy) -> Result<Self, RadrootsSdkError>
- where
- I: IntoIterator<Item = S>,
- S: AsRef<str>,
- {
- Ok(Self {
- target_set: TargetSet::nostr_relays(relays, policy)?,
- })
- }
-
- pub fn target_set(&self) -> &TargetSet {
- &self.target_set
- }
-
- pub fn relay_urls(&self) -> Vec<String> {
- self.target_set.nostr_relay_urls()
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
-pub struct ReticulumProfile {
- endpoint_uri: String,
- scope: MeshScopeId,
- agent_endpoint: Option<ReticulumAgentEndpoint>,
- behavior: ReticulumBehavior,
-}
-
-impl ReticulumProfile {
- pub fn deferred_until_implemented() -> Self {
- Self {
- endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
- scope: MeshScopeId::local_reticulum(),
- agent_endpoint: None,
- behavior: ReticulumBehavior::RejectDeliveryAttempts,
- }
- }
-
- pub fn with_behavior(mut self, behavior: ReticulumBehavior) -> Self {
- self.behavior = behavior;
- self
- }
-
- pub fn endpoint_uri(&self) -> &str {
- self.endpoint_uri.as_str()
- }
-
- pub fn scope(&self) -> &MeshScopeId {
- &self.scope
- }
-
- pub fn agent_endpoint(&self) -> Option<&ReticulumAgentEndpoint> {
- self.agent_endpoint.as_ref()
- }
-
- pub fn with_agent_endpoint(mut self, agent_endpoint: ReticulumAgentEndpoint) -> Self {
- self.agent_endpoint = Some(agent_endpoint);
- self
- }
-
- pub fn with_scope(mut self, scope: MeshScopeId) -> Self {
- self.scope = scope;
- self
- }
-
- pub fn behavior(&self) -> ReticulumBehavior {
- self.behavior
- }
-
- pub fn target_set(&self) -> Result<TargetSet, RadrootsSdkError> {
- if self.endpoint_uri.as_str() != RADROOTS_RETICULUM_ENDPOINT_URI {
- return Err(radroots_transport::RadrootsTransportError::InvalidTargetUri.into());
- }
- TargetSet::transport_targets(vec![Target::new_with_metadata(
- TransportId::RETICULUM,
- self.endpoint_uri.as_str(),
- Some(self.scope.transport_scope()),
- None,
- )?])
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(transparent)]
-pub struct ReticulumAgentEndpoint(String);
-
-impl ReticulumAgentEndpoint {
- pub fn parse(raw: impl AsRef<str>) -> Result<Self, RadrootsSdkError> {
- let uri = raw.as_ref();
- let Some(suffix) = uri.strip_prefix(RETICULUM_AGENT_ENDPOINT_PREFIX) else {
- return Err(RadrootsSdkError::InvalidRequest {
- message: "Reticulum agent endpoint is invalid".to_owned(),
- });
- };
- if uri.is_empty()
- || uri != uri.trim()
- || suffix.is_empty()
- || uri
- .chars()
- .any(|ch| ch.is_ascii_control() || ch.is_ascii_whitespace())
- {
- return Err(RadrootsSdkError::InvalidRequest {
- message: "Reticulum agent endpoint is invalid".to_owned(),
- });
- }
- Ok(Self(uri.to_owned()))
- }
-
- pub fn as_str(&self) -> &str {
- self.0.as_str()
- }
-}
-
-impl Default for ReticulumProfile {
- fn default() -> Self {
- Self::deferred_until_implemented()
- }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case")]
-#[non_exhaustive]
-pub enum ReticulumBehavior {
- RejectDeliveryAttempts,
- DeferDeliveryPlans,
-}
-
-impl ReticulumBehavior {
- pub fn as_str(self) -> &'static str {
- match self {
- Self::RejectDeliveryAttempts => "reject_delivery_attempts",
- Self::DeferDeliveryPlans => "defer_delivery_plans",
- }
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
-pub struct MultiTargetProfile {
- nostr: NostrProfile,
- reticulum: ReticulumProfile,
-}
-
-impl MultiTargetProfile {
- pub fn new(nostr: NostrProfile, reticulum: ReticulumProfile) -> Self {
- Self { nostr, reticulum }
- }
-
- pub fn nostr(&self) -> &NostrProfile {
- &self.nostr
- }
-
- pub fn reticulum(&self) -> &ReticulumProfile {
- &self.reticulum
- }
-}
-
-#[derive(Clone, Default, PartialEq, Eq)]
-pub enum RadrootsdExecutionAuth {
- #[default]
- None,
- BearerToken(String),
-}
-
-impl core::fmt::Debug for RadrootsdExecutionAuth {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- match self {
- Self::None => f.write_str("None"),
- Self::BearerToken(_) => f.write_str("BearerToken(<redacted>)"),
- }
- }
-}
-
-impl serde::Serialize for RadrootsdExecutionAuth {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- let mut state = serializer.serialize_struct("RadrootsdExecutionAuth", 1)?;
- match self {
- Self::None => state.serialize_field("kind", "none")?,
- Self::BearerToken(_) => state.serialize_field("kind", "bearer_token")?,
- }
- state.end()
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
-pub struct RadrootsdExecutionProfile {
- endpoint_url: String,
- auth: RadrootsdExecutionAuth,
-}
-
-impl RadrootsdExecutionProfile {
- pub fn new(endpoint_url: impl Into<String>) -> Self {
- Self {
- endpoint_url: endpoint_url.into(),
- auth: RadrootsdExecutionAuth::None,
- }
- }
-
- pub fn with_bearer_token(mut self, token: impl Into<String>) -> Self {
- self.auth = RadrootsdExecutionAuth::BearerToken(token.into());
- self
- }
-
- pub fn endpoint_url(&self) -> &str {
- self.endpoint_url.as_str()
- }
-
- pub fn auth(&self) -> &RadrootsdExecutionAuth {
- &self.auth
- }
-}
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize)]
-#[serde(rename_all = "snake_case", tag = "kind")]
-#[non_exhaustive]
-pub enum TransportProfile {
- #[default]
- LocalOnly,
- Nostr {
- profile: NostrProfile,
- },
- Reticulum {
- profile: ReticulumProfile,
- },
- MultiTarget {
- profile: MultiTargetProfile,
- },
-}
-
-impl TransportProfile {
- pub fn local_only() -> Self {
- Self::LocalOnly
- }
-
- pub fn nostr(profile: NostrProfile) -> Self {
- Self::Nostr { profile }
- }
-
- pub fn reticulum(profile: ReticulumProfile) -> Self {
- Self::Reticulum { profile }
- }
-
- pub fn multi_target(profile: MultiTargetProfile) -> Self {
- Self::MultiTarget { profile }
- }
-
- pub(crate) fn transport_profile_id(&self) -> &'static str {
- match self {
- Self::LocalOnly => "local_only",
- Self::Nostr { .. } => "nostr",
- Self::Reticulum { .. } => "reticulum",
- Self::MultiTarget { .. } => "multi_target",
- }
- }
-
- pub(crate) fn target_set(&self) -> Result<Option<TargetSet>, RadrootsSdkError> {
- match self {
- Self::LocalOnly => Ok(None),
- Self::Nostr { profile } => Ok(Some(profile.target_set().clone())),
- Self::Reticulum { profile } => Ok(Some(profile.target_set()?)),
- Self::MultiTarget { profile } => {
- let mut targets = profile.nostr().target_set().targets().to_vec();
- targets.extend(profile.reticulum().target_set()?.into_targets());
- Ok(Some(TargetSet::transport_targets(targets)?))
- }
- }
- }
-
- pub(crate) fn configured_transport_targets(&self) -> Result<Vec<Target>, RadrootsSdkError> {
- Ok(self
- .target_set()?
- .map(TargetSet::into_targets)
- .unwrap_or_default())
- }
-
- pub(crate) fn transport_statuses(&self) -> Vec<RadrootsTransportStatus> {
- match self {
- Self::LocalOnly => vec![
- RadrootsTransportStatus::new(
- TransportId::LOCAL,
- true,
- RadrootsTransportImplementationState::Real,
- false,
- "local persistence only",
- )
- .with_profile_id(self.transport_profile_id()),
- ],
- Self::Nostr { .. } => vec![nostr_transport_status(self.transport_profile_id())],
- Self::Reticulum { profile } => {
- vec![reticulum_transport_status(
- self.transport_profile_id(),
- profile.endpoint_uri(),
- )]
- }
- Self::MultiTarget { profile } => vec![
- nostr_transport_status(self.transport_profile_id()),
- reticulum_transport_status(
- self.transport_profile_id(),
- profile.reticulum().endpoint_uri(),
- ),
- ],
- }
- }
-
- pub(crate) fn configured_nostr_relay_urls(&self) -> Vec<String> {
- match self {
- Self::Nostr { profile } => profile.relay_urls(),
- Self::MultiTarget { profile } => profile.nostr().relay_urls(),
- Self::LocalOnly | Self::Reticulum { .. } => Vec::new(),
- }
- }
-}
-
-fn nostr_transport_status(profile_id: &str) -> RadrootsTransportStatus {
- RadrootsTransportStatus::new(
- TransportId::NOSTR,
- true,
- RadrootsTransportImplementationState::Real,
- true,
- "ready",
- )
- .with_profile_id(profile_id)
-}
-
-fn reticulum_transport_status(profile_id: &str, endpoint_uri: &str) -> RadrootsTransportStatus {
- RadrootsTransportStatus::new(
- TransportId::RETICULUM,
- true,
- RadrootsTransportImplementationState::Real,
- false,
- RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
- )
- .with_profile_id(profile_id)
- .with_endpoint_uri(endpoint_uri)
- .with_maturity(RadrootsTransportCapabilityMaturity::Preview)
- .with_availability(RadrootsTransportCapabilityAvailability::Unavailable)
-}
-
-impl From<RadrootsTransportDeliveryReceipt> for TransportReceipt {
- fn from(value: RadrootsTransportDeliveryReceipt) -> Self {
- Self {
- request_id: value.request_id().to_owned(),
- target_set: value.target_set().clone(),
- target_receipts: value.target_receipts().to_vec(),
- }
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
-pub struct TransportReceipt {
- request_id: String,
- target_set: TransportTargetSet,
- target_receipts: Vec<RadrootsTransportTargetReceipt>,
-}
-
-impl TransportReceipt {
- pub fn request_id(&self) -> &str {
- self.request_id.as_str()
- }
-
- pub fn target_set(&self) -> &TransportTargetSet {
- &self.target_set
- }
-
- pub fn target_receipts(&self) -> &[RadrootsTransportTargetReceipt] {
- self.target_receipts.as_slice()
- }
-
- pub fn satisfied_target_count(
- &self,
- satisfaction_class: RadrootsTransportSatisfactionClass,
- ) -> usize {
- self.target_receipts
- .iter()
- .filter(|receipt| receipt.status.counts_as_satisfied(satisfaction_class))
- .count()
- }
-
- pub fn is_satisfied_by(&self, policy: &SatisfactionPolicy) -> Result<bool, RadrootsSdkError> {
- let receipt = RadrootsTransportDeliveryReceipt::new(
- self.request_id.clone(),
- self.target_set.clone(),
- self.target_receipts.clone(),
- )?;
- Ok(receipt.is_satisfied_by(&policy.transport_satisfaction_policy()?)?)
- }
-}
-
-fn normalized_nostr_relay_url(
- value: &str,
- policy: NostrRelayUrlPolicy,
-) -> Result<String, RadrootsSdkError> {
- let relay = RelayUrl::parse(value, policy.nostr_transport_policy())?;
- Ok(relay.into_string())
-}
-
-#[cfg(test)]
-#[path = "../tests/unit/transport_tests.rs"]
-mod tests;
+//! Transport capability composition.
diff --git a/crates/sdk/tests/package_boundary.rs b/crates/sdk/tests/package_boundary.rs
@@ -0,0 +1,72 @@
+use std::collections::BTreeSet;
+
+const MANIFEST: &str = include_str!("../Cargo.toml");
+const ROOT: &str = include_str!("../src/lib.rs");
+
+#[test]
+fn manifest_has_final_identity_and_dependency_boundary() {
+ assert!(MANIFEST.contains("name = \"radroots_sdk\""));
+ assert!(MANIFEST.contains("publish = false"));
+ assert!(MANIFEST.contains("version.workspace = true"));
+ assert!(MANIFEST.contains("name = \"package_boundary\""));
+
+ let dependencies = dependency_names(MANIFEST);
+ let expected = BTreeSet::from([
+ "radroots_core",
+ "radroots_event",
+ "radroots_event_codec",
+ "radroots_geonames",
+ "radroots_identity",
+ "radroots_nostr",
+ "radroots_nostr_connect",
+ "radroots_protocol",
+ "radroots_secrets",
+ "radroots_signing",
+ "radroots_storage",
+ "radroots_storage_sqlite",
+ "radroots_sync",
+ "radroots_trade",
+ "radroots_transport",
+ "radroots_transport_nostr",
+ ]);
+ assert_eq!(dependencies, expected);
+}
+
+#[test]
+fn root_declares_exact_final_module_skeleton() {
+ let actual = ROOT
+ .lines()
+ .filter_map(|line| line.trim().strip_prefix("pub mod "))
+ .map(|module| module.trim_end_matches(';'))
+ .collect::<BTreeSet<_>>();
+ let expected = BTreeSet::from([
+ "capability",
+ "client",
+ "diagnostics",
+ "error",
+ "farm",
+ "listing",
+ "signing",
+ "storage",
+ "sync",
+ "trade",
+ "transport",
+ ]);
+ assert_eq!(actual, expected);
+ assert!(!ROOT.contains("no_std"));
+}
+
+fn dependency_names(manifest: &str) -> BTreeSet<&str> {
+ let dependencies = manifest
+ .split_once("[dependencies]")
+ .expect("dependency section")
+ .1
+ .split_once("[[test]]")
+ .expect("test section")
+ .0;
+ dependencies
+ .lines()
+ .filter_map(|line| line.split_once(" = ").map(|(name, _)| name.trim()))
+ .filter(|name| name.starts_with("radroots_"))
+ .collect()
+}