Skip to main content

draco_core/
status.rs

1use thiserror::Error;
2
3/// Error returned by Draco decoding, encoding, and data model operations.
4#[derive(Error, Debug, Clone, PartialEq)]
5pub enum DracoError {
6    /// Generic Draco error with a human-readable message.
7    #[error("General error: {0}")]
8    DracoError(String),
9    /// File or stream I/O error.
10    #[error("IO error: {0}")]
11    IoError(String),
12    /// Invalid caller-provided parameter.
13    #[error("Invalid parameter: {0}")]
14    InvalidParameter(String),
15    /// Bitstream version is known but unsupported.
16    #[error("Unsupported version: {0}")]
17    UnsupportedVersion(String),
18    /// Bitstream version could not be identified.
19    #[error("Unknown version: {0}")]
20    UnknownVersion(String),
21    /// Bitstream uses a feature this crate does not support.
22    #[error("Unsupported feature: {0}")]
23    UnsupportedFeature(String),
24    /// Bitstream version is outside the supported range.
25    #[error("Bitstream version unsupported")]
26    BitstreamVersionUnsupported,
27    /// Buffer read or write failed.
28    #[error("Buffer decode error: {0}")]
29    BufferError(String),
30}
31
32/// Prefix of the message [`DracoError::count_exceeds_bitstream`] builds.
33///
34/// The refusal has no variant of its own: `DracoError` is exhaustive, so adding
35/// one is a breaking change, and this refusal is tied to a guard that
36/// `hardening_status.yaml` records as unsound and due to be replaced. Marking
37/// the message instead keeps the constructor and the predicate that recognises
38/// it in one place, where a message is an implementation detail rather than an
39/// interface.
40const COUNT_EXCEEDS_BITSTREAM_PREFIX: &str = "Declared count";
41
42impl DracoError {
43    /// The decoder's refusal of a declared count larger than the remaining
44    /// bitstream could describe.
45    ///
46    /// Gated with the decode paths that raise it; the predicate below is not,
47    /// because a caller holding a `DracoError` may ask about it whatever this
48    /// build compiled.
49    #[cfg(feature = "decoder")]
50    pub(crate) fn count_exceeds_bitstream(count: usize, remaining_bytes: usize) -> Self {
51        DracoError::DracoError(format!(
52            "{COUNT_EXCEEDS_BITSTREAM_PREFIX} {count} exceeds what the remaining \
53             {remaining_bytes} bytes can describe"
54        ))
55    }
56
57    /// Whether this is the decoder's count-vs-size preflight refusal.
58    ///
59    /// It is the one decode refusal a caller may legitimately want to tell
60    /// apart: the bound it applies assumes at least one bit per point or face,
61    /// which highly repetitive geometry can beat, so this is also the refusal
62    /// that can be a false positive. See the `decoder-count-guard-is-unsound`
63    /// entry in `hardening_status.yaml`.
64    pub fn is_count_exceeds_bitstream(&self) -> bool {
65        matches!(self, DracoError::DracoError(message)
66            if message.starts_with(COUNT_EXCEEDS_BITSTREAM_PREFIX))
67    }
68}
69
70/// Convenience result type for operations that only report success or failure.
71pub type Status = Result<(), DracoError>;
72
73impl From<()> for DracoError {
74    fn from(_: ()) -> Self {
75        DracoError::DracoError("Unknown error".to_string())
76    }
77}
78
79/// Returns a successful [`Status`].
80pub fn ok_status() -> Status {
81    Ok(())
82}
83
84/// Creates a generic [`DracoError`] from a message.
85pub fn error_status(msg: impl Into<String>) -> DracoError {
86    DracoError::DracoError(msg.into())
87}