Skip to main content

cadmpeg_core/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Errors returned by codec parsing and resource enforcement.
3
4use crate::decode::{ErrorContext, ResourceLimit, SourceLocation};
5
6/// Errors a codec can raise.
7///
8/// Marked `#[non_exhaustive]`: external exhaustive matches must carry a
9/// wildcard arm.
10#[derive(Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum CodecError {
13    /// The bytes are not this codec's format.
14    #[error("not the expected format: {0}")]
15    WrongFormat(String),
16    /// The container was structurally malformed.
17    #[error("malformed container: {0}")]
18    Malformed(String),
19    /// The document supplied to an encoder violates its input contract.
20    #[error("invalid encoder input: {0}")]
21    InvalidInput(String),
22    /// A required read extended past the end of its window after commitment.
23    ///
24    /// Distinct from [`CodecError::Malformed`]: a truncation is missing input,
25    /// not an inconsistency inside the bytes that are present.
26    #[error(
27        "truncated input during {} at space {} offset {}",
28        .context.operation, .location.space.index(), .location.offset
29    )]
30    Truncated {
31        /// Where the truncated read began.
32        location: SourceLocation,
33        /// Static context for the failure.
34        context: ErrorContext,
35    },
36    /// A resource limit refused the decode: policy or the allocator.
37    ///
38    /// Never reported as [`CodecError::Malformed`]: a budget refusal is a
39    /// statement about policy, not about the input.
40    #[error(
41        "resource limit on {:?}: {:?} (limit {}, used {}, requested {})",
42        .0.dimension, .0.reason, .0.limit, .0.used, .0.additional
43    )]
44    ResourceLimit(ResourceLimit),
45    /// Strict decode mode refused a reported loss.
46    ///
47    /// Never reported as [`CodecError::Malformed`]: a strict refusal is a
48    /// statement about the decode mode, not about the input. The bytes can be
49    /// well formed and still refuse under strict mode. The strict-mode gate in
50    /// the `Codec` decode wrapper is the only construction site.
51    #[error("strict mode rejects {loss_code}: {message}")]
52    StrictRefusal {
53        /// Stable `namespace/code` form of the refusing loss.
54        loss_code: String,
55        /// The refusing loss's own message, without any refusal prefix.
56        message: String,
57    },
58    /// The codec does not implement a required capability.
59    #[error("not implemented yet: {0}")]
60    NotImplemented(String),
61    /// Underlying I/O failure.
62    #[error("io error: {0}")]
63    Io(#[from] std::io::Error),
64}
65
66impl CodecError {
67    /// Builds a malformed-container error from a displayable message.
68    pub fn malformed(message: impl std::fmt::Display) -> Self {
69        Self::Malformed(message.to_string())
70    }
71
72    /// Builds a truncation error at a qualified source location.
73    pub const fn truncated(location: SourceLocation, operation: &'static str) -> Self {
74        Self::Truncated {
75            location,
76            context: ErrorContext {
77                operation,
78                location: Some(location),
79            },
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::CodecError;
87
88    #[test]
89    fn malformed_constructor_formats_the_message_once() {
90        let error = CodecError::malformed(format_args!("field {} is invalid", 7));
91
92        assert_eq!(error.to_string(), "malformed container: field 7 is invalid");
93    }
94
95    #[test]
96    fn a_strict_refusal_names_the_loss_and_claims_no_container_defect() {
97        let error = CodecError::StrictRefusal {
98            loss_code: "step/parse.noncanonical-syntax".into(),
99            message: "complex partial records are not alphabetical".into(),
100        };
101
102        assert_eq!(
103            error.to_string(),
104            "strict mode rejects step/parse.noncanonical-syntax: complex partial \
105             records are not alphabetical"
106        );
107    }
108}