1use std::borrow::Cow;
2#[cfg(feature = "unstable-v2")]
3use std::collections::HashSet;
4
5use derive_debug::Dbg;
6
7#[cfg(feature = "unstable-v2")]
8use crate::decoder::RleLayout;
9use crate::decoder::{DictionaryType, FastPForKind, GeometryValues, PhysicalEncoding, StreamType};
10use crate::encoder::geometry::VertexBufferType;
11use crate::encoder::{IntEncoder, StagedId, StagedProperty};
12#[cfg(feature = "unstable-v2")]
13use crate::encoder::{StagedInterior, StagedMValue, StagedNested, StagedNode};
14#[cfg(feature = "unstable-v2")]
15use crate::tile::MAX_NESTED_DEPTH;
16use crate::tile::{ColumnRole, Extent, reject_taken_name};
17use crate::{MltError, MltResult};
18
19#[derive(Debug, Clone, Default, PartialEq)]
21pub struct EncodedUnknown {
22 pub(crate) tag: u8,
23 pub(crate) value: Vec<u8>,
24}
25
26impl EncodedUnknown {
27 pub fn new(tag: u8, value: Vec<u8>) -> MltResult<Self> {
28 if tag == 1 {
29 return Err(MltError::ParsingColumnType(tag));
30 }
31 Ok(Self { tag, value })
32 }
33
34 #[must_use]
35 pub fn tag(&self) -> u32 {
36 u32::from(self.tag)
37 }
38
39 #[must_use]
40 pub fn data(&self) -> &[u8] {
41 &self.value
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq)]
48pub struct CurveParams {
49 pub shift: u32,
50 pub bits: u32,
51}
52
53impl Default for CurveParams {
54 fn default() -> Self {
55 Self { shift: 0, bits: 1 }
56 }
57}
58
59impl CurveParams {
60 #[must_use]
62 pub fn from_vertices(vertices: &[i32]) -> Self {
63 if vertices.is_empty() {
64 return Self::default();
65 }
66 let (min, max) = vertices
67 .iter()
68 .fold((i32::MAX, i32::MIN), |(mn, mx), &v| (mn.min(v), mx.max(v)));
69 crate::codecs::hilbert::hilbert_curve_params_from_bounds(min, max)
70 }
71}
72
73#[derive(Debug, PartialEq, Clone)]
80pub struct StagedLayer {
81 pub(crate) name: String,
82 pub(crate) extent: Extent,
83 pub(crate) id: StagedId,
84 pub(crate) geometry: GeometryValues,
85 pub(crate) properties: Vec<StagedProperty>,
86 #[cfg(feature = "unstable-v2")]
88 pub(crate) m_values: Vec<StagedMValue>,
89 #[cfg(feature = "unstable-v2")]
91 pub(crate) nested: Vec<StagedNested>,
92}
93
94#[cfg_attr(not(feature = "__private"), allow(dead_code))]
95impl StagedLayer {
96 pub fn new(
97 name: impl Into<String>,
98 extent: u32,
99 id: StagedId,
100 geometry: GeometryValues,
101 properties: Vec<StagedProperty>,
102 ) -> MltResult<Self> {
103 let (name, extent, _) = validate_staged(name, extent, &id, &geometry, &properties)?;
104 Ok(Self {
105 name,
106 extent,
107 id,
108 geometry,
109 properties,
110 #[cfg(feature = "unstable-v2")]
111 m_values: Vec::new(),
112 #[cfg(feature = "unstable-v2")]
113 nested: Vec::new(),
114 })
115 }
116
117 #[cfg(feature = "unstable-v2")]
119 pub fn with_m_values(
120 name: impl Into<String>,
121 extent: u32,
122 id: StagedId,
123 geometry: GeometryValues,
124 properties: Vec<StagedProperty>,
125 m_values: Vec<StagedMValue>,
126 ) -> MltResult<Self> {
127 let (name, extent, columns) = validate_staged(name, extent, &id, &geometry, &properties)?;
128 validate_m_values(&geometry, &columns, &m_values)?;
129 Ok(Self {
130 name,
131 extent,
132 id,
133 geometry,
134 properties,
135 m_values,
136 nested: Vec::new(),
137 })
138 }
139
140 #[cfg(feature = "unstable-v2")]
142 pub fn with_nested(
143 name: impl Into<String>,
144 extent: u32,
145 id: StagedId,
146 geometry: GeometryValues,
147 properties: Vec<StagedProperty>,
148 m_values: Vec<StagedMValue>,
149 nested: Vec<StagedNested>,
150 ) -> MltResult<Self> {
151 let (name, extent, columns) = validate_staged(name, extent, &id, &geometry, &properties)?;
152 validate_m_values(&geometry, &columns, &m_values)?;
153 validate_nested(&geometry, &columns, &m_values, &nested)?;
154 Ok(Self {
155 name,
156 extent,
157 id,
158 geometry,
159 properties,
160 m_values,
161 nested,
162 })
163 }
164
165 #[must_use]
166 pub fn name(&self) -> &str {
167 &self.name
168 }
169
170 #[must_use]
171 pub fn extent(&self) -> Extent {
172 self.extent
173 }
174
175 #[must_use]
176 pub fn id(&self) -> &StagedId {
177 &self.id
178 }
179
180 #[must_use]
181 pub fn geometry(&self) -> &GeometryValues {
182 &self.geometry
183 }
184
185 #[must_use]
186 pub fn properties(&self) -> &[StagedProperty] {
187 &self.properties
188 }
189
190 #[cfg(feature = "unstable-v2")]
191 #[must_use]
192 pub fn m_values(&self) -> &[StagedMValue] {
193 &self.m_values
194 }
195
196 #[cfg(feature = "unstable-v2")]
197 #[must_use]
198 pub fn nested(&self) -> &[StagedNested] {
199 &self.nested
200 }
201}
202
203fn validate_staged<'p>(
205 name: impl Into<String>,
206 extent: u32,
207 id: &StagedId,
208 geometry: &GeometryValues,
209 properties: &'p [StagedProperty],
210) -> MltResult<(String, Extent, Vec<Cow<'p, str>>)> {
211 let name = name.into();
212 if name.is_empty() {
213 return Err(MltError::MissingLayerName);
214 }
215 let extent = Extent::new(extent)?;
216 let feature_count = geometry.feature_count();
217 if let Some(actual) = id.feature_count()
218 && actual != feature_count
219 {
220 return Err(MltError::StagedFeatureCountMismatch {
221 column: "id".into(),
222 expected: feature_count,
223 actual,
224 });
225 }
226 let mut columns: Vec<Cow<'p, str>> = Vec::with_capacity(properties.len());
229 for property in properties {
230 let actual = property.feature_count();
231 if actual != feature_count {
232 return Err(MltError::StagedFeatureCountMismatch {
233 column: property.name().to_string(),
234 expected: feature_count,
235 actual,
236 });
237 }
238 if let StagedProperty::SharedDict(sd) = property {
239 for item in &sd.items {
240 let name = format!("{}{}", sd.prefix, item.suffix);
241 reject_taken_name(&name, ColumnRole::Property, property_columns(&columns))?;
242 columns.push(Cow::Owned(name));
243 }
244 } else {
245 reject_taken_name(
246 property.name(),
247 ColumnRole::Property,
248 property_columns(&columns),
249 )?;
250 columns.push(Cow::Borrowed(property.name()));
251 }
252 }
253 Ok((name, extent, columns))
254}
255
256fn property_columns<'a>(
258 columns: &'a [Cow<'a, str>],
259) -> impl Iterator<Item = (&'a str, ColumnRole)> + 'a {
260 columns
261 .iter()
262 .map(|name| (name.as_ref(), ColumnRole::Property))
263}
264
265#[cfg(feature = "unstable-v2")]
268fn validate_m_values(
269 geometry: &GeometryValues,
270 columns: &[Cow<'_, str>],
271 m_values: &[StagedMValue],
272) -> MltResult<()> {
273 let feature_count = geometry.feature_count();
274 let mut seen: Vec<&str> = Vec::with_capacity(m_values.len());
275 for column in m_values {
276 reject_taken_name(
277 column.name(),
278 ColumnRole::MValue,
279 property_columns(columns).chain(seen.iter().map(|n| (*n, ColumnRole::MValue))),
280 )?;
281 seen.push(column.name());
282 if let Some(actual) = column.feature_count()
283 && actual != feature_count
284 {
285 return Err(MltError::StagedFeatureCountMismatch {
286 column: column.name().to_string(),
287 expected: feature_count,
288 actual,
289 });
290 }
291 let mut expected = 0;
292 for index in 0..feature_count {
293 let present = column
294 .presence
295 .as_ref()
296 .is_none_or(|mask| mask.get(index).copied().unwrap_or(false));
297 if present {
298 expected += geometry.vertex_count(index)?;
299 }
300 }
301 if expected != column.values().count() {
302 return Err(MltError::MValueColumnLengthMismatch {
303 name: column.name().to_string(),
304 expected,
305 actual: column.values().count(),
306 });
307 }
308 }
309 Ok(())
310}
311
312#[cfg(feature = "unstable-v2")]
315fn validate_nested(
316 geometry: &GeometryValues,
317 columns: &[Cow<'_, str>],
318 m_values: &[StagedMValue],
319 nested: &[StagedNested],
320) -> MltResult<()> {
321 let feature_count = geometry.feature_count();
322 let mut seen: Vec<&str> = Vec::with_capacity(nested.len());
323 for column in nested {
324 reject_taken_name(
325 column.name(),
326 ColumnRole::Nested,
327 property_columns(columns)
328 .chain(m_values.iter().map(|c| (c.name(), ColumnRole::MValue)))
329 .chain(seen.iter().map(|n| (*n, ColumnRole::Nested))),
330 )?;
331 seen.push(column.name());
332 if column.root.parent_count() != feature_count {
333 return Err(MltError::StagedFeatureCountMismatch {
334 column: column.name().to_string(),
335 expected: feature_count,
336 actual: column.root.parent_count(),
337 });
338 }
339 validate_nested_interior(&column.root, 1)?;
340 }
341 Ok(())
342}
343
344#[cfg(feature = "unstable-v2")]
346fn validate_nested_node(node: &StagedNode, depth: usize) -> MltResult<()> {
347 if depth > MAX_NESTED_DEPTH {
348 return Err(MltError::NestedTooDeep(depth));
349 }
350 match node {
351 StagedNode::Leaf(_) => Ok(()),
352 StagedNode::Interior(interior) => validate_nested_interior(interior, depth),
353 }
354}
355
356#[cfg(feature = "unstable-v2")]
358fn validate_nested_interior(interior: &StagedInterior, depth: usize) -> MltResult<()> {
359 match interior {
360 StagedInterior::Struct(node) => {
361 let mut seen: HashSet<&str> = HashSet::with_capacity(node.fields.len());
362 for (name, field) in &node.fields {
363 if !seen.insert(name.as_str()) {
364 return Err(MltError::DuplicateFieldName(name.clone()));
365 }
366 validate_nested_node(field, depth + 1)?;
367 }
368 Ok(())
369 }
370 StagedInterior::List(node) => validate_nested_node(&node.element, depth + 1),
371 StagedInterior::Map(node) => validate_nested_node(&node.value, depth + 1),
372 }
373}
374
375#[cfg(feature = "unstable-v2")]
377#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
378#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
379pub enum WireVersion {
380 #[default]
382 V01,
383 V02,
387}
388
389#[cfg(feature = "unstable-v2")]
390impl WireVersion {
391 #[must_use]
393 pub(crate) fn tag(self) -> u8 {
394 match self {
395 Self::V01 => 1,
396 Self::V02 => 2,
397 }
398 }
399
400 #[must_use]
402 pub(crate) fn fastpfor_kind(self) -> FastPForKind {
403 match self {
404 Self::V01 => FastPForKind::Block256Be,
405 Self::V02 => FastPForKind::Block128Le,
406 }
407 }
408
409 #[must_use]
411 pub(crate) fn rle_layout(self) -> RleLayout {
412 match self {
413 Self::V01 => RleLayout::Split,
414 Self::V02 => RleLayout::Interleaved,
415 }
416 }
417
418 #[must_use]
421 pub(crate) fn raw_header_saving(self) -> usize {
422 match self {
423 Self::V01 => 0,
424 Self::V02 => 1,
425 }
426 }
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Hash)]
431#[expect(
432 clippy::struct_excessive_bools,
433 reason = "enums would not model this better, not a state machine"
434)]
435pub struct EncoderConfig {
436 #[cfg(feature = "unstable-v2")]
438 wire_version: WireVersion,
439 tessellate: bool,
441 attempt_spatial_morton_sort: bool,
443 attempt_spatial_hilbert_sort: bool,
445 attempt_id_sort: bool,
447 allow_fsst: bool,
449 allow_fastpfor: bool,
451 allow_shared_dict: bool,
453 #[cfg(feature = "unstable-v2")]
455 allow_float_dict: bool,
456 #[cfg(feature = "unstable-v2")]
458 allow_float_alp: bool,
459 #[cfg(feature = "unstable-v2")]
461 allow_packed_dict_codes: bool,
462 #[cfg(feature = "unstable-v2")]
464 allow_row_shapes: bool,
465 #[cfg(feature = "unstable-v2")]
467 allow_triangles_only: bool,
468 #[cfg(feature = "unstable-v2")]
470 allow_delta2: bool,
471}
472impl Default for EncoderConfig {
473 fn default() -> Self {
474 Self {
475 #[cfg(feature = "unstable-v2")]
476 wire_version: WireVersion::V01,
477 tessellate: false,
478 attempt_spatial_morton_sort: true,
479 attempt_spatial_hilbert_sort: true,
480 attempt_id_sort: true,
481 allow_fsst: true,
482 allow_fastpfor: true,
483 allow_shared_dict: true,
484 #[cfg(feature = "unstable-v2")]
486 allow_float_dict: false,
487 #[cfg(feature = "unstable-v2")]
488 allow_float_alp: false,
489 #[cfg(feature = "unstable-v2")]
490 allow_row_shapes: false,
491 #[cfg(feature = "unstable-v2")]
492 allow_packed_dict_codes: false,
493 #[cfg(feature = "unstable-v2")]
494 allow_triangles_only: false,
495 #[cfg(feature = "unstable-v2")]
496 allow_delta2: false,
497 }
498 }
499}
500
501impl EncoderConfig {
502 #[cfg(feature = "unstable-v2")]
503 #[must_use]
504 pub fn wire_version(self) -> WireVersion {
505 self.wire_version
506 }
507
508 #[must_use]
509 pub fn tessellate(self) -> bool {
510 self.tessellate
511 }
512
513 #[must_use]
514 pub fn attempt_spatial_morton_sort(self) -> bool {
515 self.attempt_spatial_morton_sort
516 }
517
518 #[must_use]
519 pub fn attempt_spatial_hilbert_sort(self) -> bool {
520 self.attempt_spatial_hilbert_sort
521 }
522
523 #[must_use]
524 pub fn attempt_id_sort(self) -> bool {
525 self.attempt_id_sort
526 }
527
528 #[must_use]
529 pub fn allow_fsst(self) -> bool {
530 self.allow_fsst
531 }
532
533 #[must_use]
534 pub fn allow_fastpfor(self) -> bool {
535 self.allow_fastpfor
536 }
537
538 #[must_use]
540 pub(crate) fn fastpfor(self) -> Option<PhysicalEncoding> {
541 #[cfg(feature = "unstable-v2")]
542 let kind = self.wire_version.fastpfor_kind();
543 #[cfg(not(feature = "unstable-v2"))]
544 let kind = FastPForKind::Block256Be;
545 self.allow_fastpfor
546 .then_some(PhysicalEncoding::FastPFor(kind))
547 }
548
549 #[must_use]
550 pub fn allow_shared_dict(self) -> bool {
551 self.allow_shared_dict
552 }
553
554 #[cfg(feature = "unstable-v2")]
556 #[must_use]
557 pub fn allow_row_shapes(self) -> bool {
558 self.allow_row_shapes
559 }
560
561 #[cfg(feature = "unstable-v2")]
563 #[must_use]
564 pub fn with_row_shapes(mut self, enabled: bool) -> Self {
565 self.allow_row_shapes = enabled;
566 self
567 }
568
569 #[must_use]
571 pub fn allow_8bit_ints(self) -> bool {
572 #[cfg(feature = "unstable-v2")]
573 return self.wire_version != WireVersion::V01;
574 #[cfg(not(feature = "unstable-v2"))]
575 return false;
576 }
577
578 #[cfg(feature = "unstable-v2")]
580 #[must_use]
581 pub fn allow_float_dict(self) -> bool {
582 self.allow_float_dict && self.wire_version != WireVersion::V01
583 }
584
585 #[cfg(feature = "unstable-v2")]
587 #[must_use]
588 pub fn allow_float_alp(self) -> bool {
589 self.allow_float_alp && self.wire_version != WireVersion::V01
590 }
591
592 #[cfg(feature = "unstable-v2")]
593 #[must_use]
594 pub fn with_wire_version(mut self, version: WireVersion) -> Self {
595 self.wire_version = version;
596 self
597 }
598
599 #[must_use]
600 pub fn with_tessellation(mut self, enabled: bool) -> Self {
601 self.tessellate = enabled;
602 self
603 }
604
605 #[cfg(feature = "unstable-v2")]
607 #[must_use]
608 pub fn allow_triangles_only(self) -> bool {
609 self.allow_triangles_only && self.wire_version != WireVersion::V01
610 }
611
612 #[cfg(feature = "unstable-v2")]
615 #[must_use]
616 pub fn with_triangles_only(mut self, enabled: bool) -> Self {
617 self.allow_triangles_only = enabled;
618 self
619 }
620
621 #[cfg(feature = "unstable-v2")]
623 #[must_use]
624 pub fn allow_delta2(self) -> bool {
625 self.allow_delta2 && self.wire_version != WireVersion::V01
626 }
627
628 #[cfg(feature = "unstable-v2")]
630 #[must_use]
631 pub fn with_delta2(mut self, enabled: bool) -> Self {
632 self.allow_delta2 = enabled;
633 self
634 }
635
636 #[must_use]
637 pub fn with_spatial_morton_sort(mut self, enabled: bool) -> Self {
638 self.attempt_spatial_morton_sort = enabled;
639 self
640 }
641
642 #[must_use]
643 pub fn with_spatial_hilbert_sort(mut self, enabled: bool) -> Self {
644 self.attempt_spatial_hilbert_sort = enabled;
645 self
646 }
647
648 #[must_use]
649 pub fn with_id_sort(mut self, enabled: bool) -> Self {
650 self.attempt_id_sort = enabled;
651 self
652 }
653
654 #[must_use]
655 pub fn with_fsst(mut self, enabled: bool) -> Self {
656 self.allow_fsst = enabled;
657 self
658 }
659
660 #[must_use]
661 pub fn with_fastpfor(mut self, enabled: bool) -> Self {
662 self.allow_fastpfor = enabled;
663 self
664 }
665
666 #[must_use]
667 pub fn with_shared_dict(mut self, enabled: bool) -> Self {
668 self.allow_shared_dict = enabled;
669 self
670 }
671
672 #[cfg(feature = "unstable-v2")]
676 #[must_use]
677 pub fn with_float_dict(mut self, enabled: bool) -> Self {
678 self.allow_float_dict = enabled;
679 self
680 }
681
682 #[cfg(feature = "unstable-v2")]
684 #[must_use]
685 pub fn with_float_alp(mut self, enabled: bool) -> Self {
686 self.allow_float_alp = enabled;
687 self
688 }
689
690 #[cfg(feature = "unstable-v2")]
692 #[must_use]
693 pub fn allow_packed_dict_codes(self) -> bool {
694 self.allow_packed_dict_codes && self.wire_version != WireVersion::V01
695 }
696
697 #[cfg(feature = "unstable-v2")]
700 #[must_use]
701 pub fn with_packed_dict_codes(mut self, enabled: bool) -> Self {
702 self.allow_packed_dict_codes = enabled;
703 self
704 }
705}
706
707#[derive(Debug, Clone, Copy, PartialEq, Eq)]
709pub enum StrEncoding {
710 Plain,
711 Dict,
712 Fsst,
713 FsstDict,
714 FrontDict,
715 FsstFrontDict,
716}
717
718#[derive(Debug, Clone, Copy, PartialEq, Eq)]
720pub enum FloatEncoding {
721 None,
722 #[cfg(feature = "unstable-v2")]
723 Dict,
724 #[cfg(feature = "unstable-v2")]
725 Alp,
726}
727
728#[derive(Debug, Clone, Copy, PartialEq)]
729pub enum ColumnKind {
730 Id,
731 Geometry,
732 Property,
733}
734
735#[cfg(feature = "unstable-v2")]
740#[derive(Clone, Copy, Debug)]
741pub struct StrAt<'a> {
742 pub name: &'a str,
743 pub subname: &'a str,
744}
745
746#[cfg(feature = "unstable-v2")]
747impl<'a> StrAt<'a> {
748 #[must_use]
749 pub const fn flat(name: &'a str) -> Self {
750 Self { name, subname: "" }
751 }
752
753 #[must_use]
754 pub const fn nested(name: &'a str, subname: &'a str) -> Self {
755 Self { name, subname }
756 }
757
758 pub(crate) fn ctx(self, stream_type: StreamType) -> StreamCtx<'a> {
759 StreamCtx::prop2(stream_type, self.name, self.subname)
760 }
761
762 pub(crate) fn qualified(self) -> Cow<'a, str> {
764 StreamCtx::prop2(StreamType::Present, self.name, self.subname).qualified()
765 }
766}
767
768#[derive(Clone, Copy, Debug, PartialEq)]
770pub struct StreamCtx<'a> {
771 pub kind: ColumnKind,
772 pub stream_type: StreamType,
773 pub name: &'a str,
774 pub subname: &'a str,
775}
776impl<'a> StreamCtx<'a> {
777 #[inline]
779 #[must_use]
780 pub const fn new(
781 kind: ColumnKind,
782 stream_type: StreamType,
783 name: &'a str,
784 subname: &'a str,
785 ) -> Self {
786 Self {
787 kind,
788 stream_type,
789 name,
790 subname,
791 }
792 }
793
794 #[inline]
795 #[must_use]
796 pub const fn id(stream_type: StreamType) -> Self {
797 Self::new(ColumnKind::Id, stream_type, "", "")
798 }
799
800 #[inline]
801 #[must_use]
802 pub const fn geom(stream_type: StreamType, name: &'a str) -> Self {
803 Self::new(ColumnKind::Geometry, stream_type, name, "")
804 }
805
806 #[inline]
807 #[must_use]
808 pub const fn prop(stream_type: StreamType, name: &'a str) -> Self {
809 Self::new(ColumnKind::Property, stream_type, name, "")
810 }
811
812 #[inline]
813 #[must_use]
814 pub const fn prop_data(name: &'a str) -> Self {
815 let stream_type = StreamType::Data(DictionaryType::None);
816 Self::new(ColumnKind::Property, stream_type, name, "")
817 }
818
819 #[inline]
820 #[must_use]
821 pub const fn prop2(stream_type: StreamType, prefix: &'a str, suffix: &'a str) -> Self {
822 Self::new(ColumnKind::Property, stream_type, prefix, suffix)
823 }
824
825 #[must_use]
827 pub fn qualified(&self) -> Cow<'a, str> {
828 if self.subname.is_empty() {
829 Cow::Borrowed(self.name)
830 } else {
831 Cow::Owned(format!("{}{}", self.name, self.subname))
832 }
833 }
834}
835
836#[derive(Dbg)]
840pub struct ExplicitEncoder {
841 pub vertex_buffer_type: VertexBufferType,
843 #[dbg(skip)]
845 pub force_stream: Box<dyn for<'a> Fn(&'a StreamCtx<'a>) -> bool>,
846 #[dbg(skip)]
848 pub get_int_encoder: Box<dyn for<'a> Fn(&'a StreamCtx<'a>) -> IntEncoder>,
849 #[dbg(skip)]
851 pub get_str_encoding: Box<dyn Fn(&str) -> StrEncoding>,
852 #[dbg(skip)]
854 pub get_float_encoding: Box<dyn Fn(&str) -> FloatEncoding>,
855}
856
857#[cfg(all(test, feature = "unstable-v2"))]
858mod tests {
859 use insta::assert_snapshot;
860
861 use super::*;
862 use crate::encoder::{StagedLeaf, StagedList, StagedStruct, StagedValues};
863
864 fn leaf() -> StagedNode {
865 StagedNode::Leaf(StagedLeaf::new(None, StagedValues::I32(vec![1])))
866 }
867
868 fn lists(depth: usize) -> StagedInterior {
870 let mut node = leaf();
871 for _ in 2..depth {
872 node = StagedNode::Interior(StagedInterior::List(StagedList::new(None, vec![1], node)));
873 }
874 StagedInterior::List(StagedList::new(None, vec![1], node))
875 }
876
877 #[test]
878 fn a_struct_repeating_a_field_name() {
879 let root =
880 StagedInterior::Struct(StagedStruct::new(None, vec![("a", leaf()), ("a", leaf())]));
881 assert_snapshot!(
882 validate_nested_interior(&root, 1).unwrap_err(),
883 @"duplicate field name in a struct node: a"
884 );
885 }
886
887 #[test]
888 fn a_tree_one_level_past_the_limit() {
889 assert_snapshot!(
890 validate_nested_interior(&lists(MAX_NESTED_DEPTH + 1), 1).unwrap_err(),
891 @"a nested column is 9 levels deep, more than the 8 the format allows"
892 );
893 }
894
895 #[test]
896 fn a_tree_at_the_limit() {
897 validate_nested_interior(&lists(MAX_NESTED_DEPTH), 1).expect("a tree at the limit");
898 }
899}