Skip to main content

mlt_ffi/
lib.rs

1mod store;
2
3#[expect(
4    clippy::unnecessary_box_returns,
5    reason = "Diplomat requires `Box<T>` returns for opaque constructors"
6)]
7#[expect(
8    clippy::use_self,
9    reason = "Diplomat expands macros using the concrete type name rather than `Self`."
10)]
11#[diplomat::bridge]
12mod ffi {
13    use std::fmt::Write as _;
14
15    use mlt_core::encoder::{EncoderConfig, Network, Profile, Tessellation, WireVersion};
16    use mlt_core::mvt::{mvt_to_tile_layers, tile_layers_to_mvt};
17    use mlt_core::wire::LayerBytes;
18    use mlt_core::{Decoder, Parser};
19
20    use crate::store::{LayerBuffer, MvtGeometryType, StoreError, StoredValue};
21
22    /// Which stage of a conversion failed.
23    #[derive(Debug, PartialEq, Eq)]
24    pub enum ConvertErrorKind {
25        /// Input bytes could not be parsed or decoded.
26        InvalidInput,
27        /// Encoding failed.
28        EncodingFailed,
29        /// A feature handed to a layer builder is malformed.
30        InvalidFeature,
31    }
32
33    /// Error returned by FFI conversion functions.
34    #[diplomat::opaque]
35    #[diplomat::attr(auto, error)]
36    pub struct ConvertError {
37        kind: ConvertErrorKind,
38        message: String,
39    }
40
41    impl ConvertError {
42        /// The stage that failed.
43        #[diplomat::attr(auto, getter = "kind")]
44        pub fn kind(&self) -> ConvertErrorKind {
45            self.kind
46        }
47
48        /// Human-readable cause.
49        #[diplomat::attr(auto, getter = "message")]
50        pub fn message(&self, write: &mut DiplomatWrite) {
51            let _ = write.write_str(&self.message);
52        }
53
54        #[cfg(test)]
55        pub(crate) fn message_text(&self) -> &str {
56            &self.message
57        }
58    }
59
60    impl ConvertError {
61        fn new(kind: ConvertErrorKind, source: &impl std::fmt::Display) -> Box<Self> {
62            Box::new(Self {
63                kind,
64                message: source.to_string(),
65            })
66        }
67    }
68
69    fn feature_error(source: &StoreError) -> Box<ConvertError> {
70        ConvertError::new(ConvertErrorKind::InvalidFeature, source)
71    }
72
73    fn invalid_input(source: impl std::fmt::Display) -> Box<ConvertError> {
74        ConvertError::new(ConvertErrorKind::InvalidInput, &source)
75    }
76
77    fn encoding_failed(source: impl std::fmt::Display) -> Box<ConvertError> {
78        ConvertError::new(ConvertErrorKind::EncodingFailed, &source)
79    }
80
81    fn set_value(
82        buffer: &mut LayerBuffer,
83        key: u32,
84        value: StoredValue,
85    ) -> Result<(), Box<ConvertError>> {
86        buffer.set(key, value).map_err(|e| feature_error(&e))
87    }
88
89    /// Owned byte buffer returned from conversion functions.
90    ///
91    /// The caller borrows the contents via [`as_bytes`](MltBuffer::as_bytes)
92    /// and the buffer is freed when the handle is dropped.
93    #[diplomat::opaque_mut]
94    pub struct MltBuffer(Vec<u8>);
95
96    impl MltBuffer {
97        /// An empty buffer, to collect the layers of one tile.
98        #[diplomat::attr(auto, constructor)]
99        pub fn new() -> Box<MltBuffer> {
100            Box::new(MltBuffer(Vec::new()))
101        }
102
103        /// Empty the buffer, keeping its allocation for the next tile.
104        pub fn clear(&mut self) {
105            self.0.clear();
106        }
107
108        /// Borrow the contents as a byte slice.
109        #[diplomat::attr(auto, getter = "bytes")]
110        #[expect(
111            clippy::needless_lifetimes,
112            reason = "diplomat requires explicit lifetimes"
113        )]
114        pub fn as_bytes<'a>(&'a self) -> &'a [u8] {
115            &self.0
116        }
117
118        /// Number of bytes in the buffer.
119        #[diplomat::attr(auto, getter = "len")]
120        pub fn len(&self) -> usize {
121            self.0.len()
122        }
123    }
124
125    /// The wire format an encoded layer uses.
126    #[expect(
127        dead_code,
128        reason = "Diplomat constructs the variants on the foreign side"
129    )]
130    pub enum MltWireVersion {
131        /// Tag `0x01`, the stable v1 format.
132        V01,
133        /// Tag `0x02`, the experimental v2 format.
134        V02,
135    }
136
137    /// Encoder options controlling which optimisations are attempted for
138    /// MVT -> MLT conversion.
139    ///
140    /// Construct with [`new`](MltEncoderOptions::new) (FSST, `FastPFOR` and shared
141    /// dictionaries enabled, no sorting and no tessellation) and toggle individual flags with the
142    /// setter methods.
143    #[diplomat::opaque_mut]
144    pub struct MltEncoderOptions(EncoderConfig);
145
146    impl MltEncoderOptions {
147        /// Create encoder options with the default configuration.
148        #[diplomat::attr(auto, constructor)]
149        pub fn new() -> Box<MltEncoderOptions> {
150            Box::new(MltEncoderOptions(
151                EncoderConfig::from_profile(Profile::Native(Network::Cellular4G))
152                    .with_tessellation(Tessellation::Off),
153            ))
154        }
155
156        /// Generate tessellation data for polygons and multi-polygons.
157        pub fn set_tessellate(&mut self, enabled: bool) {
158            self.0 = self.0.with_tessellation(if enabled {
159                Tessellation::Always
160            } else {
161                Tessellation::Off
162            });
163        }
164
165        /// Try sorting features by the Z-order (Morton) curve index.
166        pub fn set_attempt_spatial_morton_sort(&mut self, enabled: bool) {
167            self.0 = self.0.with_spatial_morton_sort(enabled);
168        }
169
170        /// Try sorting features by the Hilbert curve index.
171        pub fn set_attempt_spatial_hilbert_sort(&mut self, enabled: bool) {
172            self.0 = self.0.with_spatial_hilbert_sort(enabled);
173        }
174
175        /// Try sorting features by their feature ID in ascending order.
176        pub fn set_attempt_id_sort(&mut self, enabled: bool) {
177            self.0 = self.0.with_id_sort(enabled);
178        }
179
180        /// Allow FSST string compression.
181        pub fn set_allow_fsst(&mut self, enabled: bool) {
182            self.0 = self.0.with_fsst(enabled);
183        }
184
185        /// Allow `FastPFOR` integer compression.
186        pub fn set_allow_fastpfor(&mut self, enabled: bool) {
187            self.0 = self.0.with_fastpfor(enabled);
188        }
189
190        /// Allow string grouping into shared dictionaries.
191        pub fn set_allow_shared_dict(&mut self, enabled: bool) {
192            self.0 = self.0.with_shared_dict(enabled);
193        }
194
195        /// Select the wire format to encode to.
196        /// Every setter marked v2 only has no effect on a v1 layer.
197        pub fn set_wire_version(&mut self, version: MltWireVersion) {
198            let version = match version {
199                MltWireVersion::V01 => WireVersion::V01,
200                MltWireVersion::V02 => WireVersion::V02,
201            };
202            self.0 = self.0.with_wire_version(version);
203        }
204
205        /// v2 only: let a tessellated all-polygon layer store its triangles without the outlines.
206        /// Each polygon then decodes as the triangles it was cut into.
207        /// Requires tessellation.
208        pub fn set_allow_triangles_only(&mut self, enabled: bool) {
209            self.0 = self.0.with_triangles_only(enabled);
210        }
211
212        /// v2 only: allow integer and vertex streams to store the deltas of their deltas.
213        pub fn set_allow_delta2(&mut self, enabled: bool) {
214            self.0 = self.0.with_delta2(enabled);
215        }
216
217        /// v2 only: allow float columns to store one code per value into a dictionary.
218        pub fn set_allow_float_dict(&mut self, enabled: bool) {
219            self.0 = self.0.with_float_dict(enabled);
220        }
221
222        /// v2 only: allow float columns to store each value as a decimal-scaled integer (ALP).
223        pub fn set_allow_float_alp(&mut self, enabled: bool) {
224            self.0 = self.0.with_float_alp(enabled);
225        }
226
227        /// v2 only: allow dictionary code streams to be bit-packed.
228        pub fn set_allow_packed_dict_codes(&mut self, enabled: bool) {
229            self.0 = self.0.with_packed_dict_codes(enabled);
230        }
231
232        /// v2 only: allow plain vertex streams to be rANS-coded.
233        pub fn set_allow_rans_vertices(&mut self, enabled: bool) {
234            self.0 = self.0.with_rans_vertices(enabled);
235        }
236
237        /// v2 only: let a nested struct or map code its row shapes instead of per-field presence.
238        pub fn set_allow_row_shapes(&mut self, enabled: bool) {
239            self.0 = self.0.with_row_shapes(enabled);
240        }
241    }
242
243    impl From<MltMvtGeometryType> for MvtGeometryType {
244        fn from(geometry: MltMvtGeometryType) -> Self {
245            match geometry {
246                MltMvtGeometryType::Point => Self::Point,
247                MltMvtGeometryType::LineString => Self::LineString,
248                MltMvtGeometryType::Polygon => Self::Polygon,
249            }
250        }
251    }
252
253    /// The geometry type of an MVT feature, which also covers its multi variant.
254    #[cfg_attr(
255        not(test),
256        expect(
257            dead_code,
258            reason = "Diplomat constructs the variants on the foreign side"
259        )
260    )]
261    pub enum MltMvtGeometryType {
262        Point,
263        LineString,
264        Polygon,
265    }
266
267    /// A layer written one feature at a time, then encoded without going through MVT.
268    #[diplomat::opaque_mut]
269    pub struct MltLayerBuilder(LayerBuffer);
270
271    impl MltLayerBuilder {
272        /// Start a layer.
273        pub fn new(name: &str, extent: u32) -> Result<Box<MltLayerBuilder>, Box<ConvertError>> {
274            LayerBuffer::new(name, extent)
275                .map(|buffer| Box::new(MltLayerBuilder(buffer)))
276                .map_err(|e| ConvertError::new(ConvertErrorKind::InvalidFeature, &e))
277        }
278
279        /// Start another layer, keeping the allocations of the last one.
280        pub fn reset(&mut self, name: &str, extent: u32) -> Result<(), Box<ConvertError>> {
281            self.0
282                .reset(name, extent)
283                .map_err(|e| ConvertError::new(ConvertErrorKind::InvalidFeature, &e))
284        }
285
286        /// Declare a property column, returning the key to set it with.
287        /// Declaring a name again returns its existing key.
288        pub fn add_property(&mut self, name: &str) -> u32 {
289            self.0.add_property(name)
290        }
291
292        /// Start a feature with its geometry given as MVT commands, ending the previous one.
293        /// A ring with positive area starts a polygon and any other ring is a hole of the last one.
294        pub fn begin_mvt_feature(
295            &mut self,
296            geometry: MltMvtGeometryType,
297            commands: &[u32],
298            id: Option<u64>,
299        ) -> Result<(), Box<ConvertError>> {
300            self.0
301                .begin_mvt_feature(geometry.into(), commands, id)
302                .map_err(|e| feature_error(&e))
303        }
304
305        /// Set a boolean property of the current feature.
306        pub fn set_bool(&mut self, key: u32, value: bool) -> Result<(), Box<ConvertError>> {
307            set_value(&mut self.0, key, StoredValue::Bool(value))
308        }
309
310        /// Set an integer property of the current feature.
311        pub fn set_i64(&mut self, key: u32, value: i64) -> Result<(), Box<ConvertError>> {
312            set_value(&mut self.0, key, StoredValue::I64(value))
313        }
314
315        /// Set a 32-bit float property of the current feature.
316        pub fn set_f32(&mut self, key: u32, value: f32) -> Result<(), Box<ConvertError>> {
317            set_value(&mut self.0, key, StoredValue::F32(value))
318        }
319
320        /// Set a 64-bit float property of the current feature.
321        pub fn set_f64(&mut self, key: u32, value: f64) -> Result<(), Box<ConvertError>> {
322            set_value(&mut self.0, key, StoredValue::F64(value))
323        }
324
325        /// Set a string property of the current feature.
326        pub fn set_str(&mut self, key: u32, value: &str) -> Result<(), Box<ConvertError>> {
327            self.0.set_str(key, value).map_err(|e| feature_error(&e))
328        }
329
330        /// Encode the layer and append it to `out`.
331        pub fn encode_into(
332            &self,
333            options: &MltEncoderOptions,
334            out: &mut MltBuffer,
335        ) -> Result<(), Box<ConvertError>> {
336            let layer = self.0.encode(options.0).map_err(encoding_failed)?;
337            out.0.extend_from_slice(&layer);
338            Ok(())
339        }
340    }
341
342    /// The bytes one layer spends, by what they hold.
343    #[derive(Debug, PartialEq, Eq)]
344    pub struct MltLayerBytes {
345        /// The value of the layer's size varint: the tag and the body, without the varint itself.
346        pub size: u32,
347        /// The geometry column, or the v2 geometry section.
348        pub geometry: u32,
349        /// Every column that is neither geometry nor the id.
350        pub properties: u32,
351        /// The id column.
352        pub ids: u32,
353        /// What is left of `size` once every column is taken out.
354        pub metadata: u32,
355    }
356
357    /// The bytes each layer of a tile spends, read without decoding the tile.
358    #[diplomat::opaque]
359    pub struct MltTileStats(Vec<(String, LayerBytes)>);
360
361    impl MltTileStats {
362        /// Parse the layers of an MLT tile, leaving out any with a tag this build does not know.
363        pub fn from_bytes(mlt: &[u8]) -> Result<Box<MltTileStats>, Box<ConvertError>> {
364            let layers = Parser::default().parse_layers(mlt).map_err(invalid_input)?;
365            let stats = layers
366                .iter()
367                .filter_map(|layer| Some((layer.name()?.to_owned(), layer.bytes()?)))
368                .collect();
369            Ok(Box::new(MltTileStats(stats)))
370        }
371
372        /// Number of layers with a known tag, in tile order.
373        pub fn layer_count(&self) -> usize {
374            self.0.len()
375        }
376
377        /// The name of the `i`-th layer with a known tag, or nothing past the last one.
378        pub fn layer_name(&self, i: usize, out: &mut DiplomatWrite) {
379            if let Some((name, _)) = self.0.get(i) {
380                let _ = out.write_str(name);
381            }
382        }
383
384        #[cfg(test)]
385        pub(crate) fn layer_name_text(&self, i: usize) -> &str {
386            &self.0[i].0
387        }
388
389        /// The bytes of the `i`-th layer with a known tag, or `None` past the last one.
390        pub fn layer_bytes(&self, i: usize) -> Option<MltLayerBytes> {
391            let (_, bytes) = self.0.get(i)?;
392            Some(MltLayerBytes {
393                size: bytes.size(),
394                geometry: bytes.geometry(),
395                properties: bytes.properties(),
396                ids: bytes.ids(),
397                metadata: bytes.metadata(),
398            })
399        }
400    }
401
402    fn decode_to_mvt(
403        mlt: &[u8],
404        mut parser: Parser,
405        mut dec: Decoder,
406    ) -> Result<Box<MltBuffer>, Box<ConvertError>> {
407        let layers = parser.parse_layers(mlt).map_err(invalid_input)?;
408        let mut tiles = Vec::new();
409        for layer in layers {
410            let tile = layer.into_tile(&mut dec).map_err(invalid_input)?;
411            tiles.extend(tile);
412        }
413        let out = tile_layers_to_mvt(tiles).map_err(invalid_input)?;
414        Ok(Box::new(MltBuffer(out)))
415    }
416
417    /// Stateless FFI entry-points for MLT <-> MVT conversion.
418    #[diplomat::opaque]
419    pub struct MltConverter;
420
421    impl MltConverter {
422        /// Decode MLT bytes into MVT bytes.
423        pub fn mlt_to_mvt(mlt: &[u8]) -> Result<Box<MltBuffer>, Box<ConvertError>> {
424            decode_to_mvt(mlt, Parser::default(), Decoder::default())
425        }
426
427        /// Decode MLT bytes into MVT bytes within a memory budget.
428        pub fn mlt_to_mvt_with_limit(
429            mlt: &[u8],
430            max_bytes: u32,
431        ) -> Result<Box<MltBuffer>, Box<ConvertError>> {
432            decode_to_mvt(
433                mlt,
434                Parser::with_max_size(max_bytes),
435                Decoder::with_max_size(max_bytes),
436            )
437        }
438
439        /// Encode MVT bytes into MLT bytes using the given encoder options.
440        pub fn mvt_to_mlt(
441            mvt: &[u8],
442            options: &MltEncoderOptions,
443        ) -> Result<Box<MltBuffer>, Box<ConvertError>> {
444            let mut out = Vec::new();
445            let layers = mvt_to_tile_layers(mvt).map_err(encoding_failed)?;
446            for tile in layers {
447                let encoded_tile = tile.encode(options.0).map_err(encoding_failed)?;
448                out.extend_from_slice(&encoded_tile);
449            }
450            Ok(Box::new(MltBuffer(out)))
451        }
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use insta::assert_debug_snapshot;
458    use mlt_core::mvt::mvt_to_tile_layers;
459
460    use super::ffi::{
461        ConvertErrorKind, MltBuffer, MltConverter, MltEncoderOptions, MltLayerBuilder,
462        MltLayerBytes, MltMvtGeometryType, MltTileStats, MltWireVersion,
463    };
464
465    const POINT: &[u8] = include_bytes!("../../../test/fixtures/simple/point-boolean.mvt");
466    const POLYGON: &[u8] = include_bytes!("../../../test/fixtures/simple/polygon-boolean.mvt");
467    const MULTIPOLYGON: &[u8] =
468        include_bytes!("../../../test/fixtures/simple/multipolygon-boolean.mvt");
469
470    #[test]
471    fn v1_encoding_of_a_point_layer_decodes_to_the_same_layers() {
472        let options = MltEncoderOptions::new();
473
474        let mlt = MltConverter::mvt_to_mlt(POINT, &options).ok().unwrap();
475        let decoded = MltConverter::mlt_to_mvt(mlt.as_bytes()).ok().unwrap();
476
477        assert_eq!(
478            mvt_to_tile_layers(decoded.as_bytes()).unwrap(),
479            mvt_to_tile_layers(POINT).unwrap()
480        );
481    }
482
483    #[test]
484    fn v1_encoding_of_a_multipolygon_layer_decodes_to_the_same_layers() {
485        let options = MltEncoderOptions::new();
486
487        let mlt = MltConverter::mvt_to_mlt(MULTIPOLYGON, &options)
488            .ok()
489            .unwrap();
490        let decoded = MltConverter::mlt_to_mvt(mlt.as_bytes()).ok().unwrap();
491
492        assert_eq!(
493            mvt_to_tile_layers(decoded.as_bytes()).unwrap(),
494            mvt_to_tile_layers(MULTIPOLYGON).unwrap()
495        );
496    }
497
498    #[test]
499    fn v2_encoding_of_a_multipolygon_layer_decodes_to_the_same_layers() {
500        let mut options = MltEncoderOptions::new();
501        options.set_wire_version(MltWireVersion::V02);
502
503        let mlt = MltConverter::mvt_to_mlt(MULTIPOLYGON, &options)
504            .ok()
505            .unwrap();
506        let decoded = MltConverter::mlt_to_mvt(mlt.as_bytes()).ok().unwrap();
507
508        assert_eq!(
509            mvt_to_tile_layers(decoded.as_bytes()).unwrap(),
510            mvt_to_tile_layers(MULTIPOLYGON).unwrap()
511        );
512    }
513
514    #[test]
515    fn v2_tessellated_encoding_without_outlines_decodes_a_polygon_as_triangles() {
516        let mut options = MltEncoderOptions::new();
517        options.set_wire_version(MltWireVersion::V02);
518        options.set_tessellate(true);
519        options.set_allow_triangles_only(true);
520
521        let mlt = MltConverter::mvt_to_mlt(POLYGON, &options).ok().unwrap();
522        let decoded = MltConverter::mlt_to_mvt(mlt.as_bytes()).ok().unwrap();
523
524        assert_debug_snapshot!(mvt_to_tile_layers(decoded.as_bytes()).unwrap(), @r#"
525        [
526            TileLayer {
527                name: "layer",
528                extent: Extent(
529                    4096,
530                ),
531                property_names: [
532                    "key",
533                ],
534                property_kinds: [
535                    Bool,
536                ],
537                m_value_names: [],
538                m_value_kinds: [],
539                nested_names: [],
540                nested_kinds: [],
541                z_step: None,
542                features: [
543                    TileFeature {
544                        id: Some(
545                            1,
546                        ),
547                        geometry: POLYGON((3 6,8 12,20 34,3 6)),
548                        properties: [
549                            Bool(
550                                Some(
551                                    true,
552                                ),
553                            ),
554                        ],
555                        m_values: [],
556                        nested: [],
557                        z: [],
558                    },
559                ],
560            },
561        ]
562        "#);
563    }
564
565    #[test]
566    fn builder_point_layer_with_every_property_kind_round_trips() {
567        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
568        let flag = builder.add_property("flag");
569        let count = builder.add_property("count");
570        let ratio = builder.add_property("ratio");
571        let precise = builder.add_property("precise");
572        let name = builder.add_property("name");
573        add_point(&mut builder, [8, 12], Some(7));
574        builder.set_bool(flag, true).ok().unwrap();
575        builder.set_i64(count, -3).ok().unwrap();
576        builder.set_f32(ratio, 1.5).ok().unwrap();
577        builder.set_f64(precise, 2.25).ok().unwrap();
578        builder.set_str(name, "alpha").ok().unwrap();
579
580        let mut out = MltBuffer::new();
581        builder
582            .encode_into(&MltEncoderOptions::new(), &mut out)
583            .ok()
584            .unwrap();
585
586        let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
587        let layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
588        assert_eq!(layers.len(), 1);
589
590        assert_debug_snapshot!(layers[0].features(), @r#"
591        [
592            TileFeature {
593                id: Some(
594                    7,
595                ),
596                geometry: POINT(8 12),
597                properties: [
598                    Bool(
599                        Some(
600                            true,
601                        ),
602                    ),
603                    I64(
604                        Some(
605                            -3,
606                        ),
607                    ),
608                    F32(
609                        Some(
610                            1.5,
611                        ),
612                    ),
613                    F64(
614                        Some(
615                            2.25,
616                        ),
617                    ),
618                    Str(
619                        Some(
620                            "alpha",
621                        ),
622                    ),
623                ],
624                m_values: [],
625                nested: [],
626                z: [],
627            },
628        ]
629        "#);
630    }
631
632    #[test]
633    fn builder_reset_rejects_an_empty_layer_name() {
634        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
635
636        let error = builder.reset("", 4096).err().unwrap();
637
638        assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
639    }
640
641    #[test]
642    fn builder_reset_rejects_a_zero_extent() {
643        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
644
645        let error = builder.reset("layer", 0).err().unwrap();
646
647        assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
648    }
649
650    #[test]
651    fn builder_layers_appended_to_one_buffer_decode_as_one_tile() {
652        let mut out = MltBuffer::new();
653        let options = MltEncoderOptions::new();
654        let mut builder = MltLayerBuilder::new("first", 4096).ok().unwrap();
655        add_point(&mut builder, [1, 1], None);
656        builder.encode_into(&options, &mut out).ok().unwrap();
657        builder.reset("second", 4096).ok().unwrap();
658        add_point(&mut builder, [2, 2], None);
659        builder.encode_into(&options, &mut out).ok().unwrap();
660
661        let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
662        let names: Vec<_> = mvt_to_tile_layers(mvt.as_bytes())
663            .unwrap()
664            .into_iter()
665            .map(|layer| layer.name().to_owned())
666            .collect();
667
668        assert_eq!(names, ["first", "second"]);
669    }
670
671    fn encode_fifty_points(options: &MltEncoderOptions) -> Vec<u8> {
672        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
673        let key = builder.add_property("key");
674        for i in 0..50_i32 {
675            add_point(&mut builder, [i, 2 * i], Some(u64::from(i.unsigned_abs())));
676            builder.set_i64(key, i64::from(i)).ok().unwrap();
677        }
678        let mut out = MltBuffer::new();
679        builder.encode_into(options, &mut out).ok().unwrap();
680        out.as_bytes().to_vec()
681    }
682
683    #[test]
684    fn encoding_on_two_threads_with_separate_options_matches_encoding_on_one() {
685        let v1_options = MltEncoderOptions::new();
686        let mut v2_options = MltEncoderOptions::new();
687        v2_options.set_wire_version(MltWireVersion::V02);
688        let expected = [
689            encode_fifty_points(&v1_options),
690            encode_fifty_points(&v2_options),
691        ];
692
693        let actual = std::thread::scope(|scope| {
694            let v1 = scope.spawn(|| encode_fifty_points(&v1_options));
695            let v2 = scope.spawn(|| encode_fifty_points(&v2_options));
696            [v1.join().unwrap(), v2.join().unwrap()]
697        });
698
699        assert_eq!(actual, expected);
700    }
701
702    #[test]
703    fn clearing_a_buffer_empties_it() {
704        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
705        add_point(&mut builder, [1, 1], None);
706        let mut out = MltBuffer::new();
707        builder
708            .encode_into(&MltEncoderOptions::new(), &mut out)
709            .ok()
710            .unwrap();
711
712        out.clear();
713
714        assert_eq!(out.len(), 0);
715    }
716
717    #[test]
718    fn setting_a_property_before_a_feature_begins_is_invalid() {
719        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
720
721        let key = builder.add_property("key");
722
723        let error = builder.set_i64(key, 1).err().unwrap();
724
725        assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
726        assert_debug_snapshot!(error.message_text(), @r#""no feature begun""#);
727    }
728
729    #[test]
730    fn setting_an_undeclared_property_key_is_invalid() {
731        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
732        add_point(&mut builder, [1, 1], None);
733
734        let error = builder.set_i64(3, 1).err().unwrap();
735
736        assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
737        assert_debug_snapshot!(error.message_text(), @r#""unknown property key 3""#);
738    }
739
740    #[test]
741    fn integer_and_float_values_of_one_key_widen_to_a_float_column() {
742        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
743        let key = builder.add_property("key");
744        add_point(&mut builder, [1, 1], None);
745        builder.set_i64(key, 3).ok().unwrap();
746        add_point(&mut builder, [2, 2], None);
747        builder.set_f32(key, 1.5).ok().unwrap();
748
749        let mut out = MltBuffer::new();
750        builder
751            .encode_into(&MltEncoderOptions::new(), &mut out)
752            .ok()
753            .unwrap();
754
755        let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
756        let layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
757        assert_eq!(layers.len(), 1);
758
759        assert_debug_snapshot!(layers[0].features(), @"
760        [
761            TileFeature {
762                id: None,
763                geometry: POINT(1 1),
764                properties: [
765                    F64(
766                        Some(
767                            3.0,
768                        ),
769                    ),
770                ],
771                m_values: [],
772                nested: [],
773                z: [],
774            },
775            TileFeature {
776                id: None,
777                geometry: POINT(2 2),
778                properties: [
779                    F64(
780                        Some(
781                            1.5,
782                        ),
783                    ),
784                ],
785                m_values: [],
786                nested: [],
787                z: [],
788            },
789        ]
790        ");
791    }
792
793    #[test]
794    fn boolean_and_integer_values_of_one_key_become_text() {
795        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
796        let key = builder.add_property("key");
797        add_point(&mut builder, [1, 1], None);
798        builder.set_bool(key, true).ok().unwrap();
799        add_point(&mut builder, [2, 2], None);
800        builder.set_i64(key, 5).ok().unwrap();
801
802        let mut out = MltBuffer::new();
803        builder
804            .encode_into(&MltEncoderOptions::new(), &mut out)
805            .ok()
806            .unwrap();
807
808        let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
809        let layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
810        assert_eq!(layers.len(), 1);
811
812        assert_debug_snapshot!(layers[0].features(), @r#"
813        [
814            TileFeature {
815                id: None,
816                geometry: POINT(1 1),
817                properties: [
818                    Str(
819                        Some(
820                            "true",
821                        ),
822                    ),
823                ],
824                m_values: [],
825                nested: [],
826                z: [],
827            },
828            TileFeature {
829                id: None,
830                geometry: POINT(2 2),
831                properties: [
832                    Str(
833                        Some(
834                            "5",
835                        ),
836                    ),
837                ],
838                m_values: [],
839                nested: [],
840                z: [],
841            },
842        ]
843        "#);
844    }
845
846    fn add_point(builder: &mut MltLayerBuilder, [x, y]: [i32; 2], id: Option<u64>) {
847        builder
848            .begin_mvt_feature(MltMvtGeometryType::Point, &[9, zigzag(x), zigzag(y)], id)
849            .ok()
850            .unwrap();
851    }
852
853    fn zigzag(value: i32) -> u32 {
854        ((value << 1) ^ (value >> 31)).cast_unsigned()
855    }
856
857    fn mvt_commands(parts: &[&[i32]], close: bool) -> Vec<u32> {
858        let mut commands = Vec::new();
859        let (mut x, mut y) = (0, 0);
860        for part in parts {
861            for (i, xy) in part.chunks(2).enumerate() {
862                match i {
863                    0 => commands.push(9),
864                    1 => commands.push(2 | (u32::try_from(part.len() / 2 - 1).unwrap() << 3)),
865                    _ => {}
866                }
867                commands.extend([zigzag(xy[0] - x), zigzag(xy[1] - y)]);
868                (x, y) = (xy[0], xy[1]);
869            }
870            if close {
871                commands.push(15);
872            }
873        }
874        commands
875    }
876
877    fn decoded_features(builder: &MltLayerBuilder) -> Vec<mlt_core::TileFeature> {
878        decoded_features_with(builder, &MltEncoderOptions::new())
879    }
880
881    fn decoded_features_with(
882        builder: &MltLayerBuilder,
883        options: &MltEncoderOptions,
884    ) -> Vec<mlt_core::TileFeature> {
885        let mut out = MltBuffer::new();
886        builder.encode_into(options, &mut out).ok().unwrap();
887        let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
888        let mut layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
889        assert_eq!(layers.len(), 1);
890        layers.remove(0).features().to_vec()
891    }
892
893    #[test]
894    fn mvt_point_feature_matches_mvt_conversion_of_the_same_layer() {
895        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
896        let key = builder.add_property("key");
897        builder
898            .begin_mvt_feature(MltMvtGeometryType::Point, &[9, 50, 34], Some(1))
899            .ok()
900            .unwrap();
901        builder.set_bool(key, true).ok().unwrap();
902
903        let from_mvt = MltConverter::mvt_to_mlt(POINT, &MltEncoderOptions::new())
904            .ok()
905            .unwrap();
906        let mvt = MltConverter::mlt_to_mvt(from_mvt.as_bytes()).ok().unwrap();
907        let mvt_layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
908        assert_eq!(decoded_features(&builder), mvt_layers[0].features());
909    }
910
911    #[test]
912    fn v2_mvt_multipolygon_with_a_hole_splits_rings_by_winding() {
913        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
914        let commands = mvt_commands(
915            &[
916                &[0, 0, 100, 0, 100, 100, 0, 100],
917                &[20, 20, 20, 40, 40, 40, 40, 20],
918                &[200, 200, 300, 200, 300, 300, 200, 300],
919            ],
920            true,
921        );
922        builder
923            .begin_mvt_feature(MltMvtGeometryType::Polygon, &commands, None)
924            .ok()
925            .unwrap();
926        let mut options = MltEncoderOptions::new();
927        options.set_wire_version(MltWireVersion::V02);
928
929        assert_debug_snapshot!(decoded_features_with(&builder, &options), @"
930        [
931            TileFeature {
932                id: None,
933                geometry: MULTIPOLYGON(((0 0,100 0,100 100,0 100,0 0),(20 20,20 40,40 40,40 20,20 20)),((200 200,300 200,300 300,200 300,200 200))),
934                properties: [],
935                m_values: [],
936                nested: [],
937                z: [],
938            },
939        ]
940        ");
941    }
942
943    #[test]
944    fn mvt_features_take_the_single_or_multi_type_from_their_part_count() {
945        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
946        let mvt = MltMvtGeometryType::LineString;
947        builder
948            .begin_mvt_feature(mvt, &mvt_commands(&[&[0, 0, 10, 10, 20, 5]], false), None)
949            .ok()
950            .unwrap();
951        let lines = mvt_commands(&[&[1, 1, 2, 2], &[5, 5, 6, 7]], false);
952        builder.begin_mvt_feature(mvt, &lines, None).ok().unwrap();
953        builder
954            .begin_mvt_feature(
955                MltMvtGeometryType::Polygon,
956                &mvt_commands(&[&[0, 0, 9, 0, 9, 9]], true),
957                None,
958            )
959            .ok()
960            .unwrap();
961
962        assert_debug_snapshot!(decoded_features(&builder), @"
963        [
964            TileFeature {
965                id: None,
966                geometry: LINESTRING(0 0,10 10,20 5),
967                properties: [],
968                m_values: [],
969                nested: [],
970                z: [],
971            },
972            TileFeature {
973                id: None,
974                geometry: MULTILINESTRING((1 1,2 2),(5 5,6 7)),
975                properties: [],
976                m_values: [],
977                nested: [],
978                z: [],
979            },
980            TileFeature {
981                id: None,
982                geometry: POLYGON((0 0,9 0,9 9,0 0)),
983                properties: [],
984                m_values: [],
985                nested: [],
986                z: [],
987            },
988        ]
989        ");
990    }
991
992    #[test]
993    fn mvt_points_of_one_move_to_become_a_multipoint() {
994        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
995        builder
996            .begin_mvt_feature(MltMvtGeometryType::Point, &[25, 2, 4, 4, 4, 4, 4], None)
997            .ok()
998            .unwrap();
999
1000        assert_debug_snapshot!(decoded_features(&builder), @"
1001        [
1002            TileFeature {
1003                id: None,
1004                geometry: MULTIPOINT(1 2,3 4,5 6),
1005                properties: [],
1006                m_values: [],
1007                nested: [],
1008                z: [],
1009            },
1010        ]
1011        ");
1012    }
1013
1014    #[test]
1015    fn truncated_mvt_commands_are_invalid_and_leave_no_feature_behind() {
1016        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1017        builder
1018            .begin_mvt_feature(MltMvtGeometryType::Point, &[9, 2, 2], None)
1019            .ok()
1020            .unwrap();
1021
1022        let error = builder
1023            .begin_mvt_feature(MltMvtGeometryType::LineString, &[9, 2, 2, 18, 4], None)
1024            .err()
1025            .unwrap();
1026
1027        assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
1028        assert_debug_snapshot!(error.message_text(), @r#""invalid MVT geometry: ends inside a command""#);
1029        assert_eq!(decoded_features(&builder).len(), 1);
1030    }
1031
1032    #[test]
1033    fn line_to_in_a_point_geometry_is_invalid() {
1034        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1035
1036        let error = builder
1037            .begin_mvt_feature(MltMvtGeometryType::Point, &[9, 2, 2, 10, 4, 4], None)
1038            .err()
1039            .unwrap();
1040
1041        assert_debug_snapshot!(error.message_text(), @r#""invalid MVT geometry: a LineTo outside a line or ring""#);
1042    }
1043
1044    #[test]
1045    fn empty_mvt_commands_are_invalid() {
1046        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1047
1048        let error = builder
1049            .begin_mvt_feature(MltMvtGeometryType::Polygon, &[], None)
1050            .err()
1051            .unwrap();
1052
1053        assert_debug_snapshot!(error.message_text(), @r#""invalid MVT geometry: no rings""#);
1054    }
1055
1056    #[test]
1057    fn declaring_a_property_name_again_returns_its_key() {
1058        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1059        let first = builder.add_property("name");
1060        builder.add_property("other");
1061
1062        assert_eq!(builder.add_property("name"), first);
1063    }
1064
1065    #[test]
1066    fn reset_forgets_the_declared_property_names() {
1067        let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1068        builder.add_property("first");
1069        builder.add_property("second");
1070        builder.reset("layer", 4096).ok().unwrap();
1071
1072        assert_eq!(builder.add_property("second"), 0);
1073    }
1074
1075    #[test]
1076    fn decoding_with_a_tiny_memory_limit_reports_the_budget() {
1077        let mlt = MltConverter::mvt_to_mlt(POINT, &MltEncoderOptions::new())
1078            .ok()
1079            .unwrap();
1080
1081        let error = MltConverter::mlt_to_mvt_with_limit(mlt.as_bytes(), 1)
1082            .err()
1083            .unwrap();
1084
1085        assert_eq!(error.kind(), ConvertErrorKind::InvalidInput);
1086        assert_debug_snapshot!(error.message_text(), @r#""memory limit exceeded: limit=1, used=0, requested=8""#);
1087    }
1088
1089    #[test]
1090    fn decoding_with_a_generous_memory_limit_matches_the_default() {
1091        let mlt = MltConverter::mvt_to_mlt(POINT, &MltEncoderOptions::new())
1092            .ok()
1093            .unwrap();
1094
1095        let limited = MltConverter::mlt_to_mvt_with_limit(mlt.as_bytes(), 1 << 30)
1096            .ok()
1097            .unwrap();
1098
1099        assert_eq!(
1100            limited.as_bytes(),
1101            MltConverter::mlt_to_mvt(mlt.as_bytes())
1102                .ok()
1103                .unwrap()
1104                .as_bytes()
1105        );
1106    }
1107
1108    #[test]
1109    fn decoding_garbage_as_mlt_reports_invalid_input_with_the_parser_message() {
1110        let error = MltConverter::mlt_to_mvt(&[0xff]).err().unwrap();
1111
1112        assert_eq!(error.kind(), ConvertErrorKind::InvalidInput);
1113        assert_debug_snapshot!(error.message_text(), @r#""buffer underflow: needed 2 bytes, but only 1 remain""#);
1114    }
1115
1116    #[test]
1117    fn encoding_garbage_as_mvt_reports_encoding_failed_with_the_parser_message() {
1118        let error = MltConverter::mvt_to_mlt(&[0xff], &MltEncoderOptions::new())
1119            .err()
1120            .unwrap();
1121
1122        assert_eq!(error.kind(), ConvertErrorKind::EncodingFailed);
1123        assert_debug_snapshot!(error.message_text(), @r#""MVT error: protobuf decode error: unexpected end of buffer""#);
1124    }
1125
1126    #[test]
1127    fn tile_stats_split_each_layer_by_column_role() {
1128        let mut out = MltBuffer::new();
1129        let options = MltEncoderOptions::new();
1130        let mut builder = MltLayerBuilder::new("with_id", 4096).ok().unwrap();
1131        let name = builder.add_property("name");
1132        add_point(&mut builder, [1, 1], Some(3));
1133        builder.set_str(name, "alpha").ok().unwrap();
1134        builder.encode_into(&options, &mut out).ok().unwrap();
1135        builder.reset("bare", 4096).ok().unwrap();
1136        add_point(&mut builder, [2, 2], None);
1137        builder.encode_into(&options, &mut out).ok().unwrap();
1138
1139        let stats = MltTileStats::from_bytes(out.as_bytes()).ok().unwrap();
1140
1141        assert_eq!(stats.layer_count(), 2);
1142        assert_eq!(
1143            [stats.layer_name_text(0), stats.layer_name_text(1)],
1144            ["with_id", "bare"]
1145        );
1146        assert_debug_snapshot!([stats.layer_bytes(0), stats.layer_bytes(1)], @"
1147        [
1148            Some(
1149                MltLayerBytes {
1150                    size: 52,
1151                    geometry: 12,
1152                    properties: 15,
1153                    ids: 5,
1154                    metadata: 20,
1155                },
1156            ),
1157            Some(
1158                MltLayerBytes {
1159                    size: 22,
1160                    geometry: 12,
1161                    properties: 0,
1162                    ids: 0,
1163                    metadata: 10,
1164                },
1165            ),
1166        ]
1167        ");
1168    }
1169
1170    #[test]
1171    fn tile_stats_sizes_add_up_to_the_tile_without_the_size_varints() {
1172        let mut options = MltEncoderOptions::new();
1173        options.set_wire_version(MltWireVersion::V02);
1174        let mlt = MltConverter::mvt_to_mlt(MULTIPOLYGON, &options)
1175            .ok()
1176            .unwrap();
1177
1178        let stats = MltTileStats::from_bytes(mlt.as_bytes()).ok().unwrap();
1179        let MltLayerBytes {
1180            size,
1181            geometry,
1182            properties,
1183            ids,
1184            metadata,
1185        } = stats.layer_bytes(0).unwrap();
1186
1187        assert_eq!(stats.layer_count(), 1);
1188        assert_eq!(usize::try_from(size).unwrap() + 1, mlt.len());
1189        assert_eq!(geometry + properties + ids + metadata, size);
1190    }
1191
1192    #[test]
1193    fn tile_stats_past_the_last_layer_are_none() {
1194        let mlt = MltConverter::mvt_to_mlt(POINT, &MltEncoderOptions::new())
1195            .ok()
1196            .unwrap();
1197
1198        let stats = MltTileStats::from_bytes(mlt.as_bytes()).ok().unwrap();
1199
1200        assert_eq!(stats.layer_bytes(1), None);
1201    }
1202
1203    #[test]
1204    fn tile_stats_of_garbage_report_invalid_input() {
1205        let error = MltTileStats::from_bytes(&[0xff]).err().unwrap();
1206
1207        assert_eq!(error.kind(), ConvertErrorKind::InvalidInput);
1208    }
1209}