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 column name {name}: the {role} column repeats the {taken_by} column")]
33    DuplicateColumnName {
34        name: String,
35        role: crate::tile::ColumnRole,
36        taken_by: crate::tile::ColumnRole,
37    },
38    #[error("feature property count mismatch: expected {expected}, got {actual}")]
39    PropertyLengthMismatch { expected: usize, actual: usize },
40    #[error("property {index} kind mismatch: expected {expected:?}, got {actual:?}")]
41    PropertyKindMismatch {
42        index: usize,
43        expected: crate::tile::PropKind,
44        actual: crate::tile::PropKind,
45    },
46    #[error(
47        "property {name:?} holds a nested array or object, which is not supported; flatten it or remove it"
48    )]
49    NestedPropertyValue { name: String },
50    #[error("property {name:?} value {value} does not fit its {kind:?} column")]
51    PropertyValueMismatch {
52        name: String,
53        value: String,
54        kind: crate::tile::PropKind,
55    },
56    #[error("staged column {column} feature count mismatch: expected {expected}, got {actual}")]
57    StagedFeatureCountMismatch {
58        column: String,
59        expected: usize,
60        actual: usize,
61    },
62    #[error("missing string stream: {0}")]
63    MissingStringStream(&'static str),
64    #[error("multiple geometry columns found (only one allowed)")]
65    MultipleGeometryColumns,
66    #[error("multiple ID columns found (only one allowed)")]
67    MultipleIdColumns,
68    #[error("varint uses more bytes than necessary (non-canonical encoding)")]
69    NonCanonicalVarInt,
70    #[error("{0} is not decoded")]
71    NotDecoded(&'static str),
72    #[error("decoded data is not in encoded form")]
73    NotEncoded,
74    #[error("error parsing column type: code={0}")]
75    ParsingColumnType(u8),
76    #[error("error parsing v2 stream encoding byte: 0x{0:02X}")]
77    ParsingEncodingByte(u8),
78    #[cfg(feature = "unstable-v2")]
79    #[error(
80        "a v2 {0:?} stream carries no value count, and nothing in its context implies one: encoding byte 0x{1:02X}"
81    )]
82    StreamWithoutCount(StreamType, u8),
83    #[cfg(feature = "unstable-v2")]
84    #[error("error parsing v2 geometry layout: code={0}")]
85    ParsingGeoLayout(u8),
86    #[cfg(feature = "unstable-v2")]
87    #[error("error parsing v2 layer header byte: 0x{0:02X}")]
88    ParsingLayerHeader02(u8),
89    #[cfg(feature = "unstable-v2")]
90    #[error("error parsing v2 layer layout byte: 0x{0:02X}")]
91    ParsingLayerLayout02(u8),
92    #[cfg(feature = "unstable-v2")]
93    #[error("v2 extent {0} is not a power of two in 64..=2097152")]
94    UnsupportedExtent02(u32),
95    #[cfg(feature = "unstable-v2")]
96    #[error(
97        "v2 geometry layout {0} gives no per-feature vertex count, so it cannot carry m-values"
98    )]
99    MValuesNeedVertexCounts(&'static str),
100    #[cfg(feature = "unstable-v2")]
101    #[error("the v2 layer header byte claims an m-value section, but it holds no columns")]
102    EmptyMValueSection,
103    #[cfg(feature = "unstable-v2")]
104    #[error(
105        "m-value column {name} has no implied count, so its leading stream must write one: encoding byte 0x{byte:02X}"
106    )]
107    MValueImplicitCount { name: String, byte: u8 },
108    #[cfg(feature = "unstable-v2")]
109    #[error(
110        "m-value column {name} holds {actual} values, but its features have {expected} vertices"
111    )]
112    MValueColumnLengthMismatch {
113        name: String,
114        expected: usize,
115        actual: usize,
116    },
117    #[cfg(feature = "unstable-v2")]
118    #[error(
119        "m-value column {name} holds {actual} values, so a feature's run of {count} at offset {start} is out of range"
120    )]
121    MValueRunOutOfRange {
122        name: String,
123        start: usize,
124        count: usize,
125        actual: usize,
126    },
127    #[cfg(feature = "unstable-v2")]
128    #[error("a feature carries {actual} m-value columns, but the layer has {expected}")]
129    MValueColumnCountMismatch { expected: usize, actual: usize },
130    #[cfg(feature = "unstable-v2")]
131    #[error("m-value key {index} belongs to another layer: this feature has {columns} columns")]
132    UnknownMValueKey { index: usize, columns: usize },
133    #[cfg(feature = "unstable-v2")]
134    #[error("m-value {index} kind mismatch: expected {expected:?}, got {actual:?}")]
135    MValueKindMismatch {
136        index: usize,
137        expected: crate::tile::PropKind,
138        actual: crate::tile::PropKind,
139    },
140    #[cfg(feature = "unstable-v2")]
141    #[error("m-value {index} holds {actual} values, but the feature has {expected} vertices")]
142    MValueVertexCountMismatch {
143        index: usize,
144        expected: usize,
145        actual: usize,
146    },
147    #[error("8-bit integers are a v2 feature, so column {0} cannot be written as v1")]
148    EightBitNeedsV2(String),
149    #[cfg(feature = "unstable-v2")]
150    #[error("m-values are a v2 feature, so layer {0} cannot be written as v1")]
151    MValuesNeedV2(String),
152    #[error("a vertex stream of {0} words does not hold whole vertices of {1} words")]
153    PartialVertex(u32, u32),
154    #[cfg(feature = "unstable-v2")]
155    #[error("z coordinates are a v2 feature, so layer {0} cannot be written as v1")]
156    ZNeedsV2(String),
157    #[cfg(feature = "unstable-v2")]
158    #[error("a z step of 10^{0} m is outside 10^-3..=10^4 m")]
159    InvalidZStep(i8),
160    #[cfg(feature = "unstable-v2")]
161    #[error("z step code {0} is outside 0..=7")]
162    InvalidZStepCode(u8),
163    #[cfg(feature = "unstable-v2")]
164    #[error(
165        "layer {0} already holds features without z coordinates, so it cannot be given a z step"
166    )]
167    ZStepOnNonEmptyLayer(String),
168    #[cfg(feature = "unstable-v2")]
169    #[error("a feature carries {actual} z coordinates, but the layer expects {expected}")]
170    ZVertexCountMismatch { expected: usize, actual: usize },
171    #[cfg(feature = "unstable-v2")]
172    #[error("the vertices already carry z coordinates")]
173    ZAlreadySet,
174    #[cfg(feature = "unstable-v2")]
175    #[error("a vertex stream of {0} words does not hold whole (x, y, z) triples")]
176    InvalidTripleStreamSize(usize),
177    #[cfg(feature = "unstable-v2")]
178    #[error("nested properties are a v2 feature, so layer {0} cannot be written as v1")]
179    NestedNeedsV2(String),
180    #[cfg(feature = "unstable-v2")]
181    #[error("duplicate field name in a struct node: {0}")]
182    DuplicateFieldName(String),
183    #[cfg(feature = "unstable-v2")]
184    #[error("a struct node holds no fields")]
185    EmptyStructNode,
186    #[cfg(feature = "unstable-v2")]
187    #[error("nested column {0} has a scalar root, which is an ordinary column")]
188    NestedRootIsLeaf(String),
189    #[cfg(feature = "unstable-v2")]
190    #[error("a nested column is {0} levels deep, more than the 8 the format allows")]
191    NestedTooDeep(usize),
192    #[cfg(feature = "unstable-v2")]
193    #[error("a nested lengths stream sums to {expected}, but the node below it holds {actual}")]
194    NestedCountMismatch { expected: u32, actual: u32 },
195    #[cfg(feature = "unstable-v2")]
196    #[error(
197        "nested node {name} has no implied count, so its leading stream must write one: encoding byte 0x{byte:02X}"
198    )]
199    NestedImplicitCount { name: String, byte: u8 },
200    #[cfg(feature = "unstable-v2")]
201    #[error("a feature carries {actual} nested columns, but the layer has {expected}")]
202    NestedColumnCountMismatch { expected: usize, actual: usize },
203    #[cfg(feature = "unstable-v2")]
204    #[error("nested key {index} belongs to another layer: this feature has {columns} columns")]
205    UnknownNestedKey { index: usize, columns: usize },
206    #[cfg(feature = "unstable-v2")]
207    #[error("nested column {index} ({name}) was given a value of another shape")]
208    NestedValueMismatch { index: usize, name: String },
209    #[cfg(feature = "unstable-v2")]
210    #[error("nested column {index} was given a value of another shape")]
211    NestedShapeMismatch { index: usize },
212    #[cfg(feature = "unstable-v2")]
213    #[error("nested column {name} runs over {expected} values, but its root holds {actual}")]
214    NestedRootCountMismatch {
215        name: String,
216        expected: u32,
217        actual: u32,
218    },
219    #[cfg(feature = "unstable-v2")]
220    #[error(
221        "nested node {name} must write its presence as a raw bitmap of one bit per value: encoding byte 0x{byte:02X}"
222    )]
223    NestedPresenceEncoding { name: String, byte: u8 },
224    #[cfg(feature = "unstable-v2")]
225    #[error("a nested lengths stream holds {len} lengths, so it has no row {row}")]
226    NestedRowOutOfRange { row: usize, len: usize },
227    #[cfg(feature = "unstable-v2")]
228    #[error("a nested map holds {len} keys, so it has no key for entry {entry}")]
229    NestedKeyOutOfRange { entry: usize, len: usize },
230    #[cfg(feature = "unstable-v2")]
231    #[error("a nested leaf holds {len} values, so it has no value at index {index}")]
232    NestedValueOutOfRange { index: usize, len: usize },
233    #[cfg(feature = "unstable-v2")]
234    #[error("a row-shape table of {bits} bits does not divide into shapes of {keys} keys")]
235    NestedRowShapeTableSize { bits: usize, keys: usize },
236    #[cfg(feature = "unstable-v2")]
237    #[error("a shape-coded nested node has {len} row shapes, so it has no shape {id}")]
238    NestedRowShapeOutOfRange { id: u32, len: usize },
239    #[cfg(feature = "unstable-v2")]
240    #[error("nested node {name} is shape-coded over {expected} rows, but holds {actual} shape ids")]
241    NestedRowShapeCount {
242        name: String,
243        expected: u32,
244        actual: u32,
245    },
246    #[cfg(feature = "unstable-v2")]
247    #[error(
248        "nested node {name} reads its presence from its parent's row shapes, so it stores none of its own"
249    )]
250    NestedRowShapeChildPresence { name: String },
251    #[cfg(feature = "unstable-v2")]
252    #[error("nested node {name} is a {kind}, which has no row shapes to read")]
253    NestedRowShapeUnsupported { name: String, kind: &'static str },
254    #[error("error parsing logical technique: code={0}")]
255    ParsingLogicalTechnique(u8),
256    #[error("error parsing physical encoding: code={0}")]
257    ParsingPhysicalEncoding(u8),
258    #[error("error parsing stream type: code={0}")]
259    ParsingStreamType(u8),
260    #[error("found {0} bytes after the expected end of layer")]
261    TrailingLayerData(usize),
262    #[error("unexpected end of input (unable to take {0} bytes)")]
263    UnableToTake(u32),
264    #[error("unexpected stream type {0:?}")]
265    UnexpectedStreamType(StreamType),
266    #[error("unexpected stream type {0:?}, expected {1} for {2}")]
267    UnexpectedStreamType2(StreamType, &'static str, &'static str),
268    #[error("unsupported logical encoding {0:?} for {1}")]
269    UnsupportedLogicalEncoding(LogicalEncoding, &'static str),
270    #[error("invalid combination of logical encodings: {0:?} + {1:?}")]
271    InvalidLogicalEncodings(LogicalTechnique, LogicalTechnique),
272    #[error("logical encoding {0:?} is not one a {1:?} stream has")]
273    LogicalEncodingNotInKind(LogicalCombination, ValueKind),
274    #[error("layer has zero size")]
275    ZeroLayerSize,
276    #[error("bit-packed stream width {0} is outside 1..=32")]
277    #[cfg(feature = "unstable-v2")]
278    ParsingBitWidth(u32),
279    #[error("The encoder used to optimise data is incompatible")]
280    BadEncoderDataCombination,
281    #[error("StagedLayer::encode_explicit requires Encoder.explicit to be Some(_)")]
282    MissingExplicitEncoder,
283
284    // Wire/codec decoding (bytes -> primitives)
285    #[error("buffer underflow: needed {0} bytes, but only {1} remain")]
286    BufferUnderflow(u32, usize),
287    #[error("FastPFor decode failed: expected={0} got={1}")]
288    FastPforDecode(u32, usize),
289    #[error("invalid RLE run length (cannot convert to usize): value={0}")]
290    RleRunLenInvalid(i128),
291    #[error("presence runs overflow the {0} features they cover")]
292    PresenceRunOverflow(u32),
293    #[error("presence names {0} present features out of {1}")]
294    PresenceRunShort(u32, u32),
295    #[error("presence indices are not strictly increasing within {0} features")]
296    PresenceIndexOrder(u32),
297    #[error("unknown presence coding byte: 0x{0:02X}")]
298    PresenceCodingByte(u8),
299
300    // Structural constraints (lengths, counts, shapes)
301    #[error("geometry requires at least 1 stream, got 0")]
302    GeometryWithoutStreams,
303    #[error("FastPFor data byte length expected multiple of 4, got {0}")]
304    InvalidFastPforByteLength(usize),
305    #[error("vec2 delta stream size expected to be non-empty and multiple of 2, got {0}")]
306    InvalidPairStreamSize(usize),
307    #[error("decodable stream size expected {1}, got {0}")]
308    InvalidDecodingStreamSize(usize, usize),
309    #[error("IDs missing for encoding (expected Some IDs, got None)")]
310    IdsMissingForEncoding,
311    #[error("missing struct encoder for struct")]
312    MissingStructEncoderForStruct,
313    #[error("previous decode/parsing attempt failed")]
314    PriorParseFailure,
315    #[error("FSST-compressed data is malformed: {0}")]
316    MalformedFsst(&'static str),
317    #[cfg(feature = "unstable-v2")]
318    #[error("rANS vertex stream is malformed: {0}")]
319    MalformedRans(&'static str),
320    #[cfg(feature = "unstable-v2")]
321    #[error("dictionary code {0} is out of range for a dictionary of {1} values")]
322    DictionaryCodeOutOfRange(u32, usize),
323    #[cfg(feature = "unstable-v2")]
324    #[error(
325        "front-coded lengths stream holds a prefix and a suffix length per entry, so its {0} values cannot be split in two"
326    )]
327    FrontCodedOddLengthCount(usize),
328    #[cfg(feature = "unstable-v2")]
329    #[error(
330        "front-coded entry {index} shares {shared} bytes with a predecessor of only {available}"
331    )]
332    FrontCodedPrefixTooLong {
333        index: usize,
334        shared: usize,
335        available: usize,
336    },
337    #[cfg(feature = "unstable-v2")]
338    #[error(
339        "front-coded entry {index} needs {needed} suffix bytes, but {available} remain in the blob"
340    )]
341    FrontCodedSuffixOutOfBounds {
342        index: usize,
343        needed: usize,
344        available: usize,
345    },
346    #[cfg(feature = "unstable-v2")]
347    #[error("front-coded dictionary leaves {0} suffix bytes after its last entry")]
348    FrontCodedTrailingSuffixBytes(usize),
349    #[cfg(feature = "unstable-v2")]
350    #[error("no Framed, Exception-Free ALP parameters return this float column bit-for-bit")]
351    NoAlpParameters,
352    #[error("invalid Framed, Exception-Free ALP parameters: e={0}, f={1}")]
353    InvalidAlpParams(u8, u8),
354    #[error("invalid Framed, Exception-Free ALP scale byte {0}")]
355    InvalidAlpScale(u8),
356    #[error("presence stream has {0} bits set but {1} values provided")]
357    PresenceValueCountMismatch(usize, usize),
358    #[error("need to encode before being able to write")]
359    NeedsEncodingBeforeWriting,
360    #[error("memory limit exceeded: limit={limit}, used={used}, requested={requested}")]
361    MemoryLimitExceeded {
362        limit: u32,
363        used: u32,
364        requested: u32,
365    },
366    #[error("not implemented: {0}")]
367    NotImplemented(&'static str),
368    #[error("unsupported property value and encoder combination: {0:?} + {1:?}")]
369    UnsupportedPropertyEncoderCombination(&'static str, &'static str),
370    #[error("mixed property types are not allowed in column {0} ({1})")]
371    MixedPropertyTypes(usize, String),
372    #[error("shared dictionary requires at least 2 streams, got {0}")]
373    SharedDictRequiresStreams(usize),
374    #[error("unsupported string stream count (expected between 2 and 5): {0}")]
375    UnsupportedStringStreamCount(usize),
376    #[error("Structs are not allowed to be optional")]
377    TriedToEncodeOptionalStruct,
378    #[error(
379        "encoding instruction count mismatch: expected {input_len} instructions for {input_len} properties, got {config_len}"
380    )]
381    EncodingInstructionCountMismatch { input_len: usize, config_len: usize },
382    #[error("struct child data streams expected exactly 1 value, got {0}")]
383    UnexpectedStructChildCount(u32),
384    // Note that {expected}+1 is allowed for the legacy Java encoder bug
385    #[error("SharedDict stream count is {actual}, expected {expected}")]
386    InvalidSharedDictStreamCount { actual: u32, expected: u32 },
387    #[error("unsupported physical encoding: {0}")]
388    UnsupportedPhysicalEncoding(&'static str),
389    #[error("unsupported physical encoding: {0:?} for {1}")]
390    UnsupportedPhysicalEncodingForType(PhysicalEncoding, &'static str),
391    #[error(
392        "Extent {extent} cannot be encoded to morton due to morton allowing max. 16 bits, but {required_bits} would be required"
393    )]
394    VertexMortonNotCompatibleWithExtent { extent: u32, required_bits: u32 },
395    #[error("Morton stream uses {0} bits, but at most 16 bits are supported")]
396    InvalidMortonBits(u32),
397
398    // Geometry decode errors (field = variable name, geom_type for context)
399    #[error("MVT error: {0}")]
400    BadMvtGeometry(&'static str),
401    #[error("geometry[{0}]: index out of bounds")]
402    GeometryIndexOutOfBounds(usize),
403    #[error("geometry[{index}]: {field}[{idx}] out of bounds (len={len})")]
404    GeometryOutOfBounds {
405        index: usize,
406        field: &'static str,
407        idx: usize,
408        len: usize,
409    },
410    #[error("geometry[{index}]: vertex {vertex} out of bounds (count={count})")]
411    GeometryVertexOutOfBounds {
412        index: usize,
413        vertex: usize,
414        count: usize,
415    },
416    #[error("geometry[{0}]: {1} requires geometry_offsets")]
417    NoGeometryOffsets(usize, GeometryType),
418    #[error("geometry[{0}]: {1} requires part_offsets")]
419    NoPartOffsets(usize, GeometryType),
420    #[error("geometry[{0}]: {1} requires ring_offsets")]
421    NoRingOffsets(usize, GeometryType),
422    #[error("geometry[{0}]: unexpected offset combination for {1}")]
423    UnexpectedOffsetCombination(usize, GeometryType),
424    #[error("geometry: ring lengths without part lengths")]
425    RingLengthsWithoutPartLengths,
426    #[error("geometry[{0}]: {1} requires outlines, which a triangles-only layer does not store")]
427    NonPolygonWithoutOutlines(usize, GeometryType),
428
429    #[error("FastPFor error: {0}")]
430    FastPfor(#[from] fastpfor::FastPForError),
431    #[error(transparent)]
432    Io(#[from] std::io::Error),
433    #[error("Serde JSON error: {0}")]
434    SerdeJsonError(#[from] serde_json::Error),
435    #[error("integer conversion error: {0}")]
436    TryFromIntError(#[from] TryFromIntError),
437    #[error("num_enum conversion error: {0}")]
438    TryFromPrimitive(#[from] TryFromPrimitiveError<GeometryType>),
439    #[error("UTF-8 decode error: {0}")]
440    Utf8(#[from] std::str::Utf8Error),
441    #[error("UTF-8 decode error: {0}")]
442    FromUtf8(#[from] std::string::FromUtf8Error),
443    #[error("MVT error: {0}")]
444    Mvt(#[from] fast_mvt::MvtError),
445    #[error("MVT JSON value error: {0}")]
446    MvtJsonValue(#[from] fast_mvt::MvtJsonValueError),
447}
448
449impl From<MltError> for std::io::Error {
450    fn from(value: MltError) -> Self {
451        if let MltError::Io(e) = value {
452            e
453        } else {
454            Self::other(value)
455        }
456    }
457}
458
459pub(crate) trait AsMltError<T> {
460    fn or_overflow(&self) -> MltResult<T>;
461}
462
463impl<T: Copy> AsMltError<T> for Option<T> {
464    #[inline]
465    fn or_overflow(&self) -> MltResult<T> {
466        self.ok_or(MltError::IntegerOverflow)
467    }
468}
469
470impl AsMltError<u32> for Result<u32, TryFromIntError> {
471    #[inline]
472    fn or_overflow(&self) -> MltResult<u32> {
473        self.map_err(|_| MltError::IntegerOverflow)
474    }
475}
476
477#[inline]
478pub(crate) fn fail_if_invalid_stream_size(actual: usize, expected: usize) -> MltResult<()> {
479    if actual == expected {
480        Ok(())
481    } else {
482        Err(MltError::InvalidDecodingStreamSize(actual, expected))
483    }
484}