commit 5608af9dbb29ba340f41e62bdf7fcece594f16e0
parent 653936410c7993662fea1e49453937ca79c4036f
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:
7 files changed, 120 insertions(+), 7 deletions(-)
diff --git a/crates/studio_domain/src/account.rs b/crates/studio_domain/src/account.rs
@@ -0,0 +1 @@
+//! Public account metadata and lifecycle values.
diff --git a/crates/studio_domain/src/error.rs b/crates/studio_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/crates/studio_domain/src/key.rs b/crates/studio_domain/src/key.rs
@@ -0,0 +1 @@
+//! Validated Nostr public and secret-key boundary values.
diff --git a/crates/studio_domain/src/lib.rs b/crates/studio_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/crates/studio_domain/src/profile.rs b/crates/studio_domain/src/profile.rs
@@ -0,0 +1 @@
+//! Public Nostr profile metadata values.
diff --git a/crates/studio_domain/src/relay.rs b/crates/studio_domain/src/relay.rs
@@ -0,0 +1 @@
+//! Validated Nostr relay values.
diff --git a/crates/studio_domain/src/time.rs b/crates/studio_domain/src/time.rs
@@ -0,0 +1 @@
+//! Time values shared by account and profile records.