use std::error::Error as StdError;
use std::fmt;
use crate::crypto::CryptoError;
use crate::kdf::TransportKind;
use crate::state::StateError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SealError {
TooShort,
AeadFailed,
MsgpackFailed,
SignatureMissing,
SignatureInvalid,
UntrustedSigner,
}
impl fmt::Display for SealError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooShort => f.write_str("sealed payload too short"),
Self::AeadFailed => f.write_str("AEAD authentication failed"),
Self::MsgpackFailed => f.write_str("malformed msgpack envelope"),
Self::SignatureMissing => f.write_str("signature required but absent"),
Self::SignatureInvalid => f.write_str("signature verification failed"),
Self::UntrustedSigner => f.write_str("signer is not in the trusted set"),
}
}
}
impl StdError for SealError {}
impl From<CryptoError> for SealError {
fn from(e: CryptoError) -> Self {
match e {
CryptoError::TooShort => SealError::TooShort,
CryptoError::AeadFailed => SealError::AeadFailed,
}
}
}
#[derive(Debug)]
pub enum TransportError {
Network(String),
Auth,
Stale,
BodyTooLarge,
Timeout,
Unsupported,
Other(Box<dyn StdError + Send + Sync>),
}
impl fmt::Display for TransportError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Network(msg) => write!(f, "network error: {msg}"),
Self::Auth => f.write_str("authentication failed"),
Self::Stale => f.write_str("slot put rejected: version is not newer"),
Self::BodyTooLarge => f.write_str("body exceeded configured size cap"),
Self::Timeout => f.write_str("request timed out"),
Self::Unsupported => f.write_str("operation is not supported by this transport"),
Self::Other(e) => write!(f, "{e}"),
}
}
}
impl StdError for TransportError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::Other(e) => Some(&**e),
_ => None,
}
}
}
#[derive(Debug)]
pub enum OpenError {
NoTransport,
InvalidSeed,
TransportInit(TransportKind, Box<dyn StdError + Send + Sync>),
State(StateError),
}
impl fmt::Display for OpenError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoTransport => f.write_str("no transport configured"),
Self::InvalidSeed => f.write_str("seed failed validation"),
Self::TransportInit(kind, e) => {
write!(f, "{kind} transport failed to initialize: {e}")
}
Self::State(e) => write!(f, "state store error: {e}"),
}
}
}
impl StdError for OpenError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::TransportInit(_, e) => Some(&**e),
Self::State(e) => Some(e),
_ => None,
}
}
}
impl From<StateError> for OpenError {
fn from(e: StateError) -> Self {
Self::State(e)
}
}
#[derive(Debug)]
pub enum SendError {
AllTransportsFailed(Vec<(TransportKind, TransportError)>),
Sealing(SealError),
}
impl fmt::Display for SendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AllTransportsFailed(failures) => format_all_failed(f, failures),
Self::Sealing(e) => write!(f, "sealing failed: {e}"),
}
}
}
impl StdError for SendError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::Sealing(e) => Some(e),
Self::AllTransportsFailed(_) => None,
}
}
}
impl From<SealError> for SendError {
fn from(e: SealError) -> Self {
Self::Sealing(e)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecvError {
Closed,
}
impl fmt::Display for RecvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Closed => f.write_str("namespace closed"),
}
}
}
impl StdError for RecvError {}
#[derive(Debug)]
pub enum SlotError {
AllTransportsFailed(Vec<(TransportKind, TransportError)>),
Sealing(SealError),
State(StateError),
}
impl fmt::Display for SlotError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AllTransportsFailed(failures) => format_all_failed(f, failures),
Self::Sealing(e) => write!(f, "sealing failed: {e}"),
Self::State(e) => write!(f, "state store error: {e}"),
}
}
}
impl StdError for SlotError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::Sealing(e) => Some(e),
Self::State(e) => Some(e),
Self::AllTransportsFailed(_) => None,
}
}
}
impl From<SealError> for SlotError {
fn from(e: SealError) -> Self {
Self::Sealing(e)
}
}
impl From<StateError> for SlotError {
fn from(e: StateError) -> Self {
Self::State(e)
}
}
fn format_all_failed(
f: &mut fmt::Formatter<'_>,
failures: &[(TransportKind, TransportError)],
) -> fmt::Result {
write!(f, "all {} transport(s) failed", failures.len())?;
let mut sep = ": ";
for (kind, err) in failures {
write!(f, "{sep}{kind}: {err}")?;
sep = ", ";
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seal_error_display_strings() {
assert_eq!(SealError::TooShort.to_string(), "sealed payload too short");
assert_eq!(
SealError::AeadFailed.to_string(),
"AEAD authentication failed"
);
assert_eq!(
SealError::MsgpackFailed.to_string(),
"malformed msgpack envelope"
);
assert_eq!(
SealError::SignatureMissing.to_string(),
"signature required but absent"
);
assert_eq!(
SealError::SignatureInvalid.to_string(),
"signature verification failed"
);
assert_eq!(
SealError::UntrustedSigner.to_string(),
"signer is not in the trusted set"
);
}
#[test]
fn seal_error_no_source() {
assert!(StdError::source(&SealError::TooShort).is_none());
}
#[test]
fn seal_error_from_crypto_error() {
assert_eq!(SealError::from(CryptoError::TooShort), SealError::TooShort);
assert_eq!(
SealError::from(CryptoError::AeadFailed),
SealError::AeadFailed
);
}
#[test]
fn transport_error_display_unit_variants() {
assert_eq!(TransportError::Auth.to_string(), "authentication failed");
assert_eq!(
TransportError::Stale.to_string(),
"slot put rejected: version is not newer"
);
assert_eq!(
TransportError::BodyTooLarge.to_string(),
"body exceeded configured size cap"
);
assert_eq!(TransportError::Timeout.to_string(), "request timed out");
assert_eq!(
TransportError::Unsupported.to_string(),
"operation is not supported by this transport"
);
}
#[test]
fn transport_error_network_includes_message() {
let e = TransportError::Network("connection refused".into());
assert_eq!(e.to_string(), "network error: connection refused");
}
#[test]
fn transport_error_other_carries_source() {
let inner = std::io::Error::other("disk full");
let e = TransportError::Other(Box::new(inner));
assert_eq!(e.to_string(), "disk full");
assert!(StdError::source(&e).is_some());
}
#[test]
fn transport_error_unit_variants_have_no_source() {
assert!(StdError::source(&TransportError::Auth).is_none());
assert!(StdError::source(&TransportError::Stale).is_none());
assert!(StdError::source(&TransportError::Timeout).is_none());
assert!(StdError::source(&TransportError::Unsupported).is_none());
assert!(StdError::source(&TransportError::Network("x".into())).is_none());
}
#[test]
fn open_error_display_strings() {
assert_eq!(
OpenError::NoTransport.to_string(),
"no transport configured"
);
assert_eq!(OpenError::InvalidSeed.to_string(), "seed failed validation");
}
#[test]
fn open_error_transport_init_includes_kind() {
let inner = std::io::Error::other("bind failed");
let e = OpenError::TransportInit(TransportKind::Iroh, Box::new(inner));
assert_eq!(
e.to_string(),
"iroh transport failed to initialize: bind failed",
);
assert!(StdError::source(&e).is_some());
}
#[test]
fn open_error_state_chains_to_state_error() {
let inner = StateError::Corrupted("bad keypair length".into());
let e: OpenError = inner.into();
assert_eq!(
e.to_string(),
"state store error: state corrupted: bad keypair length"
);
let chain = StdError::source(&e).expect("State variant has a source");
assert_eq!(chain.to_string(), "state corrupted: bad keypair length");
}
#[test]
fn send_error_sealing_chains() {
let e: SendError = SealError::SignatureInvalid.into();
assert_eq!(
e.to_string(),
"sealing failed: signature verification failed"
);
let chain = StdError::source(&e).expect("Sealing variant has a source");
assert_eq!(chain.to_string(), "signature verification failed");
}
#[test]
fn send_error_all_failed_lists_each_transport() {
let failures = vec![
(TransportKind::Http, TransportError::Auth),
(
TransportKind::Pkarr,
TransportError::Network("dns refused".into()),
),
];
let e = SendError::AllTransportsFailed(failures);
assert_eq!(
e.to_string(),
"all 2 transport(s) failed: http: authentication failed, pkarr: network error: dns refused",
);
assert!(StdError::source(&e).is_none());
}
#[test]
fn recv_error_display_and_no_source() {
assert_eq!(RecvError::Closed.to_string(), "namespace closed");
assert!(StdError::source(&RecvError::Closed).is_none());
}
#[test]
fn slot_error_sealing_chains() {
let e: SlotError = SealError::AeadFailed.into();
assert_eq!(e.to_string(), "sealing failed: AEAD authentication failed");
assert!(StdError::source(&e).is_some());
}
#[test]
fn slot_error_state_chains() {
let e: SlotError = StateError::Corrupted("oops".into()).into();
assert_eq!(e.to_string(), "state store error: state corrupted: oops");
let chain = StdError::source(&e).expect("State variant has a source");
assert_eq!(chain.to_string(), "state corrupted: oops");
}
#[test]
fn slot_error_all_failed_lists_single_transport() {
let failures = vec![(TransportKind::Dht, TransportError::Stale)];
let e = SlotError::AllTransportsFailed(failures);
assert_eq!(
e.to_string(),
"all 1 transport(s) failed: dht: slot put rejected: version is not newer",
);
}
#[test]
fn transport_kind_display_lowercase_token() {
assert_eq!(TransportKind::Http.to_string(), "http");
assert_eq!(TransportKind::Pkarr.to_string(), "pkarr");
assert_eq!(TransportKind::Dht.to_string(), "dht");
assert_eq!(TransportKind::Iroh.to_string(), "iroh");
}
}