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