use crate::decode::{DecodeDepth, ResourceLimit, SourceLocation};
use crate::dialect::DialectLayers;
use crate::target::TargetRefusal;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CodecError {
#[error("not the expected format: {0}")]
WrongFormat(String),
#[error("malformed container: {0}")]
Malformed(String),
#[error("invalid encoder input: {0}")]
InvalidInput(String),
#[error(
"truncated input during {} at space {} offset {}",
.operation, .location.space.index(), .location.offset
)]
Truncated {
location: SourceLocation,
operation: &'static str,
},
#[error(
"resource limit on {:?}: {:?} (limit {}, used {}, requested {})",
.0.dimension, .0.reason, .0.limit, .0.used, .0.additional
)]
ResourceLimit(ResourceLimit),
#[error("unsupported {} dialect {}: {message}", .dialects.primary().format(), .dialects.primary().dialect())]
UnsupportedDialect {
dialects: Box<DialectLayers>,
message: String,
},
#[error("{0}")]
UnsupportedTarget(Box<TargetRefusal>),
#[error(
"the {format} encoder needs a {needed}-depth document; this one records {recorded} depth"
)]
InsufficientDepth {
format: &'static str,
needed: DecodeDepth,
recorded: DecodeDepth,
},
#[error("not implemented yet: {0}")]
NotImplemented(String),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
}
impl From<ResourceLimit> for CodecError {
fn from(limit: ResourceLimit) -> Self {
Self::ResourceLimit(limit)
}
}
impl CodecError {
pub fn malformed(message: impl std::fmt::Display) -> Self {
Self::Malformed(message.to_string())
}
pub const fn truncated(location: SourceLocation, operation: &'static str) -> Self {
Self::Truncated {
location,
operation,
}
}
}
impl From<TargetRefusal> for CodecError {
fn from(refusal: TargetRefusal) -> Self {
Self::UnsupportedTarget(Box::new(refusal))
}
}
impl From<crate::text::NamedEntryError> for CodecError {
fn from(refused: crate::text::NamedEntryError) -> Self {
Self::malformed(refused)
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::CodecError;
use crate::dialect::{DialectLayers, DialectMatch};
#[test]
fn malformed_constructor_formats_the_message_once() {
let error = CodecError::malformed(format_args!("field {} is invalid", 7));
assert_eq!(error.to_string(), "malformed container: field 7 is invalid");
}
#[test]
fn a_dialect_refusal_keeps_the_identification_it_refused() {
let error = CodecError::UnsupportedDialect {
dialects: Box::new(
DialectLayers::of(
DialectMatch::refused(crate::dialect_id!("acis:save-format-binary-other"))
.with_declared(BTreeMap::from([(
crate::nonblank_literal!("save_format"),
"700".to_owned(),
)])),
)
.with(DialectMatch::refused(crate::dialect_id!("sat:binary")))
.expect("distinct dialect layer keys"),
),
message: "save format 700 has no read grammar".into(),
};
assert_eq!(
error.to_string(),
"unsupported acis dialect acis:save-format-binary-other: save format 700 has no read grammar"
);
let CodecError::UnsupportedDialect { dialects, .. } = &error else {
panic!("the variant just built is the one matched");
};
assert_eq!(
dialects.primary().dialect().as_str(),
"acis:save-format-binary-other"
);
assert_eq!(dialects.primary().declared()["save_format"], "700");
assert_eq!(dialects.iter().count(), 2);
}
}