app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 41bbb6e18c119fbce09d8d805e815fcdf87158ca
parent 2c90672efbbfbffa5bd10a14341dfa8205d649cb
Author: triesap <tyson@radroots.org>
Date:   Sun,  2 Aug 2026 18:00:29 +0000

core(domain): define module layout and safe errors

- separate account, key, profile, relay, and time modules
- introduce stable machine-readable safe error codes
- constrain user-facing errors to static reviewed messages
- prove error formatting cannot include hidden secret payloads

Diffstat:
Acore/crates/domain/src/account.rs | 1+
Acore/crates/domain/src/error.rs | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/crates/domain/src/key.rs | 1+
Mcore/crates/domain/src/lib.rs | 15++++++++-------
Acore/crates/domain/src/profile.rs | 1+
Acore/crates/domain/src/relay.rs | 1+
Acore/crates/domain/src/time.rs | 1+
Mdocs/implementation/nostr-runtime-rcld.md | 2+-
8 files changed, 121 insertions(+), 8 deletions(-)

diff --git a/core/crates/domain/src/account.rs b/core/crates/domain/src/account.rs @@ -0,0 +1 @@ +//! Public account metadata and lifecycle values. diff --git a/core/crates/domain/src/error.rs b/core/crates/domain/src/error.rs @@ -0,0 +1,107 @@ +use std::error::Error; +use std::fmt::{self, Debug, Display, Formatter}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum SafeErrorCode { + InvalidPublicKey, + InvalidSecretKey, + AccountAlreadyExists, + AccountNotFound, + KeyringUnavailable, + CredentialMissing, + StorageUnavailable, + StorageCorrupt, + PendingOperationRecoveryRequired, + InvalidRelayConfiguration, + RelayConnectionFailed, + ProfileRefreshFailed, + ObserverRegistrationFailed, + NativeLibraryLoadFailed, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct SafeMessage(&'static str); + +impl SafeMessage { + #[must_use] + pub const fn new(message: &'static str) -> Self { + Self(message) + } + + #[must_use] + pub const fn as_str(self) -> &'static str { + self.0 + } +} + +impl Debug for SafeMessage { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.debug_tuple("SafeMessage").field(&self.0).finish() + } +} + +impl Display for SafeMessage { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.write_str(self.0) + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct SafeError { + code: SafeErrorCode, + message: SafeMessage, +} + +impl SafeError { + #[must_use] + pub const fn new(code: SafeErrorCode, message: SafeMessage) -> Self { + Self { code, message } + } + + #[must_use] + pub const fn code(self) -> SafeErrorCode { + self.code + } + + #[must_use] + pub const fn message(self) -> SafeMessage { + self.message + } +} + +impl Debug for SafeError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("SafeError") + .field("code", &self.code) + .field("message", &self.message) + .finish() + } +} + +impl Display for SafeError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.message, formatter) + } +} + +impl Error for SafeError {} + +#[cfg(test)] +mod tests { + use super::{SafeError, SafeErrorCode, SafeMessage}; + + #[test] + fn safe_error_formats_only_a_static_public_message() { + let error = SafeError::new( + SafeErrorCode::InvalidSecretKey, + SafeMessage::new("The secret key is invalid."), + ); + + assert_eq!(error.to_string(), "The secret key is invalid."); + assert_eq!(error.code(), SafeErrorCode::InvalidSecretKey); + assert_eq!(error.message().as_str(), "The secret key is invalid."); + assert!(!format!("{error:?}").contains("nsec1unsafe-test-value")); + } +} diff --git a/core/crates/domain/src/key.rs b/core/crates/domain/src/key.rs @@ -0,0 +1 @@ +//! Validated Nostr public and secret-key boundary values. diff --git a/core/crates/domain/src/lib.rs b/core/crates/domain/src/lib.rs @@ -1,9 +1,10 @@ #![doc = "Radroots Studio Nostr account domain types."] -#[cfg(test)] -mod tests { - #[test] - fn crate_is_available_to_the_workspace() { - assert_eq!(env!("CARGO_PKG_NAME"), "radroots-studio-domain"); - } -} +pub mod account; +pub mod error; +pub mod key; +pub mod profile; +pub mod relay; +pub mod time; + +pub use error::{SafeError, SafeErrorCode, SafeMessage}; diff --git a/core/crates/domain/src/profile.rs b/core/crates/domain/src/profile.rs @@ -0,0 +1 @@ +//! Public Nostr profile metadata values. diff --git a/core/crates/domain/src/relay.rs b/core/crates/domain/src/relay.rs @@ -0,0 +1 @@ +//! Validated Nostr relay values. diff --git a/core/crates/domain/src/time.rs b/core/crates/domain/src/time.rs @@ -0,0 +1 @@ +//! Time values shared by account and profile records. diff --git a/docs/implementation/nostr-runtime-rcld.md b/docs/implementation/nostr-runtime-rcld.md @@ -456,7 +456,7 @@ handoff commit sequence. - [x] 02. Establish root Rust workspace skeleton. - [x] 03. Add Rust formatting, lint, and governed check hooks. Use Cargo, Gradle, and Makefile; do not add CI workflows or scripts. -- [ ] 04. Define domain module layout and safe error shell. +- [x] 04. Define domain module layout and safe error shell. - [ ] 05. Implement Nostr public key value object. - [ ] 06. Implement secret input boundary and redacted secret wrapper. - [ ] 07. Add NIP-19 public/secret display contract types.