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