#[cfg(feature = "dcbor")]
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::ur::UrType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Limit {
MessageLength,
FragmentCount,
FragmentLength,
UriLength,
}
impl core::fmt::Display for Limit {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::MessageLength => "message length",
Self::FragmentCount => "fragment count",
Self::FragmentLength => "fragment length",
Self::UriLength => "URI length",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
NonAscii,
InvalidWord,
InvalidBytewordsLength,
InvalidBytewordsChecksum,
InvalidScheme,
TypeUnspecified,
InvalidType,
InvalidIndices,
UnexpectedType,
InvalidPartCbor,
InvalidPart,
InconsistentPart,
ResourceLimit,
InvalidPadding,
InvalidMessageChecksum,
EmptyMessage,
InvalidFragmentLength,
MessageTooLong,
NotSinglePart,
NotComplete,
CborDecode,
CborType,
Internal,
}
impl ErrorKind {
const fn message(self) -> &'static str {
match self {
Self::NonAscii => "bytewords string is not ASCII",
Self::InvalidWord => "invalid bytewords word",
Self::InvalidBytewordsLength => "invalid bytewords length",
Self::InvalidBytewordsChecksum => "invalid bytewords checksum",
Self::InvalidScheme => "invalid UR scheme",
Self::TypeUnspecified => "UR type unspecified",
Self::InvalidType => "invalid UR type",
Self::InvalidIndices => "invalid multi-part indices",
Self::UnexpectedType => "unexpected UR type",
Self::InvalidPartCbor => "invalid fountain part CBOR",
Self::InvalidPart => "invalid fountain part",
Self::InconsistentPart => "fountain part inconsistent with previous parts",
Self::ResourceLimit => "resource limit exceeded",
Self::InvalidPadding => "invalid fountain part padding",
Self::InvalidMessageChecksum => "invalid fountain message checksum",
Self::EmptyMessage => "empty message",
Self::InvalidFragmentLength => "invalid fragment length",
Self::MessageTooLong => "message too long",
Self::NotSinglePart => "expected single-part UR",
Self::NotComplete => "decoder is not complete",
Self::CborDecode => "dCBOR decode failed",
Self::CborType => "dCBOR type mismatch",
Self::Internal => "internal error",
}
}
}
#[derive(Debug, Clone)]
enum Detail {
None,
Limit(Limit),
UnexpectedType {
expected: Vec<UrType>,
found: UrType,
},
#[cfg(feature = "dcbor")]
Cbor {
source: Arc<dcbor::Error>,
},
}
#[derive(Debug, Clone)]
pub struct Error {
kind: ErrorKind,
detail: Detail,
}
impl Error {
#[must_use]
pub const fn kind(&self) -> ErrorKind {
self.kind
}
#[must_use]
pub const fn is_fatal(&self) -> bool {
matches!(
self.kind,
ErrorKind::ResourceLimit
| ErrorKind::InvalidPadding
| ErrorKind::InvalidMessageChecksum
| ErrorKind::Internal
)
}
#[must_use]
pub const fn limit(&self) -> Option<Limit> {
match &self.detail {
Detail::Limit(limit) => Some(*limit),
_ => None,
}
}
#[must_use]
pub fn expected_types(&self) -> &[UrType] {
match &self.detail {
Detail::UnexpectedType { expected, .. } => expected,
_ => &[],
}
}
#[must_use]
pub const fn found_type(&self) -> Option<&UrType> {
match &self.detail {
Detail::UnexpectedType { found, .. } => Some(found),
_ => None,
}
}
pub(crate) const fn new(kind: ErrorKind) -> Self {
Self {
kind,
detail: Detail::None,
}
}
pub(crate) const fn internal() -> Self {
Self::new(ErrorKind::Internal)
}
pub(crate) const fn resource_limit(limit: Limit) -> Self {
Self {
kind: ErrorKind::ResourceLimit,
detail: Detail::Limit(limit),
}
}
pub(crate) const fn unexpected_type(expected: Vec<UrType>, found: UrType) -> Self {
Self {
kind: ErrorKind::UnexpectedType,
detail: Detail::UnexpectedType { expected, found },
}
}
#[cfg(feature = "dcbor")]
pub(crate) fn cbor(kind: ErrorKind, source: dcbor::Error) -> Self {
Self {
kind,
detail: Detail::Cbor {
source: Arc::new(source),
},
}
}
}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
if self.kind != other.kind {
return false;
}
match (&self.detail, &other.detail) {
(Detail::None, Detail::None) => true,
(Detail::Limit(a), Detail::Limit(b)) => a == b,
(
Detail::UnexpectedType {
expected: ae,
found: af,
},
Detail::UnexpectedType {
expected: be,
found: bf,
},
) => ae == be && af == bf,
#[cfg(feature = "dcbor")]
(Detail::Cbor { .. }, Detail::Cbor { .. }) => true,
#[allow(unreachable_patterns, reason = "dcbor-gated variants")]
_ => false,
}
}
}
impl Eq for Error {}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match &self.detail {
Detail::Limit(limit) => {
write!(f, "{}: {limit}", self.kind.message())
}
Detail::UnexpectedType { expected, found } => {
let expected = expected
.iter()
.map(UrType::as_str)
.collect::<Vec<_>>()
.join(", ");
write!(
f,
"{}: expected {expected}, found {}",
self.kind.message(),
found.as_str()
)
}
Detail::None => f.write_str(self.kind.message()),
#[cfg(feature = "dcbor")]
Detail::Cbor { .. } => f.write_str(self.kind.message()),
}
}
}
impl core::error::Error for Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match &self.detail {
#[cfg(feature = "dcbor")]
Detail::Cbor { source } => Some(&**source),
_ => None,
}
}
}
pub type Result<T> = core::result::Result<T, Error>;