field_lib

Cross-platform Rust runtime for Radroots iOS and Android apps
git clone https://radroots.dev/git/field_lib.git
Log | Files | Refs | README | LICENSE

commit db4f3acda1eb67bc9a405b3c76962ca0f03d8dcb
parent cd05e1e724184056cc914d78c6faa9e5a1d3328d
Author: triesap <tyson@radroots.org>
Date:   Fri, 19 Jun 2026 16:35:54 -0700

errors: type mobile runtime failures

- replace the string-only app error with stable mobile error categories.
- map identity, relay, runtime, initialization, and unsupported failures explicitly.
- turn retired listing message fetch into an unsupported operation.
- cover identity, relay, and unsupported error mappings in field core tests.

Diffstat:
Mcrates/field_core/src/error.rs | 46++++++++++++++++++++++++++++++++++++++++++++--
Mcrates/field_core/src/logging.rs | 4++--
Mcrates/field_core/src/runtime/builder.rs | 4+++-
Mcrates/field_core/src/runtime/key_management.rs | 60++++++++++++++++++++++++++++++------------------------------
Mcrates/field_core/src/runtime/mod.rs | 6+++++-
Mcrates/field_core/src/runtime/nostr.rs | 56++++++++++++++++++++++++++++----------------------------
Mcrates/field_core/src/runtime/trade_listing.rs | 59+++++++++++++++++++++++++++++++----------------------------
Acrates/field_core/tests/error_mapping.rs | 48++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/field_core/tests/logging_error.rs | 2+-
Mcrates/field_core/tests/no_nostr_runtime.rs | 4++--
10 files changed, 194 insertions(+), 95 deletions(-)

