Skip to main content

mlt_core/decoder/
iterators.rs

1//! Zero-copy per-feature view into a fully-decoded [`Layer01<Parsed>`].
2//!
3//! [`ParsedLayer01::iter_features`] yields one [`FeatureRef`] per feature via
4//! [`LendingIterator`].  [`FeatureRef::iter_properties`] exposes per-feature
5//! property values as flat [`ColumnRef`] items; `SharedDict` columns are
6//! transparently expanded and null values are skipped.
7//!
8//! # Iterator model
9//!
10//! Feature iteration uses [`LendingIterator`] rather than [`std::iter::Iterator`].
11//! This allows the iterator to reuse an internal buffer across steps — the
12//! [`FeatureRef`] borrows its property values from that buffer — eliminating a
13//! per-feature `Vec` allocation.
14//!
15//! The consequence is that each [`FeatureRef`] must be dropped before calling
16//! [`LendingIterator::next`] again, so standard adapters like `.map()` and
17//! `.collect()` are **not** available directly.  Use a `while let` loop instead:
18
19use std::fmt;
20
21use geo_types::Geometry;
22
23use crate::decoder::{Layer01, ParsedLayer01, ParsedProperty, ParsedScalar, RawProperty};
24use crate::{Lazy, LazyParsed, MltResult, Parsed};
25
26/// A minimal lending (streaming) iterator trait.
27///
28/// Unlike [`std::iter::Iterator`], the item type may borrow from the iterator
29/// itself, enabling zero-allocation iteration where the inner buffer is reused
30/// across steps.
31///
32/// Use a `while let` loop to drive the iterator:
33/// ```ignore
34/// let mut iter = layer.iter_features();
35/// while let Some(feat) = iter.next() {
36///     let feat = feat?;
37///     /* use feat here — it borrows from iter */
38/// }
39/// ```
40pub trait LendingIterator {
41    /// The type of each element, which may borrow from `self`.
42    type Item<'this>
43    where
44        Self: 'this;
45
46    /// Advance the iterator, returning the next element or `None` when exhausted.
47    fn next(&mut self) -> Option<Self::Item<'_>>;
48}
49
50impl<'a> Layer01<'a, Lazy> {
51    /// Iterate over the property column names of this layer, in order.
52    ///
53    /// Regular columns yield one [`PropName`]; `SharedDict` columns yield one name per
54    /// sub-item.  Names are available even before any column data has been decoded.
55    ///
56    /// Pair with [`FeatureRef::iter_all_properties`] to associate per-feature
57    /// values with their column names.
58    pub fn iterate_prop_names(&self) -> impl Iterator<Item = PropName<'a>> + '_ {
59        let props = &self.properties;
60        let mut col_idx = 0;
61        let mut dict_idx = 0;
62        std::iter::from_fn(move || {
63            loop {
64                let idx = col_idx;
65                col_idx += 1;
66                let name = match props.get(idx)? {
67                    LazyParsed::Raw(r) => raw_col_name(r, &mut dict_idx),
68                    LazyParsed::Parsed(p) => parsed_col_name(p, &mut dict_idx),
69                    LazyParsed::ParsingFailed => None,
70                };
71                if dict_idx != 0 {
72                    col_idx -= 1;
73                }
74                if let Some(n) = name {
75                    return Some(n);
76                }
77            }
78        })
79    }
80}
81
82impl<'a> ParsedLayer01<'a> {
83    /// Iterate over all features in this fully-decoded layer via a [`LendingIterator`].
84    ///
85    /// Yields one `MltResult<`[`FeatureRef`]`>` per feature. Geometry decoding can
86    /// fail, hence the `Result` wrapper.
87    ///
88    /// ```text
89    /// let mut iter = parsed.iter_features();
90    /// while let Some(feat) = iter.next() {
91    ///     let feat = feat?;
92    ///     for col in feat.iter_properties() {
93    ///        // or use iter_all_properties() to include Nones
94    ///     }
95    /// }
96    /// ```
97    ///
98    /// All inner iterators — [`FeatureRef::iter_properties`],
99    /// [`FeatureRef::iter_all_properties`], and the name iterators — implement the
100    /// standard [`std::iter::Iterator`] trait and compose normally.
101    #[must_use]
102    pub fn iter_features(&self) -> Layer01FeatureIter<'_, 'a> {
103        Layer01FeatureIter::new(self)
104    }
105
106    /// Iterate over the property column names of this layer, in order.
107    /// See [`Layer01::iterate_prop_names`] for details.
108    pub fn iterate_prop_names(&self) -> impl Iterator<Item = PropName<'a>> + '_ {
109        Layer01PropNamesIter::new(&self.properties)
110    }
111}
112
113/// A zero-allocation two-part property name yielded by [`FeatureRef::iter_properties`].
114///
115/// The two parts concatenate on [`Display`](fmt::Display) as `"{}{}"`:
116/// - For regular columns: `(column_name, "")` — zero allocation, second part always empty.
117/// - For `SharedDict` sub-items: `(prefix, suffix)` — both borrow directly from layer data.
118///
119/// Structural [`PartialEq`] compares both parts independently.  Use [`PartialEq<str>`] or
120/// [`PartialEq<&str>`] (also implemented) to compare against a plain `&str` as if the two
121/// parts were concatenated.
122#[derive(Debug, Clone, Copy)] // WARN: do not auto-derive PartialEq,Eq,Hash as it won't be correct
123pub struct PropName<'a>(&'a str, &'a str);
124
125impl fmt::Display for PropName<'_> {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        f.write_str(self.0)?;
128        f.write_str(self.1)
129    }
130}
131
132impl PartialEq<PropName<'_>> for PropName<'_> {
133    fn eq(&self, other: &PropName<'_>) -> bool {
134        // Compare the concatenated strings byte-by-byte without allocating.
135        let (a0, a1) = (self.0.as_bytes(), self.1.as_bytes());
136        let a = a0.iter().chain(a1);
137        let (b0, b1) = (other.0.as_bytes(), other.1.as_bytes());
138        let b = b0.iter().chain(b1);
139        let combined_len_eq = a0.len() + a1.len() == b0.len() + b1.len();
140        combined_len_eq && a.eq(b)
141    }
142}
143
144impl PartialEq<str> for PropName<'_> {
145    /// Returns `true` if `other == self.0 + self.1`.
146    fn eq(&self, other: &str) -> bool {
147        other.strip_prefix(self.0) == Some(self.1)
148    }
149}
150
151impl PartialEq<PropName<'_>> for str {
152    fn eq(&self, other: &PropName<'_>) -> bool {
153        other == self
154    }
155}
156
157impl PartialEq<&str> for PropName<'_> {
158    fn eq(&self, other: &&str) -> bool {
159        self == *other
160    }
161}
162
163impl PartialEq<PropName<'_>> for &str {
164    fn eq(&self, other: &PropName<'_>) -> bool {
165        other == *self
166    }
167}
168
169/// A borrowed, non-null per-feature property value.
170///
171/// Nullability is lifted to [`ColumnRef`]: only non-null values appear in
172/// [`FeatureRef::iter_properties`].
173#[derive(Debug, Clone, Copy, PartialEq)]
174pub enum PropValueRef<'a> {
175    Bool(bool),
176    I8(i8),
177    U8(u8),
178    I32(i32),
179    U32(u32),
180    I64(i64),
181    U64(u64),
182    F32(f32),
183    F64(f64),
184    Str(&'a str),
185}
186
187macro_rules! impl_from_for_prop_value_ref {
188    ($($ty:ty => $variant:ident),+ $(,)?) => {
189        $(impl From<$ty> for PropValueRef<'_> {
190            fn from(v: $ty) -> Self { Self::$variant(v) }
191        })+
192    };
193}
194impl_from_for_prop_value_ref!(
195    bool => Bool, i8 => I8, u8 => U8,
196    i32 => I32, u32 => U32,
197    i64 => I64, u64 => U64,
198    f32 => F32, f64 => F64,
199);
200
201/// A single non-null property value for one feature, yielded by [`FeatureRef::iter_properties`].
202///
203/// `name` is a [`PropName`] that displays as `"{prefix}{suffix}"`.
204/// All borrows are zero-copy from the layer data.
205#[derive(Debug, Clone, Copy, PartialEq)]
206pub struct ColumnRef<'a> {
207    pub name: PropName<'a>,
208    pub value: PropValueRef<'a>,
209}
210
211/// A single map feature returned by [`ParsedLayer01::iter_features`].
212///
213/// Borrows `values` from the outer [`Layer01FeatureIter`] buffer — it must be
214/// dropped before calling [`LendingIterator::next`] again.
215#[derive(Debug)]
216pub struct FeatureRef<'feat, 'layer: 'feat> {
217    /// Optional feature ID.
218    pub id: Option<u64>,
219    /// Geometry in [`Geometry<i32>`] form (owned, decoded on demand by the iterator).
220    pub geometry: Geometry<i32>,
221    /// Borrowed slice of column descriptors from the layer; used to yield column names.
222    columns: &'layer [ParsedProperty<'layer>],
223    /// Per-feature values in column order, one per slot (scalar, string, or `SharedDict`
224    /// sub-item).  Borrowed from the iterator's reused buffer — no allocation per feature.
225    values: &'feat [Option<PropValueRef<'layer>>],
226}
227
228impl<'feat, 'layer: 'feat> FeatureRef<'feat, 'layer> {
229    /// Iterate over every property slot for this feature, **values only**, in column order.
230    ///
231    /// Yields `Option<PropValueRef>`:
232    /// - `Some(value)` — the slot contains a non-null value.
233    /// - `None` — the slot is null / absent.
234    ///
235    /// Use [`Layer01::iterate_prop_names`] to pair values with their column names.
236    pub fn iter_all_properties(&self) -> impl Iterator<Item = Option<PropValueRef<'layer>>> + '_ {
237        self.values.iter().copied()
238    }
239
240    /// Iterate over all non-null properties for this feature.
241    ///
242    /// `SharedDict` columns are transparently expanded into one [`ColumnRef`] per sub-item.
243    /// Null / absent values are skipped entirely. The iterator is infallible.
244    pub fn iter_properties(&self) -> impl Iterator<Item = ColumnRef<'layer>> + '_ {
245        Layer01PropNamesIter::new(self.columns)
246            .zip(self.values.iter().copied())
247            .filter_map(|(name, opt_val)| opt_val.map(|value| ColumnRef { name, value }))
248    }
249
250    /// Look up a property by name, returning its value if present and non-null.
251    ///
252    /// For `SharedDict` columns the expected name is `"{prefix}{suffix}"`, matching
253    /// the key used by [`iter_properties`](Self::iter_properties).
254    #[must_use]
255    pub fn get_property(&self, name: &str) -> Option<PropValueRef<'layer>> {
256        self.iter_properties()
257            .find(|col| col.name == name)
258            .map(|col| col.value)
259    }
260}
261
262// ── Column name helpers ───────────────────────────────────────────────────────
263
264/// Iterates the property column names of a fully-decoded [`ParsedLayer01`].
265///
266/// Regular columns yield one [`PropName`]; `SharedDict` columns yield one name per
267/// sub-item (`(prefix, suffix)`).
268pub(crate) struct Layer01PropNamesIter<'a, 'p> {
269    props: &'a [ParsedProperty<'p>],
270    col_idx: usize,
271    dict_idx: usize,
272}
273
274impl<'a, 'p> Layer01PropNamesIter<'a, 'p> {
275    pub(crate) fn new(props: &'a [ParsedProperty<'p>]) -> Self {
276        Self {
277            props,
278            col_idx: 0,
279            dict_idx: 0,
280        }
281    }
282}
283
284impl<'a> Iterator for Layer01PropNamesIter<'_, 'a> {
285    type Item = PropName<'a>;
286
287    fn next(&mut self) -> Option<PropName<'a>> {
288        loop {
289            let col_idx = self.col_idx;
290            self.col_idx += 1;
291            let name = parsed_col_name(self.props.get(col_idx)?, &mut self.dict_idx);
292            if self.dict_idx != 0 {
293                self.col_idx -= 1; // SharedDict not yet exhausted: revisit this column
294            }
295            if let Some(n) = name {
296                return Some(n);
297            }
298        }
299    }
300}
301
302/// Yield the next [`PropName`] from a [`ParsedProperty`] column.
303#[inline]
304fn parsed_col_name<'p>(prop: &ParsedProperty<'p>, dict_idx: &mut usize) -> Option<PropName<'p>> {
305    use ParsedProperty as P;
306    match prop {
307        P::Bool(s) => Some(PropName(s.name, "")),
308        P::I8(s) => Some(PropName(s.name, "")),
309        P::U8(s) => Some(PropName(s.name, "")),
310        P::I32(s) => Some(PropName(s.name, "")),
311        P::U32(s) => Some(PropName(s.name, "")),
312        P::I64(s) => Some(PropName(s.name, "")),
313        P::U64(s) => Some(PropName(s.name, "")),
314        P::F32(s) => Some(PropName(s.name, "")),
315        P::F64(s) => Some(PropName(s.name, "")),
316        P::Str(s) => Some(PropName(s.name, "")),
317        P::SharedDict(sd) => {
318            if *dict_idx < sd.items.len() {
319                let idx = *dict_idx;
320                *dict_idx += 1;
321                Some(PropName(sd.prefix, sd.items[idx].suffix))
322            } else {
323                *dict_idx = 0;
324                None
325            }
326        }
327    }
328}
329
330/// Yield the next [`PropName`] from a [`RawProperty`] column.  See [`parsed_col_name`].
331#[inline]
332fn raw_col_name<'p>(prop: &RawProperty<'p>, dict_idx: &mut usize) -> Option<PropName<'p>> {
333    use RawProperty as P;
334    match prop {
335        P::Bool(s)
336        | P::I8(s)
337        | P::U8(s)
338        | P::I32(s)
339        | P::U32(s)
340        | P::I64(s)
341        | P::U64(s)
342        | P::F32(s)
343        | P::F64(s) => Some(PropName(s.name, "")),
344        P::Str(s) => Some(PropName(s.name, "")),
345        P::SharedDict(sd) => {
346            if *dict_idx < sd.children.len() {
347                let idx = *dict_idx;
348                *dict_idx += 1;
349                Some(PropName(sd.name, sd.children[idx].name))
350            } else {
351                *dict_idx = 0;
352                None
353            }
354        }
355    }
356}
357
358/// A boxed per-column-slot value iterator yielding one `Option<`[`PropValueRef`]`>` per feature.
359type ColValIter<'l> = Box<dyn Iterator<Item = Option<PropValueRef<'l>>> + 'l>;
360
361/// Build one [`ColValIter`] per property column "slot" from a decoded column slice.
362///
363/// - Scalar and string columns contribute one slot each.
364/// - `SharedDict` columns contribute one slot per sub-item.
365fn build_col_iters<'p>(columns: &'p [ParsedProperty<'p>]) -> Vec<ColValIter<'p>> {
366    use ParsedProperty as PP;
367    let mut iters: Vec<ColValIter<'p>> = Vec::new();
368    for col in columns {
369        match col {
370            PP::Bool(s) => iters.push(scalar_col_iter(s)),
371            PP::I8(s) => iters.push(scalar_col_iter(s)),
372            PP::U8(s) => iters.push(scalar_col_iter(s)),
373            PP::I32(s) => iters.push(scalar_col_iter(s)),
374            PP::U32(s) => iters.push(scalar_col_iter(s)),
375            PP::I64(s) => iters.push(scalar_col_iter(s)),
376            PP::U64(s) => iters.push(scalar_col_iter(s)),
377            PP::F32(s) => iters.push(scalar_col_iter(s)),
378            PP::F64(s) => iters.push(scalar_col_iter(s)),
379            PP::Str(strings) => {
380                let data: &'p str = strings.data.as_ref();
381                let lengths: &'p [i32] = &strings.lengths;
382                let mut curr_end: u32 = 0;
383                let mut feat_idx = 0usize;
384                iters.push(Box::new(std::iter::from_fn(move || {
385                    let &end_i32 = lengths.get(feat_idx)?;
386                    feat_idx += 1;
387                    if end_i32 >= 0 {
388                        let start = curr_end as usize;
389                        curr_end = end_i32.cast_unsigned();
390                        Some(data.get(start..curr_end as usize).map(PropValueRef::Str))
391                    } else {
392                        // Null slot: curr_end unchanged (null encodes the current byte offset).
393                        Some(None)
394                    }
395                })));
396            }
397            PP::SharedDict(dict) => {
398                for item in &dict.items {
399                    let dict_ref: &'p _ = dict;
400                    let item_ref: &'p _ = item;
401                    let mut feat_idx = 0usize;
402                    iters.push(Box::new(std::iter::from_fn(move || {
403                        if feat_idx >= item_ref.ranges.len() {
404                            return None;
405                        }
406                        let idx = feat_idx;
407                        feat_idx += 1;
408                        Some(item_ref.get(dict_ref, idx).map(PropValueRef::Str))
409                    })));
410                }
411            }
412        }
413    }
414    iters
415}
416
417/// Build a boxed value iterator for a single scalar property column.
418fn scalar_col_iter<'p, T>(scalar: &'p ParsedScalar<'p, T>) -> ColValIter<'p>
419where
420    T: Copy + PartialEq,
421    PropValueRef<'p>: From<T>,
422{
423    Box::new(scalar.iter_optional().map(|o| o.map(PropValueRef::from)))
424}
425
426/// Iterator over the features of a fully-decoded [`Layer01<Parsed>`].
427///
428/// Returned by [`ParsedLayer01::iter_features`]. Implements [`LendingIterator`]:
429/// advance with `while let Some(feat) = iter.next()`.
430///
431/// Holds one O(1)-per-step cursor per property column slot. On each step the
432/// per-column cursors are advanced and their results written into a reused
433/// `values_buf` — yielding a [`FeatureRef`] that borrows that buffer with no
434/// per-feature heap allocation.
435pub struct Layer01FeatureIter<'layer, 'data: 'layer> {
436    layer: &'layer Layer01<'data, Parsed>,
437    index: usize,
438    feature_count: usize,
439    /// ID iterator, `None` when the layer has no ID column.
440    id_iter: Option<crate::utils::PresenceOptIter<'layer, u64>>,
441    /// One boxed value iterator per column slot (scalar, string, or `SharedDict` sub-item).
442    col_iters: Vec<ColValIter<'layer>>,
443    /// Reused buffer: filled on each `next()` call, borrowed by the yielded [`FeatureRef`].
444    values_buf: Vec<Option<PropValueRef<'layer>>>,
445}
446
447impl<'layer, 'data: 'layer> Layer01FeatureIter<'layer, 'data> {
448    fn new(layer: &'layer Layer01<'data, Parsed>) -> Self {
449        let col_iters = build_col_iters(&layer.properties);
450        let cap = col_iters.len();
451        Self {
452            layer,
453            index: 0,
454            feature_count: layer.feature_count(),
455            id_iter: layer.id.as_ref().map(|id| id.iter_optional()),
456            col_iters,
457            values_buf: Vec::with_capacity(cap),
458        }
459    }
460
461    /// Number of features not yet yielded.
462    #[must_use]
463    pub fn len(&self) -> usize {
464        self.feature_count - self.index
465    }
466
467    /// Returns `true` if all features have been yielded.
468    #[must_use]
469    pub fn is_empty(&self) -> bool {
470        self.index >= self.feature_count
471    }
472}
473
474impl<'layer> LendingIterator for Layer01FeatureIter<'layer, '_> {
475    type Item<'this>
476        = MltResult<FeatureRef<'this, 'layer>>
477    where
478        Self: 'this;
479
480    fn next(&mut self) -> Option<Self::Item<'_>> {
481        let index = self.index;
482        if index >= self.feature_count {
483            return None;
484        }
485        self.index += 1;
486
487        // Advance all per-feature cursors unconditionally, even if geometry decode fails,
488        // so that IDs and property values remain aligned with geometry indices.
489        let id = self.id_iter.as_mut().and_then(Iterator::next).flatten();
490        self.values_buf.clear();
491        self.values_buf
492            .extend(self.col_iters.iter_mut().map(|it| it.next().flatten()));
493
494        Some(
495            self.layer
496                .geometry
497                .to_geojson(index)
498                .map(|geometry| FeatureRef {
499                    id,
500                    geometry,
501                    columns: &self.layer.properties,
502                    values: &self.values_buf,
503                }),
504        )
505    }
506}
507
508#[cfg(test)]
509mod tests {
510    use geo_types::Point;
511    use serde_json::Value;
512
513    use super::*;
514    use crate::Layer;
515    use crate::decoder::GeometryValues;
516    use crate::encoder::model::StagedLayer;
517    use crate::encoder::{Codecs, Encoder, Presence, StagedId, StagedProperty, StagedSharedDict};
518    use crate::test_helpers::{dec, parser};
519
520    fn layer_buf(staged: StagedLayer) -> Vec<u8> {
521        staged
522            .encode_into(Encoder::default(), &mut Codecs::default())
523            .unwrap()
524            .into_layer_bytes()
525            .unwrap()
526    }
527
528    fn three_points() -> GeometryValues {
529        let mut g = GeometryValues::default();
530        g.push_geom(&Geometry::<i32>::Point(Point::new(1, 2)));
531        g.push_geom(&Geometry::<i32>::Point(Point::new(3, 4)));
532        g.push_geom(&Geometry::<i32>::Point(Point::new(5, 6)));
533        g
534    }
535
536    fn empty_layer(name: &str) -> StagedLayer {
537        StagedLayer {
538            name: name.to_string(),
539            extent: 4096,
540            id: StagedId::None,
541            geometry: GeometryValues::default(),
542            properties: vec![],
543        }
544    }
545
546    #[test]
547    fn prop_name_display_concatenates_parts() {
548        assert_eq!(PropName("addr:", "city").to_string(), "addr:city");
549        assert_eq!(PropName("name", "").to_string(), "name");
550        assert_eq!(PropName("", "").to_string(), "");
551    }
552
553    #[test]
554    fn prop_name_eq_str_matches_concatenation() {
555        assert_eq!(PropName("addr:", "city"), "addr:city");
556        assert_eq!("addr:city", PropName("addr:", "city"));
557        assert_ne!(PropName("addr:", "city"), "addr:");
558        assert_ne!(PropName("addr:", "city"), "city");
559        assert_eq!(PropName("name", ""), "name");
560    }
561
562    #[test]
563    fn prop_name_structural_eq_is_part_wise() {
564        assert_eq!(PropName("a", "b"), PropName("a", "b"));
565        assert_eq!(PropName("ab", ""), PropName("a", "b"));
566    }
567
568    #[test]
569    fn prop_name_eq_prop_name_semantic_equality() {
570        assert_eq!(PropName("ab", ""), PropName("a", "b"));
571        assert_eq!(PropName("", "ab"), PropName("a", "b"));
572        assert_eq!(PropName("abc", "def"), PropName("ab", "cdef"));
573        assert_eq!(PropName("a", "bcdef"), PropName("abcde", "f"));
574
575        assert_ne!(PropName("a", "b"), PropName("a", "c"));
576        assert_ne!(PropName("a", "b"), PropName("ab", "c"));
577        assert_ne!(PropName("abc", ""), PropName("ab", ""));
578    }
579
580    #[test]
581    fn prop_value_ref_scalars_convert_to_json() {
582        assert_eq!(Value::from(PropValueRef::Bool(true)), Value::Bool(true));
583        assert_eq!(Value::from(PropValueRef::Bool(false)), Value::Bool(false));
584        assert_eq!(Value::from(PropValueRef::I8(-1)), Value::from(-1_i8));
585        assert_eq!(Value::from(PropValueRef::U8(255)), Value::from(255_u8));
586        assert_eq!(
587            Value::from(PropValueRef::I32(-1000)),
588            Value::from(-1000_i32)
589        );
590        assert_eq!(Value::from(PropValueRef::U32(1000)), Value::from(1000_u32));
591        assert_eq!(
592            Value::from(PropValueRef::I64(i64::MIN)),
593            Value::from(i64::MIN)
594        );
595        assert_eq!(
596            Value::from(PropValueRef::U64(u64::MAX)),
597            Value::from(u64::MAX)
598        );
599        assert_eq!(
600            Value::from(PropValueRef::Str("hello")),
601            Value::String("hello".into())
602        );
603    }
604
605    #[test]
606    fn prop_value_ref_float_finite_is_number() {
607        assert!(matches!(
608            Value::from(PropValueRef::F32(1.5)),
609            Value::Number(_)
610        ));
611        assert!(matches!(
612            Value::from(PropValueRef::F64(2.5)),
613            Value::Number(_)
614        ));
615    }
616
617    #[test]
618    fn prop_value_ref_float_non_finite_becomes_string_sentinel() {
619        assert_eq!(
620            Value::from(PropValueRef::F32(f32::NAN)),
621            Value::String("f32::NAN".into())
622        );
623        assert_eq!(
624            Value::from(PropValueRef::F32(f32::INFINITY)),
625            Value::String("f32::INFINITY".into())
626        );
627        assert_eq!(
628            Value::from(PropValueRef::F64(f64::NAN)),
629            Value::String("f64::NAN".into())
630        );
631        assert_eq!(
632            Value::from(PropValueRef::F64(f64::NEG_INFINITY)),
633            Value::String("f64::NEG_INFINITY".into())
634        );
635    }
636
637    #[test]
638    fn empty_layer_yields_no_features() {
639        let buf = layer_buf(empty_layer("empty"));
640        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
641        let Layer::Tag01(lazy) = layer else {
642            panic!("expected Tag01")
643        };
644        let parsed = lazy.decode_all(&mut dec()).unwrap();
645
646        let iter = parsed.iter_features();
647        assert_eq!(iter.len(), 0);
648        assert!(iter.is_empty());
649        assert_eq!(parsed.iter_features().len(), 0);
650    }
651
652    #[test]
653    fn len_decreases_with_each_next() {
654        let buf = layer_buf(StagedLayer {
655            name: "test".into(),
656            extent: 4096,
657            id: StagedId::None,
658            geometry: three_points(),
659            properties: vec![],
660        });
661        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
662        let Layer::Tag01(lazy) = layer else { panic!() };
663        let parsed = lazy.decode_all(&mut dec()).unwrap();
664
665        let mut iter = parsed.iter_features();
666        assert_eq!(iter.len(), 3);
667        iter.next().unwrap().unwrap();
668        assert_eq!(iter.len(), 2);
669        iter.next().unwrap().unwrap();
670        assert_eq!(iter.len(), 1);
671        iter.next().unwrap().unwrap();
672        assert_eq!(iter.len(), 0);
673        assert!(iter.is_empty());
674        assert!(iter.next().is_none());
675    }
676
677    #[test]
678    fn feature_ids_are_preserved() {
679        let buf = layer_buf(StagedLayer {
680            name: "test".into(),
681            extent: 4096,
682            id: StagedId::from_optional(vec![Some(100), None, Some(200)]),
683            geometry: three_points(),
684            properties: vec![],
685        });
686        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
687        let Layer::Tag01(lazy) = layer else { panic!() };
688        let parsed = lazy.decode_all(&mut dec()).unwrap();
689
690        let mut ids = Vec::new();
691        let mut iter = parsed.iter_features();
692        while let Some(r) = iter.next() {
693            ids.push(r.unwrap().id);
694        }
695        assert_eq!(ids, [Some(100), None, Some(200)]);
696    }
697
698    #[test]
699    fn geometry_values_match_input() {
700        let buf = layer_buf(StagedLayer {
701            name: "test".into(),
702            extent: 4096,
703            id: StagedId::None,
704            geometry: three_points(),
705            properties: vec![],
706        });
707        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
708        let Layer::Tag01(lazy) = layer else { panic!() };
709        let parsed = lazy.decode_all(&mut dec()).unwrap();
710
711        let mut geoms = Vec::new();
712        let mut iter = parsed.iter_features();
713        while let Some(r) = iter.next() {
714            geoms.push(r.unwrap().geometry);
715        }
716        assert_eq!(geoms[0], Geometry::<i32>::Point(Point::new(1, 2)));
717        assert_eq!(geoms[1], Geometry::<i32>::Point(Point::new(3, 4)));
718        assert_eq!(geoms[2], Geometry::<i32>::Point(Point::new(5, 6)));
719    }
720
721    #[test]
722    fn null_scalar_values_are_skipped() {
723        let buf = layer_buf(StagedLayer {
724            name: "test".into(),
725            extent: 4096,
726            id: StagedId::None,
727            geometry: three_points(),
728            properties: vec![StagedProperty::opt_u32("n", vec![Some(1), None, Some(3)])],
729        });
730        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
731        let Layer::Tag01(lazy) = layer else { panic!() };
732        let parsed = lazy.decode_all(&mut dec()).unwrap();
733
734        let mut iter = parsed.iter_features();
735
736        {
737            let feat = iter.next().unwrap().unwrap();
738            let cols: Vec<_> = feat.iter_properties().collect();
739            assert_eq!(cols.len(), 1);
740            assert_eq!(cols[0].name, PropName("n", ""));
741            assert_eq!(cols[0].name, "n");
742            assert_eq!(cols[0].value, PropValueRef::U32(1));
743            let all: Vec<_> = feat.iter_all_properties().collect();
744            assert_eq!(all, [Some(PropValueRef::U32(1))]);
745        }
746        {
747            let feat = iter.next().unwrap().unwrap();
748            assert!(feat.iter_properties().next().is_none());
749            let all: Vec<_> = feat.iter_all_properties().collect();
750            assert_eq!(all, [None]);
751        }
752        {
753            let feat = iter.next().unwrap().unwrap();
754            assert_eq!(feat.get_property("n"), Some(PropValueRef::U32(3)));
755            let all: Vec<_> = feat.iter_all_properties().collect();
756            assert_eq!(all, [Some(PropValueRef::U32(3))]);
757        }
758
759        let names: Vec<_> = parsed.iterate_prop_names().map(|n| n.to_string()).collect();
760        assert_eq!(names, ["n"]);
761    }
762
763    #[test]
764    fn null_string_values_are_skipped() {
765        let buf = layer_buf(StagedLayer {
766            name: "test".into(),
767            extent: 4096,
768            id: StagedId::None,
769            geometry: three_points(),
770            properties: vec![StagedProperty::opt_str(
771                "label",
772                vec![Some("foo"), None, Some("bar")],
773            )],
774        });
775        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
776        let Layer::Tag01(lazy) = layer else { panic!() };
777        let parsed = lazy.decode_all(&mut dec()).unwrap();
778
779        let mut iter = parsed.iter_features();
780        {
781            let feat = iter.next().unwrap().unwrap();
782            assert_eq!(feat.get_property("label"), Some(PropValueRef::Str("foo")));
783        }
784        {
785            let feat = iter.next().unwrap().unwrap();
786            assert_eq!(feat.get_property("label"), None);
787        }
788        {
789            let feat = iter.next().unwrap().unwrap();
790            assert_eq!(feat.get_property("label"), Some(PropValueRef::Str("bar")));
791        }
792    }
793
794    #[test]
795    fn multiple_columns_independently_nullable() {
796        let buf = layer_buf(StagedLayer {
797            name: "test".into(),
798            extent: 4096,
799            id: StagedId::None,
800            geometry: three_points(),
801            properties: vec![
802                StagedProperty::opt_bool("flag", vec![Some(true), Some(false), None]),
803                StagedProperty::opt_i32("score", vec![None, Some(-5), Some(7)]),
804            ],
805        });
806        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
807        let Layer::Tag01(lazy) = layer else { panic!() };
808        let parsed = lazy.decode_all(&mut dec()).unwrap();
809
810        let mut iter = parsed.iter_features();
811
812        // feat 0: flag=true, score=null → 1 property
813        {
814            let feat = iter.next().unwrap().unwrap();
815            assert_eq!(feat.iter_properties().count(), 1);
816            assert_eq!(feat.get_property("flag"), Some(PropValueRef::Bool(true)));
817            assert_eq!(feat.get_property("score"), None);
818        }
819        // feat 1: flag=false, score=-5 → 2 properties
820        {
821            let feat = iter.next().unwrap().unwrap();
822            assert_eq!(feat.iter_properties().count(), 2);
823            assert_eq!(feat.get_property("flag"), Some(PropValueRef::Bool(false)));
824            assert_eq!(feat.get_property("score"), Some(PropValueRef::I32(-5)));
825        }
826        // feat 2: flag=null, score=7 → 1 property
827        {
828            let feat = iter.next().unwrap().unwrap();
829            assert_eq!(feat.iter_properties().count(), 1);
830            assert_eq!(feat.get_property("flag"), None);
831            assert_eq!(feat.get_property("score"), Some(PropValueRef::I32(7)));
832        }
833    }
834
835    #[test]
836    fn geometry_error_does_not_misalign_ids() {
837        use crate::decoder::GeometryType;
838
839        let buf = layer_buf(StagedLayer {
840            name: "test".into(),
841            extent: 4096,
842            id: StagedId::from_optional(vec![Some(10), Some(20), Some(30)]),
843            geometry: three_points(),
844            properties: vec![],
845        });
846        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
847        let Layer::Tag01(lazy) = layer else { panic!() };
848        let mut parsed = lazy.decode_all(&mut dec()).unwrap();
849
850        // Corrupt feature 1's geometry type: Point → LineString.
851        // A LineString requires part_offsets, which are absent here, so
852        // to_geojson(1) will return Err(NoPartOffsets).
853        parsed.geometry.vector_types[1] = GeometryType::LineString;
854
855        let mut iter = parsed.iter_features();
856
857        // Feature 0: valid Point, id = Some(10)
858        let feat0 = iter.next().unwrap().unwrap();
859        assert_eq!(feat0.id, Some(10));
860
861        // Feature 1: geometry error — iterator still advances ID cursor
862        assert!(iter.next().unwrap().is_err());
863
864        // Feature 2: valid Point, id must be Some(30), not Some(20)
865        let feat2 = iter.next().unwrap().unwrap();
866        assert_eq!(
867            feat2.id,
868            Some(30),
869            "id cursor was not advanced on geometry error"
870        );
871
872        assert!(iter.next().is_none());
873    }
874
875    #[test]
876    fn get_property_absent_column_returns_none() {
877        let buf = layer_buf(StagedLayer {
878            name: "test".into(),
879            extent: 4096,
880            id: StagedId::None,
881            geometry: three_points(),
882            properties: vec![StagedProperty::u32("x", vec![1, 2, 3])],
883        });
884        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
885        let Layer::Tag01(lazy) = layer else { panic!() };
886        let parsed = lazy.decode_all(&mut dec()).unwrap();
887
888        let mut iter = parsed.iter_features();
889        let feat = iter.next().unwrap().unwrap();
890        assert_eq!(feat.get_property("no_such_column"), None);
891    }
892
893    #[test]
894    fn shared_dict_columns_are_expanded() {
895        let shared_dict = StagedSharedDict::new(
896            "addr:",
897            [
898                (
899                    "city",
900                    vec![Some("Paris"), Some("Rome"), None],
901                    Presence::Mixed,
902                ),
903                (
904                    "zip",
905                    vec![Some("75001"), None, Some("00100")],
906                    Presence::Mixed,
907                ),
908            ],
909        )
910        .unwrap();
911
912        let buf = layer_buf(StagedLayer {
913            name: "test".into(),
914            extent: 4096,
915            id: StagedId::None,
916            geometry: three_points(),
917            properties: vec![StagedProperty::SharedDict(shared_dict)],
918        });
919        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
920        let Layer::Tag01(lazy) = layer else { panic!() };
921        let parsed = lazy.decode_all(&mut dec()).unwrap();
922
923        let mut iter = parsed.iter_features();
924
925        // feat 0: city=Paris, zip=75001
926        {
927            let feat = iter.next().unwrap().unwrap();
928            assert_eq!(
929                feat.get_property("addr:city"),
930                Some(PropValueRef::Str("Paris"))
931            );
932            assert_eq!(
933                feat.get_property("addr:zip"),
934                Some(PropValueRef::Str("75001"))
935            );
936            assert_eq!(feat.iter_properties().count(), 2);
937        }
938        // feat 1: city=Rome, zip=null
939        {
940            let feat = iter.next().unwrap().unwrap();
941            assert_eq!(
942                feat.get_property("addr:city"),
943                Some(PropValueRef::Str("Rome"))
944            );
945            assert_eq!(feat.get_property("addr:zip"), None);
946            assert_eq!(feat.iter_properties().count(), 1);
947            // iter_all_properties: values only (no names); SharedDict expands to two slots
948            let all: Vec<_> = feat.iter_all_properties().collect();
949            assert_eq!(all, [Some(PropValueRef::Str("Rome")), None]);
950        }
951        // feat 2: city=null, zip=00100
952        {
953            let feat = iter.next().unwrap().unwrap();
954            assert_eq!(feat.get_property("addr:city"), None);
955            assert_eq!(
956                feat.get_property("addr:zip"),
957                Some(PropValueRef::Str("00100"))
958            );
959        }
960
961        let names: Vec<_> = parsed.iterate_prop_names().map(|n| n.to_string()).collect();
962        assert_eq!(names, ["addr:city", "addr:zip"]);
963    }
964}