lib

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

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:
MCargo.lock | 1-
Mcrates/authority/Cargo.toml | 1-
Mcrates/authority/src/error.rs | 237+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
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" + ); + } +}