diff --git a/crates/field_core/src/error.rs b/crates/field_core/src/error.rs @@ -2,6 +2,48 @@ use thiserror::Error; #[derive(Debug, Error, uniffi::Error)] pub enum RadrootsAppError { - #[error("{0}")] - Msg(String), + #[error("initialization: {0}")] + Initialization(String), + #[error("identity: {0}")] + Identity(String), + #[error("secure store: {0}")] + SecureStore(String), + #[error("relay: {0}")] + Relay(String), + #[error("runtime: {0}")] + Runtime(String), + #[error("unsupported: {0}")] + Unsupported(String), + #[error("internal: {0}")] + Internal(String), +} + +impl RadrootsAppError { + pub fn initialization(message: impl Into<String>) -> Self { + Self::Initialization(message.into()) + } + + pub fn identity(message: impl Into<String>) -> Self { + Self::Identity(message.into()) + } + + pub fn secure_store(message: impl Into<String>) -> Self { + Self::SecureStore(message.into()) + } + + pub fn relay(message: impl Into<String>) -> Self { + Self::Relay(message.into()) + } + + pub fn runtime(message: impl Into<String>) -> Self { + Self::Runtime(message.into()) + } + + pub fn unsupported(message: impl Into<String>) -> Self { + Self::Unsupported(message.into()) + } + + pub fn internal(message: impl Into<String>) -> Self { + Self::Internal(message.into()) + } } diff --git a/crates/field_core/src/logging.rs b/crates/field_core/src/logging.rs @@ -15,7 +15,7 @@ pub fn init_logging( }; match radroots_log::init_logging(opts) { Ok(()) => Ok(()), - Err(err) => Err(crate::RadrootsAppError::Msg(format!("{err}"))), + Err(err) => Err(crate::RadrootsAppError::initialization(format!("{err}"))), } } @@ -23,7 +23,7 @@ pub fn init_logging( pub fn init_logging_stdout() -> Result<(), crate::RadrootsAppError> { match radroots_log::init_stdout() { Ok(()) => Ok(()), - Err(err) => Err(crate::RadrootsAppError::Msg(format!("{err}"))), + Err(err) => Err(crate::RadrootsAppError::initialization(format!("{err}"))), } } diff --git a/crates/field_core/src/runtime/builder.rs b/crates/field_core/src/runtime/builder.rs @@ -36,7 +36,9 @@ impl RuntimeBuilder { .build() { Ok(handle) => Ok(handle), - Err(err) => Err(RadrootsAppError::Msg(format!("net build failed: {err}"))), + Err(err) => Err(RadrootsAppError::initialization(format!( + "net build failed: {err}" + ))), } } diff --git a/crates/field_core/src/runtime/key_management.rs b/crates/field_core/src/runtime/key_management.rs @@ -35,14 +35,14 @@ fn account_record( let selected_identity_id = net .accounts .default_account_id() - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; let account = net .accounts .list_accounts() - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))? + .map_err(|e| RadrootsAppError::identity(format!("{e}")))? .into_iter() .find(|account| &account.account_id == account_id) - .ok_or_else(|| RadrootsAppError::Msg(format!("identity not found: {account_id}")))?; + .ok_or_else(|| RadrootsAppError::identity(format!("identity not found: {account_id}")))?; let is_selected = selected_identity_id .as_ref() .map(|selected| selected == &account.account_id) @@ -66,7 +66,7 @@ fn invalidate_nostr_runtime(net: &mut radroots_net_core::Net) { #[cfg(feature = "nostr-client")] fn identity_from_secret(secret_key: &str) -> Result<RadrootsIdentity, RadrootsAppError> { RadrootsIdentity::from_secret_key_str(secret_key) - .map_err(|e| RadrootsAppError::Msg(format!("{e}"))) + .map_err(|e| RadrootsAppError::identity(format!("{e}"))) } #[cfg(feature = "nostr-client")] @@ -92,7 +92,7 @@ fn restore_host_custody_identity( let account_id = net .accounts .upsert_identity(identity, label, make_selected) - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; invalidate_nostr_runtime(net); account_record(net, &account_id) } @@ -148,16 +148,16 @@ impl RadrootsRuntime { { let guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let selected_identity_id = guard .accounts .default_account_id() - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; let accounts = guard .accounts .list_accounts() - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; return Ok(accounts .into_iter() .map(|account| { @@ -177,7 +177,7 @@ impl RadrootsRuntime { } #[cfg(not(feature = "nostr-client"))] { - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -194,16 +194,16 @@ impl RadrootsRuntime { { let guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let selected_identity_id = guard .accounts .default_account_id() - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; let selected_npub = guard .accounts .default_public_identity() - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))? + .map_err(|e| RadrootsAppError::identity(format!("{e}")))? .map(|identity| identity.public_key_npub); let has_selected_signing_identity = guard .accounts @@ -214,7 +214,7 @@ impl RadrootsRuntime { let identities = guard .accounts .list_accounts() - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))? + .map_err(|e| RadrootsAppError::identity(format!("{e}")))? .into_iter() .map(|account| { let is_selected = selected_identity_id @@ -239,7 +239,7 @@ impl RadrootsRuntime { } #[cfg(not(feature = "nostr-client"))] { - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -255,7 +255,7 @@ impl RadrootsRuntime { #[cfg(not(feature = "nostr-client"))] { let _ = secret_key; - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -269,7 +269,7 @@ impl RadrootsRuntime { { let mut guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let (identity, _) = host_custody_identity_from_secret(secret_key.as_str())?; return restore_host_custody_identity(&mut guard, &identity, label, make_selected); @@ -277,7 +277,7 @@ impl RadrootsRuntime { #[cfg(not(feature = "nostr-client"))] { let _ = (secret_key, label, make_selected); - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -286,21 +286,21 @@ impl RadrootsRuntime { { let mut guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let account_id = RadrootsIdentityId::parse(identity_id.as_str()) - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; guard .accounts .set_default_account(&account_id) - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; invalidate_nostr_runtime(&mut guard); Ok(()) } #[cfg(not(feature = "nostr-client"))] { let _ = identity_id; - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -309,21 +309,21 @@ impl RadrootsRuntime { { let mut guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let account_id = RadrootsIdentityId::parse(identity_id.as_str()) - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; guard .accounts .remove_account(&account_id) - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; invalidate_nostr_runtime(&mut guard); Ok(()) } #[cfg(not(feature = "nostr-client"))] { let _ = identity_id; - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -332,22 +332,22 @@ impl RadrootsRuntime { { let mut guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let accounts = guard .accounts .list_accounts() - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; for account in accounts { guard .accounts .remove_account(&account.account_id) - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; } guard .accounts .clear_default_account() - .map_err(|e| RadrootsAppError::Msg(format!("{e}")))?; + .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; invalidate_nostr_runtime(&mut guard); if let Ok(mut rx_guard) = self.post_events_rx.lock() { *rx_guard = None; @@ -356,7 +356,7 @@ impl RadrootsRuntime { } #[cfg(not(feature = "nostr-client"))] { - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } diff --git a/crates/field_core/src/runtime/mod.rs b/crates/field_core/src/runtime/mod.rs @@ -49,7 +49,11 @@ impl RadrootsRuntime { #[cfg(feature = "rt")] let handle = match NetBuilder::new().config(cfg).manage_runtime(true).build() { Ok(handle) => handle, - Err(err) => return Err(RadrootsAppError::Msg(format!("net build failed: {err}"))), + Err(err) => { + return Err(RadrootsAppError::initialization(format!( + "net build failed: {err}" + ))); + } }; #[cfg(not(feature = "rt"))] let handle = NetBuilder::new() diff --git a/crates/field_core/src/runtime/nostr.rs b/crates/field_core/src/runtime/nostr.rs @@ -83,16 +83,16 @@ impl RadrootsRuntime { { let mut guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; guard .nostr_set_default_relays(&relays) - .map_err(|e| RadrootsAppError::Msg(format!("{e}"))) + .map_err(|e| RadrootsAppError::relay(format!("{e}"))) } #[cfg(not(feature = "nostr-client"))] { let _ = relays; - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -101,15 +101,15 @@ impl RadrootsRuntime { { let mut guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; guard .nostr_connect_if_key_present() - .map_err(|e| RadrootsAppError::Msg(format!("{e}"))) + .map_err(|e| RadrootsAppError::relay(format!("{e}"))) } #[cfg(not(feature = "nostr-client"))] { - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -194,19 +194,19 @@ impl RadrootsRuntime { { let guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let mgr = guard .nostr .as_ref() - .ok_or_else(|| RadrootsAppError::Msg("nostr not initialized".into()))?; + .ok_or_else(|| RadrootsAppError::relay("nostr not initialized"))?; mgr.publish_profile_event_blocking(name, display_name, nip05, about) - .map_err(|e| RadrootsAppError::Msg(e.to_string())) + .map_err(|e| RadrootsAppError::relay(e.to_string())) } #[cfg(not(feature = "nostr-client"))] { let _ = (name, display_name, nip05, about); - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -215,19 +215,19 @@ impl RadrootsRuntime { { let guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let mgr = guard .nostr .as_ref() - .ok_or_else(|| RadrootsAppError::Msg("nostr not initialized".into()))?; + .ok_or_else(|| RadrootsAppError::relay("nostr not initialized"))?; mgr.publish_post_event_blocking(content) - .map_err(|e| RadrootsAppError::Msg(e.to_string())) + .map_err(|e| RadrootsAppError::relay(e.to_string())) } #[cfg(not(feature = "nostr-client"))] { let _ = content; - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -240,21 +240,21 @@ impl RadrootsRuntime { { let guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let mgr = guard .nostr .as_ref() - .ok_or_else(|| RadrootsAppError::Msg("nostr not initialized".into()))?; + .ok_or_else(|| RadrootsAppError::relay("nostr not initialized"))?; let items = mgr .fetch_post_events_blocking(limit, since_unix) - .map_err(|e| RadrootsAppError::Msg(e.to_string()))?; + .map_err(|e| RadrootsAppError::relay(e.to_string()))?; Ok(items.into_iter().map(map_post_event_metadata).collect()) } #[cfg(not(feature = "nostr-client"))] { let _ = (limit, since_unix); - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -269,19 +269,19 @@ impl RadrootsRuntime { { let guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let mgr = guard .nostr .as_ref() - .ok_or_else(|| RadrootsAppError::Msg("nostr not initialized".into()))?; + .ok_or_else(|| RadrootsAppError::relay("nostr not initialized"))?; mgr.publish_post_reply_event_blocking( parent_event_id_hex, parent_author_hex, content, root_event_id_hex, ) - .map_err(|e| RadrootsAppError::Msg(e.to_string())) + .map_err(|e| RadrootsAppError::relay(e.to_string())) } #[cfg(not(feature = "nostr-client"))] { @@ -291,7 +291,7 @@ impl RadrootsRuntime { content, root_event_id_hex, ); - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -303,12 +303,12 @@ impl RadrootsRuntime { { let guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let mgr = guard .nostr .as_ref() - .ok_or_else(|| RadrootsAppError::Msg("nostr not initialized".into()))?; + .ok_or_else(|| RadrootsAppError::relay("nostr not initialized"))?; mgr.start_post_event_stream(since_unix); if let Ok(mut rx_guard) = self.post_events_rx.lock() { if rx_guard.is_none() { @@ -320,7 +320,7 @@ impl RadrootsRuntime { #[cfg(not(feature = "nostr-client"))] { let _ = since_unix; - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -350,12 +350,12 @@ impl RadrootsRuntime { { let guard = match self.net.lock() { Ok(guard) => guard, - Err(err) => return Err(RadrootsAppError::Msg(format!("{err}"))), + Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), }; let mgr = guard .nostr .as_ref() - .ok_or_else(|| RadrootsAppError::Msg("nostr not initialized".into()))?; + .ok_or_else(|| RadrootsAppError::relay("nostr not initialized"))?; mgr.stop_post_event_stream(); if let Ok(mut rx_guard) = self.post_events_rx.lock() { *rx_guard = None; @@ -364,7 +364,7 @@ impl RadrootsRuntime { } #[cfg(not(feature = "nostr-client"))] { - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } } diff --git a/crates/field_core/src/runtime/trade_listing.rs b/crates/field_core/src/runtime/trade_listing.rs @@ -115,10 +115,11 @@ impl RadrootsRuntime { draft: TradeListingDraft, ) -> Result<TradeListingEventParts, RadrootsAppError> { let listing = listing_from_draft(&draft)?; - let parts = listing_to_wire_parts(&listing) - .map_err(|error| RadrootsAppError::Msg(format!("listing encode failed: {error}")))?; + let parts = listing_to_wire_parts(&listing).map_err(|error| { + RadrootsAppError::runtime(format!("listing encode failed: {error}")) + })?; let tags_json = serde_json::to_string(&parts.tags).map_err(|error| { - RadrootsAppError::Msg(format!("listing tags encode failed: {error}")) + RadrootsAppError::runtime(format!("listing tags encode failed: {error}")) })?; Ok(TradeListingEventParts { kind: parts.kind, @@ -136,28 +137,28 @@ impl RadrootsRuntime { let guard = self .net .lock() - .map_err(|error| RadrootsAppError::Msg(format!("{error}")))?; + .map_err(|error| RadrootsAppError::runtime(format!("{error}")))?; let mgr = guard .nostr .as_ref() - .ok_or_else(|| RadrootsAppError::Msg("nostr not initialized".into()))?; + .ok_or_else(|| RadrootsAppError::relay("nostr not initialized"))?; let listing = listing_from_draft(&draft)?; let current_pubkey = current_pubkey_hex(self)?; if listing.farm.pubkey != current_pubkey { - return Err(RadrootsAppError::Msg( - "farm_pubkey must match the default account public key".into(), + return Err(RadrootsAppError::runtime( + "farm_pubkey must match the default account public key", )); } let parts = listing_to_wire_parts(&listing).map_err(|error| { - RadrootsAppError::Msg(format!("listing encode failed: {error}")) + RadrootsAppError::runtime(format!("listing encode failed: {error}")) })?; mgr.send_custom_event_blocking(parts.kind, parts.content, parts.tags) - .map_err(|error| RadrootsAppError::Msg(error.to_string())) + .map_err(|error| RadrootsAppError::relay(error.to_string())) } #[cfg(not(feature = "nostr-client"))] { let _ = draft; - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -171,11 +172,11 @@ impl RadrootsRuntime { let guard = self .net .lock() - .map_err(|error| RadrootsAppError::Msg(format!("{error}")))?; + .map_err(|error| RadrootsAppError::runtime(format!("{error}")))?; let mgr = guard .nostr .as_ref() - .ok_or_else(|| RadrootsAppError::Msg("nostr not initialized".into()))?; + .ok_or_else(|| RadrootsAppError::relay("nostr not initialized"))?; let mut filter = RadrootsNostrFilter::new().kind(RadrootsNostrKind::Custom(KIND_LISTING as u16)); filter = filter.limit(limit.into()); @@ -185,7 +186,7 @@ impl RadrootsRuntime { let events = mgr .fetch_events_blocking(filter, core::time::Duration::from_secs(10)) - .map_err(|error| RadrootsAppError::Msg(error.to_string()))?; + .map_err(|error| RadrootsAppError::relay(error.to_string()))?; let mut out = Vec::new(); for event in events { let event = radroots_event_from_nostr(&event); @@ -199,7 +200,7 @@ impl RadrootsRuntime { #[cfg(not(feature = "nostr-client"))] { let _ = (limit, since_unix); - Err(RadrootsAppError::Msg("nostr disabled".into())) + Err(RadrootsAppError::unsupported("nostr disabled")) } } @@ -216,8 +217,8 @@ impl RadrootsRuntime { listing_id, recipient_pubkey, ); - Err(RadrootsAppError::Msg( - "legacy listing validation requests are retired".into(), + Err(RadrootsAppError::unsupported( + "legacy listing validation requests are retired", )) } @@ -226,8 +227,8 @@ impl RadrootsRuntime { draft: TradeOrderDraft, ) -> Result<TradeOrderSendResult, RadrootsAppError> { let _ = draft; - Err(RadrootsAppError::Msg( - "legacy listing order requests are retired; use active trade order APIs".into(), + Err(RadrootsAppError::unsupported( + "legacy listing order requests are retired; use active trade order APIs", )) } @@ -238,7 +239,9 @@ impl RadrootsRuntime { since_unix: Option<u64>, ) -> Result<Vec<TradeListingMessageSummary>, RadrootsAppError> { let _ = (listing_addr, limit, since_unix); - Ok(Vec::new()) + Err(RadrootsAppError::unsupported( + "legacy listing message fetch is retired", + )) } } @@ -260,9 +263,9 @@ fn listing_from_draft(draft: &TradeListingDraft) -> Result<RadrootsListing, Radr "bin_id", )?; let listing_id = RadrootsDTag::parse(listing_id) - .map_err(|error| RadrootsAppError::Msg(format!("invalid listing_id: {error}")))?; + .map_err(|error| RadrootsAppError::runtime(format!("invalid listing_id: {error}")))?; let bin_id = RadrootsInventoryBinId::parse(bin_id) - .map_err(|error| RadrootsAppError::Msg(format!("invalid bin_id: {error}")))?; + .map_err(|error| RadrootsAppError::runtime(format!("invalid bin_id: {error}")))?; let amount = parse_decimal(&draft.bin_display_amount, "bin_display_amount")?; let unit = parse_unit(&draft.bin_display_unit)?; let canonical_unit = unit.canonical_unit(); @@ -367,19 +370,19 @@ fn current_pubkey_hex(runtime: &RadrootsRuntime) -> Result<String, RadrootsAppEr let guard = runtime .net .lock() - .map_err(|error| RadrootsAppError::Msg(format!("{error}")))?; + .map_err(|error| RadrootsAppError::runtime(format!("{error}")))?; let identity = guard .accounts .default_public_identity() - .map_err(|error| RadrootsAppError::Msg(format!("{error}")))? - .ok_or_else(|| RadrootsAppError::Msg("default account is not configured".into()))?; + .map_err(|error| RadrootsAppError::identity(format!("{error}")))? + .ok_or_else(|| RadrootsAppError::identity("default account is not configured"))?; Ok(identity.public_key_hex) } fn non_empty(value: String, field: &str) -> Result<String, RadrootsAppError> { let value = value.trim().to_string(); if value.is_empty() { - return Err(RadrootsAppError::Msg(format!("{field} is required"))); + return Err(RadrootsAppError::runtime(format!("{field} is required"))); } Ok(value) } @@ -392,17 +395,17 @@ fn blank_to_none(value: Option<String>) -> Option<String> { fn parse_decimal(value: &str, field: &str) -> Result<RadrootsCoreDecimal, RadrootsAppError> { RadrootsCoreDecimal::from_str(value.trim()) - .map_err(|error| RadrootsAppError::Msg(format!("{field} is invalid: {error}"))) + .map_err(|error| RadrootsAppError::runtime(format!("{field} is invalid: {error}"))) } fn parse_currency(value: &str) -> Result<RadrootsCoreCurrency, RadrootsAppError> { RadrootsCoreCurrency::from_str(value.trim()) - .map_err(|error| RadrootsAppError::Msg(format!("currency is invalid: {error}"))) + .map_err(|error| RadrootsAppError::runtime(format!("currency is invalid: {error}"))) } fn parse_unit(value: &str) -> Result<RadrootsCoreUnit, RadrootsAppError> { RadrootsCoreUnit::from_str(value.trim()) - .map_err(|error| RadrootsAppError::Msg(format!("unit is invalid: {error}"))) + .map_err(|error| RadrootsAppError::runtime(format!("unit is invalid: {error}"))) } fn parse_delivery_method(value: &str) -> RadrootsListingDeliveryMethod { diff --git a/crates/field_core/tests/error_mapping.rs b/crates/field_core/tests/error_mapping.rs @@ -0,0 +1,48 @@ +#![cfg(feature = "nostr-client")] + +use radroots_field_core::{RadrootsAppError, RadrootsRuntime}; + +#[test] +fn invalid_host_custody_secret_maps_to_identity_error() { + let runtime = RadrootsRuntime::new().expect("runtime"); + + let err = runtime + .nostr_identity_validate_host_custody_secret("not-a-secret".to_string()) + .expect_err("invalid secret should fail"); + + assert!(matches!(err, RadrootsAppError::Identity(_))); +} + +#[test] +fn uninitialized_nostr_publish_maps_to_relay_error() { + let runtime = RadrootsRuntime::new().expect("runtime"); + + let err = runtime + .nostr_post_text_note("hello".to_string()) + .expect_err("uninitialized nostr should fail"); + + assert!(matches!( + err, + RadrootsAppError::Relay(message) if message == "nostr not initialized" + )); +} + +#[test] +fn retired_trade_operations_map_to_unsupported_error() { + let runtime = RadrootsRuntime::new().expect("runtime"); + + let validation_err = runtime + .trade_listing_send_validation_request( + "event".to_string(), + "seller".to_string(), + "listing".to_string(), + "recipient".to_string(), + ) + .expect_err("retired validation request should fail"); + let messages_err = runtime + .trade_listing_fetch_messages("listing".to_string(), 10, None) + .expect_err("retired message fetch should fail"); + + assert!(matches!(validation_err, RadrootsAppError::Unsupported(_))); + assert!(matches!(messages_err, RadrootsAppError::Unsupported(_))); +} diff --git a/crates/field_core/tests/logging_error.rs b/crates/field_core/tests/logging_error.rs @@ -5,5 +5,5 @@ use radroots_field_core::logging; fn init_logging_stdout_maps_global_subscriber_error() { let _ = tracing_subscriber::fmt().try_init(); let err = logging::init_logging_stdout(); - assert!(matches!(err, Err(RadrootsAppError::Msg(_)))); + assert!(matches!(err, Err(RadrootsAppError::Initialization(_)))); } diff --git a/crates/field_core/tests/no_nostr_runtime.rs b/crates/field_core/tests/no_nostr_runtime.rs @@ -11,7 +11,7 @@ use radroots_net_core::config::NetConfig; fn expect_disabled<T>(result: Result<T, RadrootsAppError>) { match result { - Err(RadrootsAppError::Msg(message)) => assert_eq!(message, "nostr disabled"), + Err(RadrootsAppError::Unsupported(message)) => assert_eq!(message, "nostr disabled"), _ => panic!("expected nostr disabled error"), } } @@ -87,7 +87,7 @@ fn runtime_builder_and_logging_paths_are_exercised() { drop(default_handle); let err = logging::init_logging(Some("/dev/null/file.log".to_string()), None, Some(false)); - assert!(matches!(err, Err(RadrootsAppError::Msg(_)))); + assert!(matches!(err, Err(RadrootsAppError::Initialization(_)))); let _ = logging::init_logging(None, None, None); let _ = logging::init_logging(None, Some("app.log".to_string()), Some(false)); let _ = logging::init_logging_stdout();