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}