Skip to main content

mlt_core/
errors.rs

1use std::num::TryFromIntError;
2
3use num_enum::TryFromPrimitiveError;
4
5use crate::decoder::{
6    GeometryType, LogicalCombination, LogicalEncoding, LogicalTechnique, PhysicalEncoding,
7    StreamType, ValueKind,
8};
9
10pub type MltResult<T> = Result<T, MltError>;
11pub(crate) type MltRefResult<'a, T> = Result<(&'a [u8], T), MltError>;
12
13#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum MltError {
16    #[error("cannot decode {0} as {1}")]
17    DataWidthMismatch(&'static str, &'static str),
18    #[error("dictionary index {0} out of bounds (len={1})")]
19    DictIndexOutOfBounds(u32, usize),
20    #[error("duplicate value found where unique required")]
21    DuplicateValue,
22    #[error("Integer overflow")]
23    IntegerOverflow,
24    #[error("missing geometry column in feature table")]
25    MissingGeometry,
26    #[error("missing layer name")]
27    MissingLayerName,
28    #[error("invalid extent: {0}")]
29    InvalidExtent(u32),
30    #[error("missing property name")]
31    MissingPropertyName,
32    #[error("duplicate property name: {0}")]
33    DuplicatePropertyName(String),
34    #[error("feature property count mismatch: expected {expected}, got {actual}")]
35    PropertyLengthMismatch { expected: usize, actual: usize },
36    #[error("property {index} kind mismatch: expected {expected:?}, got {actual:?}")]
37    PropertyKindMismatch {
38        index: usize,
39        expected: crate::tile::PropKind,
40        actual: crate::tile::PropKind,
41    },
42    #[error("staged column {column} feature count mismatch: expected {expected}, got {actual}")]
43    StagedFeatureCountMismatch {
44        column: String,
45        expected: usize,
46        actual: usize,
47    },
48    #[error("missing string stream: {0}")]
49    MissingStringStream(&'static str),
50    #[error("multiple geometry columns found (only one allowed)")]
51    MultipleGeometryColumns,
52    #[error("multiple ID columns found (only one allowed)")]
53    MultipleIdColumns,
54    #[error("varint uses more bytes than necessary (non-canonical encoding)")]
55    NonCanonicalVarInt,
56    #[error("{0} is not decoded")]
57    NotDecoded(&'static str),
58    #[error("decoded data is not in encoded form")]
59    NotEncoded,
60    #[error("error parsing column type: code={0}")]
61    ParsingColumnType(u8),
62    #[error("error parsing v2 stream encoding byte: 0x{0:02X}")]
63    ParsingEncodingByte(u8),
64    #[cfg(feature = "unstable-v2")]
65    #[error("error parsing v2 geometry layout: code={0}")]
66    ParsingGeoLayout(u8),
67    #[cfg(feature = "unstable-v2")]
68    #[error("error parsing v2 layer layout: byte=0x{0:02X}")]
69    ParsingLayerLayout(u8),
70    #[error("error parsing logical technique: code={0}")]
71    ParsingLogicalTechnique(u8),
72    #[error("error parsing physical encoding: code={0}")]
73    ParsingPhysicalEncoding(u8),
74    #[error("error parsing stream type: code={0}")]
75    ParsingStreamType(u8),
76    #[error("found {0} bytes after the expected end of layer")]
77    TrailingLayerData(usize),
78    #[error("unexpected end of input (unable to take {0} bytes)")]
79    UnableToTake(u32),
80    #[error("unexpected stream type {0:?}")]
81    UnexpectedStreamType(StreamType),
82    #[error("unexpected stream type {0:?}, expected {1} for {2}")]
83    UnexpectedStreamType2(StreamType, &'static str, &'static str),
84    #[error("unsupported logical encoding {0:?} for {1}")]
85    UnsupportedLogicalEncoding(LogicalEncoding, &'static str),
86    #[error("invalid combination of logical encodings: {0:?} + {1:?}")]
87    InvalidLogicalEncodings(LogicalTechnique, LogicalTechnique),
88    #[error("logical encoding {0:?} is not one a {1:?} stream has")]
89    LogicalEncodingNotInKind(LogicalCombination, ValueKind),
90    #[error("layer has zero size")]
91    ZeroLayerSize,
92    #[error("The encoder used to optimise data is incompatible")]
93    BadEncoderDataCombination,
94    #[error("StagedLayer::encode_explicit requires Encoder.explicit to be Some(_)")]
95    MissingExplicitEncoder,
96
97    // Wire/codec decoding (bytes -> primitives)
98    #[error("buffer underflow: needed {0} bytes, but only {1} remain")]
99    BufferUnderflow(u32, usize),
100    #[error("FastPFor decode failed: expected={0} got={1}")]
101    FastPforDecode(u32, usize),
102    #[error("invalid RLE run length (cannot convert to usize): value={0}")]
103    RleRunLenInvalid(i128),
104
105    // Structural constraints (lengths, counts, shapes)
106    #[error("geometry requires at least 1 stream, got 0")]
107    GeometryWithoutStreams,
108    #[error("FastPFor data byte length expected multiple of 4, got {0}")]
109    InvalidFastPforByteLength(usize),
110    #[error("vec2 delta stream size expected to be non-empty and multiple of 2, got {0}")]
111    InvalidPairStreamSize(usize),
112    #[error("decodable stream size expected {1}, got {0}")]
113    InvalidDecodingStreamSize(usize, usize),
114    #[error("IDs missing for encoding (expected Some IDs, got None)")]
115    IdsMissingForEncoding,
116    #[error("missing struct encoder for struct")]
117    MissingStructEncoderForStruct,
118    #[error("previous decode/parsing attempt failed")]
119    PriorParseFailure,
120    #[error("FSST-compressed data is malformed: {0}")]
121    MalformedFsst(&'static str),
122    #[cfg(feature = "unstable-v2")]
123    #[error("dictionary code {0} is out of range for a dictionary of {1} values")]
124    DictionaryCodeOutOfRange(u32, usize),
125    #[cfg(feature = "unstable-v2")]
126    #[error(
127        "front-coded lengths stream holds a prefix and a suffix length per entry, so its {0} values cannot be split in two"
128    )]
129    FrontCodedOddLengthCount(usize),
130    #[cfg(feature = "unstable-v2")]
131    #[error(
132        "front-coded entry {index} shares {shared} bytes with a predecessor of only {available}"
133    )]
134    FrontCodedPrefixTooLong {
135        index: usize,
136        shared: usize,
137        available: usize,
138    },
139    #[cfg(feature = "unstable-v2")]
140    #[error(
141        "front-coded entry {index} needs {needed} suffix bytes, but {available} remain in the blob"
142    )]
143    FrontCodedSuffixOutOfBounds {
144        index: usize,
145        needed: usize,
146        available: usize,
147    },
148    #[cfg(feature = "unstable-v2")]
149    #[error("front-coded dictionary leaves {0} suffix bytes after its last entry")]
150    FrontCodedTrailingSuffixBytes(usize),
151    #[cfg(feature = "unstable-v2")]
152    #[error("no ALP parameters return this float column bit-for-bit")]
153    NoAlpParameters,
154    #[error("invalid ALP parameters: e={0}, f={1}")]
155    InvalidAlpParams(u8, u8),
156    #[error("presence stream has {0} bits set but {1} values provided")]
157    PresenceValueCountMismatch(usize, usize),
158    #[error("need to encode before being able to write")]
159    NeedsEncodingBeforeWriting,
160    #[error("memory limit exceeded: limit={limit}, used={used}, requested={requested}")]
161    MemoryLimitExceeded {
162        limit: u32,
163        used: u32,
164        requested: u32,
165    },
166    #[error("not implemented: {0}")]
167    NotImplemented(&'static str),
168    #[error("unsupported property value and encoder combination: {0:?} + {1:?}")]
169    UnsupportedPropertyEncoderCombination(&'static str, &'static str),
170    #[error("mixed property types are not allowed in column {0} ({1})")]
171    MixedPropertyTypes(usize, String),
172    #[error("shared dictionary requires at least 2 streams, got {0}")]
173    SharedDictRequiresStreams(usize),
174    #[error("unsupported string stream count (expected between 2 and 5): {0}")]
175    UnsupportedStringStreamCount(usize),
176    #[error("Structs are not allowed to be optional")]
177    TriedToEncodeOptionalStruct,
178    #[error(
179        "encoding instruction count mismatch: expected {input_len} instructions for {input_len} properties, got {config_len}"
180    )]
181    EncodingInstructionCountMismatch { input_len: usize, config_len: usize },
182    #[error("struct child data streams expected exactly 1 value, got {0}")]
183    UnexpectedStructChildCount(u32),
184    // Note that {expected}+1 is allowed for the legacy Java encoder bug
185    #[error("SharedDict stream count is {actual}, expected {expected}")]
186    InvalidSharedDictStreamCount { actual: u32, expected: u32 },
187    #[error("unsupported physical encoding: {0}")]
188    UnsupportedPhysicalEncoding(&'static str),
189    #[error("unsupported physical encoding: {0:?} for {1}")]
190    UnsupportedPhysicalEncodingForType(PhysicalEncoding, &'static str),
191    #[error(
192        "Extent {extent} cannot be encoded to morton due to morton allowing max. 16 bits, but {required_bits} would be required"
193    )]
194    VertexMortonNotCompatibleWithExtent { extent: u32, required_bits: u32 },
195    #[error("Morton stream uses {0} bits, but at most 16 bits are supported")]
196    InvalidMortonBits(u32),
197
198    // Geometry decode errors (field = variable name, geom_type for context)
199    #[error("MVT error: {0}")]
200    BadMvtGeometry(&'static str),
201    #[error("geometry[{0}]: index out of bounds")]
202    GeometryIndexOutOfBounds(usize),
203    #[error("geometry[{index}]: {field}[{idx}] out of bounds (len={len})")]
204    GeometryOutOfBounds {
205        index: usize,
206        field: &'static str,
207        idx: usize,
208        len: usize,
209    },
210    #[error("geometry[{index}]: vertex {vertex} out of bounds (count={count})")]
211    GeometryVertexOutOfBounds {
212        index: usize,
213        vertex: usize,
214        count: usize,
215    },
216    #[error("geometry[{0}]: {1} requires geometry_offsets")]
217    NoGeometryOffsets(usize, GeometryType),
218    #[error("geometry[{0}]: {1} requires part_offsets")]
219    NoPartOffsets(usize, GeometryType),
220    #[error("geometry[{0}]: {1} requires ring_offsets")]
221    NoRingOffsets(usize, GeometryType),
222    #[error("geometry[{0}]: unexpected offset combination for {1}")]
223    UnexpectedOffsetCombination(usize, GeometryType),
224    #[error("geometry: ring lengths without part lengths")]
225    RingLengthsWithoutPartLengths,
226
227    #[error("FastPFor error: {0}")]
228    FastPfor(#[from] fastpfor::FastPForError),
229    #[error(transparent)]
230    Io(#[from] std::io::Error),
231    #[error("Serde JSON error: {0}")]
232    SerdeJsonError(#[from] serde_json::Error),
233    #[error("integer conversion error: {0}")]
234    TryFromIntError(#[from] TryFromIntError),
235    #[error("num_enum conversion error: {0}")]
236    TryFromPrimitive(#[from] TryFromPrimitiveError<GeometryType>),
237    #[error("UTF-8 decode error: {0}")]
238    Utf8(#[from] std::str::Utf8Error),
239    #[error("UTF-8 decode error: {0}")]
240    FromUtf8(#[from] std::string::FromUtf8Error),
241    #[error("MVT error: {0}")]
242    Mvt(#[from] fast_mvt::MvtError),
243    #[error("MVT JSON value error: {0}")]
244    MvtJsonValue(#[from] fast_mvt::MvtJsonValueError),
245}
246
247impl From<MltError> for std::io::Error {
248    fn from(value: MltError) -> Self {
249        if let MltError::Io(e) = value {
250            e
251        } else {
252            Self::other(value)
253        }
254    }
255}
256
257pub(crate) trait AsMltError<T> {
258    fn or_overflow(&self) -> MltResult<T>;
259}
260
261impl<T: Copy> AsMltError<T> for Option<T> {
262    #[inline]
263    fn or_overflow(&self) -> MltResult<T> {
264        self.ok_or(MltError::IntegerOverflow)
265    }
266}
267
268impl AsMltError<u32> for Result<u32, TryFromIntError> {
269    #[inline]
270    fn or_overflow(&self) -> MltResult<u32> {
271        self.map_err(|_| MltError::IntegerOverflow)
272    }
273}
274
275#[inline]
276pub(crate) fn fail_if_invalid_stream_size(actual: usize, expected: usize) -> MltResult<()> {
277    if actual == expected {
278        Ok(())
279    } else {
280        Err(MltError::InvalidDecodingStreamSize(actual, expected))
281    }
282}