lib

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

commit b65d2bcad55bf3c7c55037f2bc1bbe2a6c6a6f61
parent cb1de9a3294b4cdf2f25bd9e8e199051344e943f
Author: triesap <tyson@radroots.org>
Date:   Thu,  6 Aug 2026 06:57:07 +0000

mobile: replace debugderived wire labels

- define exhaustive protocol class labels independent of Rust variant names
- define exhaustive recovery action labels from the protocol authority
- remove generic Debug case conversion from mobile error records
- preserve the versioned secret-safe error wire contract with tests

Diffstat:
Mcrates/mobile_core/src/error.rs | 30+++---------------------------
Mcrates/protocol/src/error/v1.rs | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 69 insertions(+), 27 deletions(-)

diff --git a/crates/mobile_core/src/error.rs b/crates/mobile_core/src/error.rs @@ -34,12 +34,12 @@ impl RadrootsAppError { report: SdkErrorRecord { schema_version: report.schema_version(), code: report.code().as_str().to_owned(), - class: debug_label(report.class()), + class: report.class().as_str().to_owned(), retryable: report.retryable(), recovery_actions: report .recovery_actions() .iter() - .map(|action| debug_label(*action)) + .map(|action| action.as_str().to_owned()) .collect(), operation_id: report.operation_id().map(|id| id.as_str().to_owned()), capability_id: report.capability_id().map(|id| id.as_str().to_owned()), @@ -65,21 +65,10 @@ impl RadrootsAppError { } } -fn debug_label(value: impl std::fmt::Debug) -> String { - let mut label = String::new(); - for (index, character) in format!("{value:?}").chars().enumerate() { - if character.is_ascii_uppercase() && index != 0 { - label.push('_'); - } - label.push(character.to_ascii_lowercase()); - } - label -} - #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { - use super::{RadrootsAppError, SdkErrorRecord, debug_label}; + use super::{RadrootsAppError, SdkErrorRecord}; #[test] fn sdk_error_records_are_versioned_stable_and_secret_safe() { @@ -106,14 +95,6 @@ mod tests { } #[test] - fn debug_labels_use_stable_mobile_case() { - assert_eq!( - debug_label(SampleLabel::RetryAfterClose), - "retry_after_close" - ); - } - - #[test] fn public_error_constructors_preserve_typed_variants() { assert!(matches!( RadrootsAppError::initialization("init"), @@ -132,9 +113,4 @@ mod tests { RadrootsAppError::Internal(message) if message == "internal" )); } - - #[derive(Debug)] - enum SampleLabel { - RetryAfterClose, - } } diff --git a/crates/protocol/src/error/v1.rs b/crates/protocol/src/error/v1.rs @@ -53,6 +53,33 @@ pub enum Class { Unknown, } +impl Class { + /// Returns the stable wire identity for this class. + pub const fn as_str(self) -> &'static str { + match self { + Self::Validation => "validation", + Self::Contract => "contract", + Self::Storage => "storage", + Self::Resource => "resource", + Self::Conflict => "conflict", + Self::Operation => "operation", + Self::Authorization => "authorization", + Self::Signer => "signer", + Self::Network => "network", + Self::Sync => "sync", + Self::Runtime => "runtime", + Self::Projection => "projection", + Self::Query => "query", + Self::Capability => "capability", + Self::Privacy => "privacy", + Self::Security => "security", + Self::Maintenance => "maintenance", + Self::Internal => "internal", + Self::Unknown => "unknown", + } + } +} + /// Stable recovery action vocabulary established by the SDK surface. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] @@ -74,6 +101,30 @@ pub enum RecoveryAction { RecreateClient, } +impl RecoveryAction { + /// Returns the stable wire identity for this recovery action. + pub const fn as_str(self) -> &'static str { + match self { + Self::InspectLocalStores => "inspect_local_stores", + Self::ConfigureStorage => "configure_storage", + Self::InspectGeoNamesAsset => "inspect_geo_names_asset", + Self::RetryOperationWithSameIdempotencyKey => { + "retry_operation_with_same_idempotency_key" + } + Self::ConfigureTransportTargets => "configure_transport_targets", + Self::ConfigureGeoNamesCache => "configure_geo_names_cache", + Self::ConfigureSigner => "configure_signer", + Self::FixRequest => "fix_request", + Self::SelectAuthorizedActor => "select_authorized_actor", + Self::CompleteSignerAuthentication => "complete_signer_authentication", + Self::RetryAfterTransportFailure => "retry_after_transport_failure", + Self::RetryGeoNamesDownload => "retry_geonames_download", + Self::EnableRequiredFeature => "enable_required_feature", + Self::RecreateClient => "recreate_client", + } + } +} + /// One generated catalog descriptor. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Descriptor { @@ -770,6 +821,21 @@ mod tests { use super::*; #[test] + fn class_and_recovery_labels_are_explicit_and_stable() { + assert_eq!(Class::Validation.as_str(), "validation"); + assert_eq!(Class::Unknown.as_str(), "unknown"); + assert_eq!( + RecoveryAction::InspectLocalStores.as_str(), + "inspect_local_stores" + ); + assert_eq!(RecoveryAction::RecreateClient.as_str(), "recreate_client"); + assert_eq!( + RecoveryAction::RetryOperationWithSameIdempotencyKey.as_str(), + "retry_operation_with_same_idempotency_key" + ); + } + + #[test] fn generated_catalog_is_complete_unique_and_self_consistent() { assert_eq!(CATALOG.len(), 63); assert_eq!(KnownCode::ALL.len(), CATALOG.len());