commit 3601cef97efbe31b86c57de8cd80aa17fef3d06a
parent a6f662f4d42e1591c6cfc3fdc614c20df5c2fdd7
Author: triesap <tyson@radroots.org>
Date: Sun, 14 Jun 2026 15:22:23 -0700
authority: restore no-std errors
- replace std-bound thiserror derives with portable display impls
- preserve authority and signer error conversion behavior
- keep std Error source wiring behind the std feature
- add display/source assertions for the rewritten error surface
Diffstat:
3 files changed, 203 insertions(+), 36 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3967,7 +3967,6 @@ version = "0.1.0-alpha.2"
dependencies = [
"radroots_events",
"radroots_nostr",
- "thiserror 1.0.69",
]
[[package]]
diff --git a/crates/authority/Cargo.toml b/crates/authority/Cargo.toml
@@ -24,7 +24,6 @@ local_signer = [
[dependencies]
radroots_events = { workspace = true, default-features = false }
radroots_nostr = { workspace = true, optional = true, default-features = false }
-thiserror = { workspace = true }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }
diff --git a/crates/authority/src/error.rs b/crates/authority/src/error.rs
@@ -1,121 +1,290 @@
#![forbid(unsafe_code)]
-use thiserror::Error;
+use core::fmt;
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
#[cfg(feature = "std")]
use std::{string::String, vec::Vec};
-#[derive(Debug, Error, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq)]
pub enum RadrootsAuthorityError {
- #[error("invalid actor public key")]
InvalidActorPubkey,
- #[error("invalid actor account id: empty")]
InvalidActorAccountIdEmpty,
- #[error("invalid actor account id: contains leading or trailing whitespace")]
InvalidActorAccountIdUntrimmed,
- #[error("invalid actor account id: contains a control character")]
InvalidActorAccountIdControlCharacter,
- #[error("invalid actor account id: longer than {max_len} characters")]
- InvalidActorAccountIdTooLong { max_len: usize },
+ InvalidActorAccountIdTooLong {
+ max_len: usize,
+ },
- #[error("invalid signer public key")]
InvalidSignerPubkey,
- #[error("unknown event contract `{contract_id}`")]
- UnknownContract { contract_id: String },
+ UnknownContract {
+ contract_id: String,
+ },
- #[error("event contract `{contract_id}` expects kind {expected_kind}, got {actual_kind}")]
DraftKindMismatch {
contract_id: String,
expected_kind: u32,
actual_kind: u32,
},
- #[error("actor does not satisfy role {required_role:?} for contract `{contract_id}`")]
ActorRoleUnsatisfied {
contract_id: String,
required_role: radroots_events::contract::RadrootsActorRole,
},
- #[error("actor pubkey mismatch: expected {expected_pubkey}, got {actor_pubkey}")]
ActorPubkeyMismatch {
expected_pubkey: String,
actor_pubkey: String,
},
- #[error("signer pubkey mismatch: expected {expected_pubkey}, got {signer_pubkey}")]
SignerPubkeyMismatch {
expected_pubkey: String,
signer_pubkey: String,
},
- #[error("signed event pubkey mismatch: expected {expected_pubkey}, got {actual_pubkey}")]
SignedEventPubkeyMismatch {
expected_pubkey: String,
actual_pubkey: String,
},
- #[error("signed event id mismatch: expected {expected_event_id}, got {actual_event_id}")]
SignedEventIdMismatch {
expected_event_id: String,
actual_event_id: String,
},
- #[error(
- "signed event created_at mismatch: expected {expected_created_at}, got {actual_created_at}"
- )]
SignedEventCreatedAtMismatch {
expected_created_at: u32,
actual_created_at: u32,
},
- #[error("signed event kind mismatch: expected {expected_kind}, got {actual_kind}")]
SignedEventKindMismatch {
expected_kind: u32,
actual_kind: u32,
},
- #[error("signed event tags mismatch: expected {expected_tags:?}, got {actual_tags:?}")]
SignedEventTagsMismatch {
expected_tags: Vec<Vec<String>>,
actual_tags: Vec<Vec<String>>,
},
- #[error("signed event content mismatch")]
SignedEventContentMismatch {
expected_content: String,
actual_content: String,
},
- #[error("signed event computed id could not be derived: {message}")]
- SignedEventComputedIdInvalid { message: String },
+ SignedEventComputedIdInvalid {
+ message: String,
+ },
- #[error(
- "signed event computed id mismatch: expected {expected_event_id}, computed {computed_event_id}"
- )]
SignedEventComputedIdMismatch {
expected_event_id: String,
computed_event_id: String,
},
- #[error("signer error: {0}")]
- Signer(#[from] RadrootsSignerError),
+ Signer(RadrootsSignerError),
+}
+
+impl fmt::Display for RadrootsAuthorityError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::InvalidActorPubkey => write!(f, "invalid actor public key"),
+ Self::InvalidActorAccountIdEmpty => write!(f, "invalid actor account id: empty"),
+ Self::InvalidActorAccountIdUntrimmed => {
+ write!(
+ f,
+ "invalid actor account id: contains leading or trailing whitespace"
+ )
+ }
+ Self::InvalidActorAccountIdControlCharacter => {
+ write!(f, "invalid actor account id: contains a control character")
+ }
+ Self::InvalidActorAccountIdTooLong { max_len } => {
+ write!(
+ f,
+ "invalid actor account id: longer than {max_len} characters"
+ )
+ }
+ Self::InvalidSignerPubkey => write!(f, "invalid signer public key"),
+ Self::UnknownContract { contract_id } => {
+ write!(f, "unknown event contract `{contract_id}`")
+ }
+ Self::DraftKindMismatch {
+ contract_id,
+ expected_kind,
+ actual_kind,
+ } => write!(
+ f,
+ "event contract `{contract_id}` expects kind {expected_kind}, got {actual_kind}"
+ ),
+ Self::ActorRoleUnsatisfied {
+ contract_id,
+ required_role,
+ } => write!(
+ f,
+ "actor does not satisfy role {required_role:?} for contract `{contract_id}`"
+ ),
+ Self::ActorPubkeyMismatch {
+ expected_pubkey,
+ actor_pubkey,
+ } => write!(
+ f,
+ "actor pubkey mismatch: expected {expected_pubkey}, got {actor_pubkey}"
+ ),
+ Self::SignerPubkeyMismatch {
+ expected_pubkey,
+ signer_pubkey,
+ } => write!(
+ f,
+ "signer pubkey mismatch: expected {expected_pubkey}, got {signer_pubkey}"
+ ),
+ Self::SignedEventPubkeyMismatch {
+ expected_pubkey,
+ actual_pubkey,
+ } => write!(
+ f,
+ "signed event pubkey mismatch: expected {expected_pubkey}, got {actual_pubkey}"
+ ),
+ Self::SignedEventIdMismatch {
+ expected_event_id,
+ actual_event_id,
+ } => write!(
+ f,
+ "signed event id mismatch: expected {expected_event_id}, got {actual_event_id}"
+ ),
+ Self::SignedEventCreatedAtMismatch {
+ expected_created_at,
+ actual_created_at,
+ } => write!(
+ f,
+ "signed event created_at mismatch: expected {expected_created_at}, got {actual_created_at}"
+ ),
+ Self::SignedEventKindMismatch {
+ expected_kind,
+ actual_kind,
+ } => write!(
+ f,
+ "signed event kind mismatch: expected {expected_kind}, got {actual_kind}"
+ ),
+ Self::SignedEventTagsMismatch {
+ expected_tags,
+ actual_tags,
+ } => write!(
+ f,
+ "signed event tags mismatch: expected {expected_tags:?}, got {actual_tags:?}"
+ ),
+ Self::SignedEventContentMismatch { .. } => {
+ write!(f, "signed event content mismatch")
+ }
+ Self::SignedEventComputedIdInvalid { message } => {
+ write!(
+ f,
+ "signed event computed id could not be derived: {message}"
+ )
+ }
+ Self::SignedEventComputedIdMismatch {
+ expected_event_id,
+ computed_event_id,
+ } => write!(
+ f,
+ "signed event computed id mismatch: expected {expected_event_id}, computed {computed_event_id}"
+ ),
+ Self::Signer(error) => write!(f, "signer error: {error}"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RadrootsAuthorityError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::Signer(error) => Some(error),
+ _ => None,
+ }
+ }
+}
+
+impl From<RadrootsSignerError> for RadrootsAuthorityError {
+ fn from(error: RadrootsSignerError) -> Self {
+ Self::Signer(error)
+ }
}
-#[derive(Debug, Error, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq)]
pub enum RadrootsSignerError {
- #[error("signer unavailable")]
Unavailable,
- #[error("signer rejected draft")]
Rejected,
- #[error("signing failed: {message}")]
SigningFailed { message: String },
}
+
+impl fmt::Display for RadrootsSignerError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Unavailable => write!(f, "signer unavailable"),
+ Self::Rejected => write!(f, "signer rejected draft"),
+ Self::SigningFailed { message } => write!(f, "signing failed: {message}"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RadrootsSignerError {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::error::Error as _;
+
+ #[test]
+ fn authority_error_display_uses_contract_messages() {
+ assert_eq!(
+ RadrootsAuthorityError::InvalidActorPubkey.to_string(),
+ "invalid actor public key"
+ );
+ assert_eq!(
+ RadrootsAuthorityError::DraftKindMismatch {
+ contract_id: "radroots.social.post.v1".to_owned(),
+ expected_kind: 1,
+ actual_kind: 2,
+ }
+ .to_string(),
+ "event contract `radroots.social.post.v1` expects kind 1, got 2"
+ );
+ assert_eq!(
+ RadrootsAuthorityError::SignedEventTagsMismatch {
+ expected_tags: vec![vec!["t".to_owned(), "soil".to_owned()]],
+ actual_tags: vec![vec!["t".to_owned(), "seed".to_owned()]],
+ }
+ .to_string(),
+ "signed event tags mismatch: expected [[\"t\", \"soil\"]], got [[\"t\", \"seed\"]]"
+ );
+ }
+
+ #[test]
+ fn signer_error_display_and_source_are_stable() {
+ let signer_error = RadrootsSignerError::SigningFailed {
+ message: "deterministic failure".to_owned(),
+ };
+ assert_eq!(
+ signer_error.to_string(),
+ "signing failed: deterministic failure"
+ );
+
+ let authority_error = RadrootsAuthorityError::from(signer_error);
+ assert_eq!(
+ authority_error.to_string(),
+ "signer error: signing failed: deterministic failure"
+ );
+ assert_eq!(
+ authority_error.source().expect("signer source").to_string(),
+ "signing failed: deterministic failure"
+ );
+ }
+}