1pub mod emit;
19pub mod traverse;
20
21use alloc::vec::Vec;
22
23use crate::document::node::NodeValue;
24use crate::document::{EureDocument, NodeId};
25use crate::map::Map;
26use crate::path::PathSegment;
27use crate::value::ValueKind;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub enum Form {
36 Inline,
38 BindingBlock,
40 BindingValueBlock,
42 Section,
44 SectionBlock,
46 SectionValueBlock,
48 Flatten,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub enum ArrayForm {
55 Inline,
57 PerElement(Form),
60 PerElementIndexed(Form),
62}
63
64#[derive(Debug, Clone)]
66pub struct LayoutPlan {
67 doc: EureDocument,
68 forms: Map<NodeId, Form>,
69 array_forms: Map<NodeId, ArrayForm>,
70 order: Map<NodeId, Vec<NodeId>>,
71}
72
73#[derive(Debug, Clone)]
75pub struct PlanBuilder {
76 doc: EureDocument,
77 forms: Map<NodeId, Form>,
78 array_forms: Map<NodeId, ArrayForm>,
79 order: Map<NodeId, Vec<NodeId>>,
80}
81
82#[derive(Debug, Clone, PartialEq)]
84pub enum ArrayFormReason {
85 ElementIncompatibleForm { element: NodeId, kind: ValueKind },
87 FlattenElementDisallowed,
91}
92
93#[derive(Debug, Clone, PartialEq, thiserror::Error)]
95pub enum PlanError {
96 #[error("path {path:?} does not resolve in document")]
97 PathNotFound { path: Vec<PathSegment> },
98
99 #[error("form {form:?} is incompatible with node {node:?} of kind {kind}")]
100 IncompatibleForm {
101 node: NodeId,
102 form: Form,
103 kind: ValueKind,
104 },
105
106 #[error("array form {form:?} is incompatible with array node {node:?}: {reason:?}")]
107 IncompatibleArrayForm {
108 node: NodeId,
109 form: ArrayForm,
110 reason: ArrayFormReason,
111 },
112
113 #[error("node {0:?} has no assigned form")]
114 MissingForm(NodeId),
115
116 #[error("node {node:?} would be emitted multiple times")]
117 DuplicateEmission { node: NodeId },
118
119 #[error("section form at node {0:?} is not allowed in this context")]
120 SectionInForbiddenContext(NodeId),
121
122 #[error("schema produced conflicting forms for node {node:?}")]
123 ConflictingOverride { node: NodeId },
124
125 #[error("ordered child {child:?} is not a direct child of parent {parent:?}")]
126 OrderChildNotDirect { parent: NodeId, child: NodeId },
127
128 #[error("ordered children for parent {parent:?} contain duplicate {child:?}")]
129 OrderDuplicateChild { parent: NodeId, child: NodeId },
130
131 #[error(
132 "ordered child {child:?} of parent {parent:?} is an extension; extension order is fixed"
133 )]
134 OrderExtensionChild { parent: NodeId, child: NodeId },
135
136 #[error("set_form called on array node {0:?}; use set_array_form")]
137 FormOnArrayNode(NodeId),
138
139 #[error("set_array_form called on non-array node {0:?}; use set_form")]
140 ArrayFormOnNonArray(NodeId),
141
142 #[error("set_form called on root node {0:?}; root form is implicit")]
143 FormOnRoot(NodeId),
144
145 #[error("order requested on array node {0:?}; element order is data")]
146 OrderOnArrayNode(NodeId),
147}
148
149pub(crate) fn check_form_compat(id: NodeId, form: Form, kind: ValueKind) -> Result<(), PlanError> {
156 let compatible = match form {
157 Form::Inline => !matches!(kind, ValueKind::PartialMap),
158 Form::BindingBlock | Form::Section | Form::SectionBlock | Form::Flatten => {
159 matches!(kind, ValueKind::Map | ValueKind::PartialMap)
160 }
161 Form::BindingValueBlock | Form::SectionValueBlock => {
162 matches!(
166 kind,
167 ValueKind::Hole
168 | ValueKind::Null
169 | ValueKind::Bool
170 | ValueKind::Integer
171 | ValueKind::F32
172 | ValueKind::F64
173 | ValueKind::Text
174 | ValueKind::Tuple
175 )
176 }
177 };
178 if compatible {
179 Ok(())
180 } else {
181 Err(PlanError::IncompatibleForm {
182 node: id,
183 form,
184 kind,
185 })
186 }
187}
188
189pub(crate) fn check_array_form_compat(
191 doc: &EureDocument,
192 id: NodeId,
193 form: ArrayForm,
194) -> Result<(), PlanError> {
195 match form {
196 ArrayForm::Inline => Ok(()),
197 ArrayForm::PerElement(Form::Flatten) | ArrayForm::PerElementIndexed(Form::Flatten) => {
198 Err(PlanError::IncompatibleArrayForm {
199 node: id,
200 form,
201 reason: ArrayFormReason::FlattenElementDisallowed,
202 })
203 }
204 ArrayForm::PerElement(element) | ArrayForm::PerElementIndexed(element) => {
205 let element_ids = match &doc.node(id).content {
206 NodeValue::Array(arr) => arr.iter().copied().collect::<Vec<_>>(),
207 _ => return Err(PlanError::ArrayFormOnNonArray(id)),
208 };
209 for element_id in element_ids {
210 let kind = doc.node(element_id).content.value_kind();
211 if check_form_compat(element_id, element, kind).is_err() {
212 return Err(PlanError::IncompatibleArrayForm {
213 node: id,
214 form,
215 reason: ArrayFormReason::ElementIncompatibleForm {
216 element: element_id,
217 kind,
218 },
219 });
220 }
221 }
222 Ok(())
223 }
224 }
225}
226
227impl PlanBuilder {
232 pub fn new(doc: EureDocument) -> Self {
234 Self {
235 doc,
236 forms: Map::default(),
237 array_forms: Map::default(),
238 order: Map::default(),
239 }
240 }
241
242 pub fn document(&self) -> &EureDocument {
244 &self.doc
245 }
246
247 pub fn node_at(&self, path: &[PathSegment]) -> Result<NodeId, PlanError> {
249 let mut current = self.doc.get_root_id();
250 for segment in path {
251 match traverse::child_node_id(&self.doc, current, segment) {
252 Some(id) => current = id,
253 None => {
254 return Err(PlanError::PathNotFound {
255 path: path.to_vec(),
256 });
257 }
258 }
259 }
260 Ok(current)
261 }
262
263 pub fn set_form(&mut self, id: NodeId, form: Form) -> Result<&mut Self, PlanError> {
265 if id == self.doc.get_root_id() {
266 return Err(PlanError::FormOnRoot(id));
267 }
268 let kind = self.doc.node(id).content.value_kind();
269 if matches!(kind, ValueKind::Array) {
270 return Err(PlanError::FormOnArrayNode(id));
271 }
272 check_form_compat(id, form, kind)?;
273 self.forms.insert(id, form);
274 Ok(self)
275 }
276
277 pub fn set_form_at(
279 &mut self,
280 path: &[PathSegment],
281 form: Form,
282 ) -> Result<&mut Self, PlanError> {
283 let id = self.node_at(path)?;
284 self.set_form(id, form)
285 }
286
287 pub fn set_array_form(&mut self, id: NodeId, form: ArrayForm) -> Result<&mut Self, PlanError> {
289 let kind = self.doc.node(id).content.value_kind();
290 if !matches!(kind, ValueKind::Array) {
291 return Err(PlanError::ArrayFormOnNonArray(id));
292 }
293 check_array_form_compat(&self.doc, id, form)?;
294 self.array_forms.insert(id, form);
295 Ok(self)
296 }
297
298 pub fn set_array_form_at(
300 &mut self,
301 path: &[PathSegment],
302 form: ArrayForm,
303 ) -> Result<&mut Self, PlanError> {
304 let id = self.node_at(path)?;
305 self.set_array_form(id, form)
306 }
307
308 pub fn order(&mut self, parent: NodeId, children: Vec<NodeId>) -> Result<&mut Self, PlanError> {
311 if matches!(self.doc.node(parent).content.value_kind(), ValueKind::Array) {
312 return Err(PlanError::OrderOnArrayNode(parent));
313 }
314 let direct = traverse::children_of(&self.doc, parent);
315
316 let mut seen = Vec::with_capacity(children.len());
317 for child in &children {
318 let Some((segment, _)) = direct.iter().find(|(_, id)| id == child) else {
319 return Err(PlanError::OrderChildNotDirect {
320 parent,
321 child: *child,
322 });
323 };
324 if matches!(segment, PathSegment::Extension(_)) {
325 return Err(PlanError::OrderExtensionChild {
326 parent,
327 child: *child,
328 });
329 }
330 if seen.contains(child) {
331 return Err(PlanError::OrderDuplicateChild {
332 parent,
333 child: *child,
334 });
335 }
336 seen.push(*child);
337 }
338 self.order.insert(parent, children);
339 Ok(self)
340 }
341
342 pub fn order_at(
345 &mut self,
346 parent_path: &[PathSegment],
347 segs: Vec<PathSegment>,
348 ) -> Result<&mut Self, PlanError> {
349 let parent = self.node_at(parent_path)?;
350 let direct = traverse::children_of(&self.doc, parent);
351 let mut children = Vec::with_capacity(segs.len());
352 for seg in &segs {
353 let id = direct
354 .iter()
355 .find(|(s, _)| s == seg)
356 .map(|(_, id)| *id)
357 .ok_or_else(|| PlanError::PathNotFound {
358 path: {
359 let mut p = parent_path.to_vec();
360 p.push(seg.clone());
361 p
362 },
363 })?;
364 children.push(id);
365 }
366 self.order(parent, children)
367 }
368
369 pub fn build(mut self) -> Result<LayoutPlan, PlanError> {
371 let root = self.doc.get_root_id();
377 self.fill_defaults(root, true);
378
379 let all = traverse::all_reachable_ids(&self.doc);
382 for id in &all {
383 let kind = self.doc.node(*id).content.value_kind();
384 if matches!(kind, ValueKind::Array) {
385 let form = *self
386 .array_forms
387 .get(id)
388 .ok_or(PlanError::MissingForm(*id))?;
389 check_array_form_compat(&self.doc, *id, form)?;
390 } else if *id != self.doc.get_root_id() {
391 let form = *self.forms.get(id).ok_or(PlanError::MissingForm(*id))?;
392 check_form_compat(*id, form, kind)?;
393 }
394 }
395
396 let plan = LayoutPlan {
399 doc: self.doc,
400 forms: self.forms,
401 array_forms: self.array_forms,
402 order: self.order,
403 };
404 plan.validate_structure()?;
405 Ok(plan)
406 }
407
408 fn fill_defaults(&mut self, id: NodeId, allow_sections: bool) {
409 let kind = self.doc.node(id).content.value_kind();
410 let root = self.doc.get_root_id();
411
412 if matches!(kind, ValueKind::Array) {
413 let array_form = match self.array_forms.get(&id).copied() {
414 Some(f) => f,
415 None => {
416 let auto = auto::auto_array_form(&self.doc, id, allow_sections);
417 self.array_forms.insert(id, auto);
418 auto
419 }
420 };
421 let elem_form: Option<Form> = match array_form {
422 ArrayForm::Inline => None,
423 ArrayForm::PerElement(f) | ArrayForm::PerElementIndexed(f) => Some(f),
424 };
425 for (_, elem_id) in traverse::children_of(&self.doc, id) {
426 self.fill_element_defaults(elem_id, allow_sections, elem_form);
427 }
428 return;
429 }
430
431 let child_allow = if id == root {
432 true
433 } else {
434 let form = match self.forms.get(&id).copied() {
435 Some(f) => f,
436 None => {
437 let auto = auto::auto_form(&self.doc, id, allow_sections);
438 self.forms.insert(id, auto);
439 auto
440 }
441 };
442 child_allow_for_form(form, allow_sections)
443 };
444
445 for (_, child_id) in traverse::children_of(&self.doc, id) {
446 self.fill_defaults(child_id, child_allow);
447 }
448 }
449
450 fn fill_element_defaults(&mut self, id: NodeId, outer_allow: bool, elem_form: Option<Form>) {
455 let kind = self.doc.node(id).content.value_kind();
456
457 if matches!(kind, ValueKind::Array) {
458 self.fill_defaults(id, outer_allow);
459 return;
460 }
461
462 if !self.forms.contains_key(&id) {
468 let chosen = match elem_form {
469 Some(f) if check_form_compat(id, f, kind).is_ok() => f,
470 _ => auto::auto_form(&self.doc, id, outer_allow),
471 };
472 self.forms.insert(id, chosen);
473 }
474
475 let child_allow = match elem_form {
478 Some(f) => child_allow_for_form(f, outer_allow),
479 None => outer_allow,
480 };
481 for (_, child_id) in traverse::children_of(&self.doc, id) {
482 self.fill_defaults(child_id, child_allow);
483 }
484 }
485}
486
487fn child_allow_for_form(form: Form, outer_allow: bool) -> bool {
492 match form {
493 Form::Inline => outer_allow,
494 Form::BindingBlock | Form::BindingValueBlock | Form::SectionBlock => true,
495 Form::Section | Form::SectionValueBlock => false,
496 Form::Flatten => outer_allow,
497 }
498}
499
500pub(crate) mod auto {
505 use super::{ArrayForm, Form};
506 use crate::document::node::NodeValue;
507 use crate::document::{EureDocument, NodeId};
508 use crate::value::ValueKind;
509
510 pub(crate) fn auto_form(doc: &EureDocument, id: NodeId, allow_sections: bool) -> Form {
515 let node = doc.node(id);
516 let kind = node.content.value_kind();
517 match kind {
518 ValueKind::Hole
519 | ValueKind::Null
520 | ValueKind::Bool
521 | ValueKind::Integer
522 | ValueKind::F32
523 | ValueKind::F64
524 | ValueKind::Text
525 | ValueKind::Tuple => Form::Inline,
526 ValueKind::Array => unreachable!("arrays use auto_array_form"),
527 ValueKind::Map | ValueKind::PartialMap => {
528 if !node.extensions.is_empty() || map_has_complex_child(doc, &node.content) {
529 if allow_sections {
530 Form::Section
531 } else {
532 Form::BindingBlock
533 }
534 } else if map_only_scalar_children(doc, &node.content) {
535 Form::Inline
536 } else {
537 Form::BindingBlock
538 }
539 }
540 }
541 }
542
543 pub(crate) fn auto_array_form(
546 doc: &EureDocument,
547 id: NodeId,
548 allow_sections: bool,
549 ) -> ArrayForm {
550 let NodeValue::Array(arr) = &doc.node(id).content else {
551 return ArrayForm::Inline;
552 };
553 if arr.is_empty() {
554 return ArrayForm::Inline;
555 }
556 let all_maps = arr.iter().all(|&el| {
557 matches!(
558 doc.node(el).content.value_kind(),
559 ValueKind::Map | ValueKind::PartialMap
560 )
561 });
562 if all_maps {
563 let elem_form = if allow_sections {
564 Form::Section
565 } else {
566 Form::BindingBlock
567 };
568 ArrayForm::PerElement(elem_form)
569 } else {
570 ArrayForm::Inline
571 }
572 }
573
574 fn map_only_scalar_children(doc: &EureDocument, content: &NodeValue) -> bool {
575 let ids: alloc::vec::Vec<NodeId> = match content {
576 NodeValue::Map(map) => map.iter().map(|(_, &id)| id).collect(),
577 NodeValue::PartialMap(pm) => pm.iter().map(|(_, &id)| id).collect(),
578 _ => return true,
579 };
580 ids.iter().all(|&child| {
581 let child_node = doc.node(child);
582 child_node.extensions.is_empty()
583 && matches!(
584 child_node.content.value_kind(),
585 ValueKind::Null
586 | ValueKind::Bool
587 | ValueKind::Integer
588 | ValueKind::F32
589 | ValueKind::F64
590 | ValueKind::Text
591 | ValueKind::Hole
592 | ValueKind::Tuple
593 )
594 })
595 }
596
597 fn map_has_complex_child(doc: &EureDocument, content: &NodeValue) -> bool {
598 let ids: alloc::vec::Vec<NodeId> = match content {
599 NodeValue::Map(map) => map.iter().map(|(_, &id)| id).collect(),
600 NodeValue::PartialMap(pm) => pm.iter().map(|(_, &id)| id).collect(),
601 _ => return false,
602 };
603 ids.iter().any(|&child| {
604 let child_node = doc.node(child);
605 !child_node.extensions.is_empty()
606 || matches!(
607 child_node.content.value_kind(),
608 ValueKind::Map | ValueKind::PartialMap | ValueKind::Array
609 )
610 })
611 }
612}
613
614impl LayoutPlan {
619 pub fn builder(doc: EureDocument) -> PlanBuilder {
621 PlanBuilder::new(doc)
622 }
623
624 pub fn auto(doc: EureDocument) -> Result<Self, PlanError> {
626 Self::builder(doc).build()
627 }
628
629 pub fn sectioned(doc: EureDocument) -> Result<Self, PlanError> {
631 let mut b = Self::builder(doc);
632 let all = traverse::all_reachable_ids(b.document());
633 let root = b.document().get_root_id();
634 for id in all {
635 if id == root {
636 continue;
637 }
638 let kind = b.document().node(id).content.value_kind();
639 match kind {
640 ValueKind::Map | ValueKind::PartialMap => {
641 b.set_form(id, Form::Section)?;
642 }
643 ValueKind::Array => {
644 let form = match auto::auto_array_form(b.document(), id, true) {
645 ArrayForm::PerElement(_) | ArrayForm::PerElementIndexed(_) => {
646 ArrayForm::PerElement(Form::Section)
647 }
648 ArrayForm::Inline => ArrayForm::Inline,
649 };
650 b.set_array_form(id, form)?;
651 }
652 _ => {
653 b.set_form(id, Form::Inline)?;
654 }
655 }
656 }
657 b.build()
658 }
659
660 pub fn flat(doc: EureDocument) -> Result<Self, PlanError> {
663 let mut b = Self::builder(doc);
664 let all = traverse::all_reachable_ids(b.document());
665 let root = b.document().get_root_id();
666 for id in all {
667 if id == root {
668 continue;
669 }
670 let kind = b.document().node(id).content.value_kind();
671 match kind {
672 ValueKind::Map | ValueKind::PartialMap => {
673 b.set_form(id, Form::BindingBlock)?;
674 }
675 ValueKind::Array => {
676 let form = match auto::auto_array_form(b.document(), id, false) {
677 ArrayForm::PerElement(_) | ArrayForm::PerElementIndexed(_) => {
678 ArrayForm::PerElement(Form::BindingBlock)
679 }
680 ArrayForm::Inline => ArrayForm::Inline,
681 };
682 b.set_array_form(id, form)?;
683 }
684 _ => {
685 b.set_form(id, Form::Inline)?;
686 }
687 }
688 }
689 b.build()
690 }
691
692 pub fn document(&self) -> &EureDocument {
694 &self.doc
695 }
696
697 pub fn form_of(&self, id: NodeId) -> Option<Form> {
699 self.forms.get(&id).copied()
700 }
701
702 pub fn array_form_of(&self, id: NodeId) -> Option<ArrayForm> {
704 self.array_forms.get(&id).copied()
705 }
706
707 pub fn order_of(&self, parent: NodeId) -> Option<&[NodeId]> {
709 self.order.get(&parent).map(|v| v.as_slice())
710 }
711
712 pub fn emit(self) -> crate::source::SourceDocument {
714 emit::emit(self)
715 }
716
717 fn validate_structure(&self) -> Result<(), PlanError> {
718 emit::dry_walk_validate(self)
720 }
721}
722
723#[cfg(test)]
724mod tests {
725 use super::*;
726 use crate::document::constructor::DocumentConstructor;
727 use crate::path::ArrayIndexKind;
728 use crate::value::{ObjectKey, PrimitiveValue};
729 use alloc::vec;
730
731 fn scalar_doc() -> EureDocument {
732 let mut c = DocumentConstructor::new();
733 c.bind_empty_map().unwrap();
734 let scope = c.begin_scope();
735 c.navigate(PathSegment::Value(ObjectKey::String("name".into())))
736 .unwrap();
737 c.bind_primitive(PrimitiveValue::from("Alice")).unwrap();
738 c.end_scope(scope).unwrap();
739 c.finish()
740 }
741
742 fn array_of_maps_doc() -> EureDocument {
743 let mut c = DocumentConstructor::new();
744 c.bind_empty_map().unwrap();
745 let outer = c.begin_scope();
746 c.navigate(PathSegment::Value(ObjectKey::String("items".into())))
747 .unwrap();
748 c.bind_empty_array().unwrap();
749
750 for name in ["a", "b"] {
751 let elem_scope = c.begin_scope();
752 c.navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
753 .unwrap();
754 c.bind_empty_map().unwrap();
755 let inner = c.begin_scope();
756 c.navigate(PathSegment::Value(ObjectKey::String("name".into())))
757 .unwrap();
758 c.bind_primitive(PrimitiveValue::from(name)).unwrap();
759 c.end_scope(inner).unwrap();
760 c.end_scope(elem_scope).unwrap();
761 }
762 c.end_scope(outer).unwrap();
763 c.finish()
764 }
765
766 fn nested_map_doc() -> EureDocument {
767 let mut c = DocumentConstructor::new();
768 c.bind_empty_map().unwrap();
769
770 let outer = c.begin_scope();
771 c.navigate(PathSegment::Value(ObjectKey::String("outer".into())))
772 .unwrap();
773 c.bind_empty_map().unwrap();
774
775 let inner_scope = c.begin_scope();
776 c.navigate(PathSegment::Value(ObjectKey::String("inner".into())))
777 .unwrap();
778 c.bind_empty_map().unwrap();
779
780 let leaf_scope = c.begin_scope();
781 c.navigate(PathSegment::Value(ObjectKey::String("name".into())))
782 .unwrap();
783 c.bind_primitive(PrimitiveValue::from("Ada")).unwrap();
784 c.end_scope(leaf_scope).unwrap();
785
786 c.end_scope(inner_scope).unwrap();
787 c.end_scope(outer).unwrap();
788 c.finish()
789 }
790
791 fn scalar_array_doc() -> EureDocument {
792 let mut c = DocumentConstructor::new();
793 c.bind_empty_map().unwrap();
794 let outer = c.begin_scope();
795 c.navigate(PathSegment::Value(ObjectKey::String("items".into())))
796 .unwrap();
797 c.bind_empty_array().unwrap();
798
799 for value in [1_i64, 2] {
800 let elem_scope = c.begin_scope();
801 c.navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
802 .unwrap();
803 c.bind_primitive(PrimitiveValue::Integer(value.into()))
804 .unwrap();
805 c.end_scope(elem_scope).unwrap();
806 }
807
808 c.end_scope(outer).unwrap();
809 c.finish()
810 }
811
812 fn array_of_partial_maps_doc() -> EureDocument {
813 let mut c = DocumentConstructor::new();
814 c.bind_empty_map().unwrap();
815 let outer = c.begin_scope();
816 c.navigate(PathSegment::Value(ObjectKey::String("items".into())))
817 .unwrap();
818 c.bind_empty_array().unwrap();
819
820 let elem_scope = c.begin_scope();
821 c.navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
822 .unwrap();
823 c.bind_empty_partial_map().unwrap();
824 let map_scope = c.begin_scope();
825 c.navigate(PathSegment::HoleKey(Some("x".parse().unwrap())))
826 .unwrap();
827 c.bind_primitive(PrimitiveValue::Integer(1.into())).unwrap();
828 c.end_scope(map_scope).unwrap();
829 c.end_scope(elem_scope).unwrap();
830
831 c.end_scope(outer).unwrap();
832 c.finish()
833 }
834
835 fn scalar_with_extension_doc() -> EureDocument {
836 let mut c = DocumentConstructor::new();
837 c.bind_empty_map().unwrap();
838
839 let outer = c.begin_scope();
840 c.navigate(PathSegment::Value(ObjectKey::String("name".into())))
841 .unwrap();
842 c.bind_primitive(PrimitiveValue::from("Alice")).unwrap();
843
844 let ext_scope = c.begin_scope();
845 c.navigate(PathSegment::Extension("meta".parse().unwrap()))
846 .unwrap();
847 c.bind_empty_map().unwrap();
848 let meta_scope = c.begin_scope();
849 c.navigate(PathSegment::Value(ObjectKey::String("alpha".into())))
850 .unwrap();
851 c.bind_primitive(PrimitiveValue::Integer(1.into())).unwrap();
852 c.end_scope(meta_scope).unwrap();
853 c.end_scope(ext_scope).unwrap();
854
855 c.end_scope(outer).unwrap();
856 c.finish()
857 }
858
859 fn root_extension_doc() -> EureDocument {
860 let mut c = DocumentConstructor::new();
861 c.bind_empty_map().unwrap();
862 c.set_extension("meta", true).unwrap();
863 let scope = c.begin_scope();
864 c.navigate(PathSegment::Value(ObjectKey::String("name".into())))
865 .unwrap();
866 c.bind_primitive(PrimitiveValue::from("Alice")).unwrap();
867 c.end_scope(scope).unwrap();
868 c.finish()
869 }
870
871 #[test]
872 fn auto_scalar_produces_inline_binding() {
873 let doc = scalar_doc();
874 let plan = LayoutPlan::auto(doc).unwrap();
875 let src = plan.emit();
876 let root = src.root_source();
877 assert_eq!(root.bindings.len(), 1);
878 assert!(matches!(
879 root.bindings[0].bind,
880 crate::source::BindSource::Value(_)
881 ));
882 assert!(root.sections.is_empty());
883 }
884
885 #[test]
886 fn auto_array_of_maps_emits_array_of_sections() {
887 let doc = array_of_maps_doc();
888 let plan = LayoutPlan::auto(doc).unwrap();
889 let src = plan.emit();
890 let root = src.root_source();
891
892 assert_eq!(root.sections.len(), 2, "expected two `@ items[]` sections");
893 for section in &root.sections {
894 let last = section.path.last().unwrap();
895 assert_eq!(
896 last.array,
897 Some(ArrayIndexKind::Push),
898 "expected push marker `[]` on array section"
899 );
900 }
901 }
902
903 #[test]
904 fn flat_array_of_maps_uses_binding_blocks() {
905 let doc = array_of_maps_doc();
906 let plan = LayoutPlan::flat(doc).unwrap();
907 let src = plan.emit();
908 let root = src.root_source();
909
910 assert_eq!(root.sections.len(), 0);
911 assert_eq!(root.bindings.len(), 2);
912 for b in &root.bindings {
913 assert!(matches!(b.bind, crate::source::BindSource::Block(_)));
914 assert_eq!(b.path.last().unwrap().array, Some(ArrayIndexKind::Push));
915 }
916 }
917
918 #[test]
919 fn set_form_on_array_rejected() {
920 let doc = array_of_maps_doc();
921 let mut b = LayoutPlan::builder(doc);
922 let id = b
923 .node_at(&[PathSegment::Value(ObjectKey::String("items".into()))])
924 .unwrap();
925 let err = b.set_form(id, Form::Section).unwrap_err();
926 assert!(matches!(err, PlanError::FormOnArrayNode(_)));
927 }
928
929 #[test]
930 fn set_array_form_on_non_array_rejected() {
931 let doc = scalar_doc();
932 let mut b = LayoutPlan::builder(doc);
933 let id = b
934 .node_at(&[PathSegment::Value(ObjectKey::String("name".into()))])
935 .unwrap();
936 let err = b.set_array_form(id, ArrayForm::Inline).unwrap_err();
937 assert!(matches!(err, PlanError::ArrayFormOnNonArray(_)));
938 }
939
940 #[test]
941 fn per_element_flatten_rejected() {
942 let doc = array_of_maps_doc();
943 let mut b = LayoutPlan::builder(doc);
944 let id = b
945 .node_at(&[PathSegment::Value(ObjectKey::String("items".into()))])
946 .unwrap();
947 let err = b
948 .set_array_form(id, ArrayForm::PerElement(Form::Flatten))
949 .unwrap_err();
950 assert!(matches!(err, PlanError::IncompatibleArrayForm { .. }));
951 }
952
953 #[test]
954 fn sectioned_nested_maps_rejected_in_items_context() {
955 let err = LayoutPlan::sectioned(nested_map_doc()).unwrap_err();
956 assert!(matches!(err, PlanError::SectionInForbiddenContext(_)));
957 }
958
959 #[test]
960 fn value_block_forms_rejected_for_maps() {
961 let doc = nested_map_doc();
962 let mut b = LayoutPlan::builder(doc);
963 let outer_id = b
964 .node_at(&[PathSegment::Value(ObjectKey::String("outer".into()))])
965 .unwrap();
966
967 let err = b.set_form(outer_id, Form::BindingValueBlock).unwrap_err();
968 assert!(matches!(err, PlanError::IncompatibleForm { .. }));
969
970 let err = b.set_form(outer_id, Form::SectionValueBlock).unwrap_err();
971 assert!(matches!(err, PlanError::IncompatibleForm { .. }));
972 }
973
974 #[test]
975 fn scalar_arrays_support_section_value_block_elements() {
976 let doc = scalar_array_doc();
977 let mut b = LayoutPlan::builder(doc);
978 let items_id = b
979 .node_at(&[PathSegment::Value(ObjectKey::String("items".into()))])
980 .unwrap();
981 b.set_array_form(items_id, ArrayForm::PerElement(Form::SectionValueBlock))
982 .unwrap();
983
984 let src = b.build().unwrap().emit();
985 let root = src.root_source();
986 assert_eq!(root.sections.len(), 2);
987 for section in &root.sections {
988 match §ion.body {
989 crate::source::SectionBody::Items { value, bindings } => {
990 assert!(value.is_some());
991 assert!(bindings.is_empty());
992 }
993 other => panic!("expected items body, got {other:?}"),
994 }
995 }
996 }
997
998 #[test]
999 fn per_element_inline_rejected_for_partial_map_elements() {
1000 let doc = array_of_partial_maps_doc();
1001 let mut b = LayoutPlan::builder(doc);
1002 let items_id = b
1003 .node_at(&[PathSegment::Value(ObjectKey::String("items".into()))])
1004 .unwrap();
1005 let err = b
1006 .set_array_form(items_id, ArrayForm::PerElement(Form::Inline))
1007 .unwrap_err();
1008 assert!(matches!(
1009 err,
1010 PlanError::IncompatibleArrayForm {
1011 reason: ArrayFormReason::ElementIncompatibleForm {
1012 kind: ValueKind::PartialMap,
1013 ..
1014 },
1015 ..
1016 }
1017 ));
1018 }
1019
1020 #[test]
1021 fn inline_extensions_inherit_section_context() {
1022 let doc = scalar_with_extension_doc();
1023 let mut b = LayoutPlan::builder(doc);
1024 b.set_form_at(
1025 &[PathSegment::Value(ObjectKey::String("name".into()))],
1026 Form::Inline,
1027 )
1028 .unwrap();
1029 b.set_form_at(
1030 &[
1031 PathSegment::Value(ObjectKey::String("name".into())),
1032 PathSegment::Extension("meta".parse().unwrap()),
1033 ],
1034 Form::Section,
1035 )
1036 .unwrap();
1037
1038 let src = b.build().unwrap().emit();
1039 let root = src.root_source();
1040 assert_eq!(root.bindings.len(), 1);
1041 assert_eq!(root.sections.len(), 1);
1042 }
1043
1044 #[test]
1045 fn per_element_section_block_roundtrips_path_with_push_marker() {
1046 let doc = array_of_maps_doc();
1047 let mut b = LayoutPlan::builder(doc);
1048 let id = b
1049 .node_at(&[PathSegment::Value(ObjectKey::String("items".into()))])
1050 .unwrap();
1051 b.set_array_form(id, ArrayForm::PerElement(Form::SectionBlock))
1052 .unwrap();
1053 let plan = b.build().unwrap();
1054 let src = plan.emit();
1055 let root = src.root_source();
1056 assert_eq!(root.sections.len(), 2);
1057 for section in &root.sections {
1058 assert!(matches!(section.body, crate::source::SectionBody::Block(_)));
1059 assert_eq!(
1060 section.path.last().unwrap().array,
1061 Some(ArrayIndexKind::Push)
1062 );
1063 }
1064 }
1065
1066 #[test]
1067 fn path_not_found_error() {
1068 let doc = scalar_doc();
1069 let b = LayoutPlan::builder(doc);
1070 let err = b
1071 .node_at(&[PathSegment::Value(ObjectKey::String("missing".into()))])
1072 .unwrap_err();
1073 assert!(matches!(err, PlanError::PathNotFound { .. }));
1074 }
1075
1076 #[test]
1077 fn order_on_array_rejected() {
1078 let doc = array_of_maps_doc();
1079 let mut b = LayoutPlan::builder(doc);
1080 let items_id = b
1081 .node_at(&[PathSegment::Value(ObjectKey::String("items".into()))])
1082 .unwrap();
1083 let err = b.order(items_id, vec![]).unwrap_err();
1084 assert!(matches!(err, PlanError::OrderOnArrayNode(_)));
1085 }
1086
1087 #[test]
1088 fn ordering_extensions_is_rejected() {
1089 let doc = root_extension_doc();
1090 let mut b = LayoutPlan::builder(doc);
1091 let err = b
1092 .order_at(&[], vec![PathSegment::Extension("meta".parse().unwrap())])
1093 .unwrap_err();
1094 assert!(matches!(err, PlanError::OrderExtensionChild { .. }));
1095 }
1096
1097 #[test]
1098 fn set_form_on_root_rejected() {
1099 let doc = scalar_doc();
1100 let root = doc.get_root_id();
1101 let mut b = LayoutPlan::builder(doc);
1102 let err = b.set_form(root, Form::Section).unwrap_err();
1103 assert!(matches!(err, PlanError::FormOnRoot(_)));
1104 }
1105}