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