Skip to main content

accent_proust/
renderable.rs

1//! The renderable tree: what transform produces and a renderer consumes.
2//!
3//! Mirrors upstream `src/tag.ts` and the four types in `src/types.ts` that
4//! describe its shape (`Scalar`, `RenderableTreeNode`, `RenderableTreeNodes`,
5//! `Primitive`). Upstream keeps them apart because TypeScript separates a class
6//! from the aliases that mention it; here they are one module because they are
7//! one data structure and every consumer needs all four.
8//!
9//! It sits at the crate root rather than under `transform` or `render` because
10//! it is the boundary *between* them. `Schema::transform` returns one of these,
11//! [`crate::render`] walks one, and the formatter never sees one at all. A type
12//! three stages share belongs above all three.
13//!
14//! # Owned, on purpose
15//!
16//! Nothing here borrows. The AST borrows its source -- a
17//! [`Location`](crate::ast::Location) is a byte range plus the text it covers --
18//! and that is where the lifetime stops. Transform resolves variables, runs
19//! schema hooks and synthesises nodes that were never in the source, so a
20//! renderable tree cannot honestly claim to be a view of a document. Making it
21//! owned is also what lets a host cache or send one.
22//!
23//! # The runtime type guard is not ported
24//!
25//! Upstream tags carry `$$mdtype: 'Tag'` and a static `Tag.isTag(x)`, because a
26//! JavaScript consumer holding `Tag | Scalar` has no other way to ask which it
27//! has. [`RenderableTreeNode`] is an enum, so the question is answered by
28//! matching and the guard has nothing left to protect. The same call was already
29//! made for the AST's `$$mdtype`.
30
31use indexmap::IndexMap;
32
33use crate::ast::Value;
34
35/// A JSON-shaped value: what an attribute holds and what a leaf child is.
36///
37/// Upstream's `Scalar = Primitive | Scalar[] | {[key: string]: Scalar}`, with
38/// `Primitive = null | boolean | number | string`. The primitives are spelled
39/// out as variants here rather than kept in a separate `Primitive` type,
40/// because Rust has no untagged union to build one out of and a nested
41/// `Scalar::Primitive(Primitive::String(..))` would only add a level of
42/// wrapping for consumers to strip.
43///
44/// # Absence is not null
45///
46/// JavaScript distinguishes `null` from `undefined`, and Markdoc uses the
47/// difference: an attribute whose value is `undefined` is dropped by the
48/// transformer, while `null` is rendered. So absence is [`Option::None`] around
49/// a `Scalar` and never [`Scalar::Null`]. Collapsing the two would make
50/// `{% foo bar=null /%}` and `{% foo /%}` the same document.
51#[non_exhaustive]
52pub enum Scalar {
53    /// `null`.
54    Null,
55    /// `true` or `false`.
56    Boolean(bool),
57    /// A number. One numeric type, `f64`, as everywhere else in this crate:
58    /// upstream parses every literal with `parseFloat`.
59    Number(f64),
60    /// A string.
61    String(String),
62    /// An array.
63    Array(Vec<Scalar>),
64    /// An object, in insertion order.
65    ///
66    /// [`IndexMap`] rather than `HashMap` for the reason attributes are: two
67    /// runs over one document must produce identical bytes.
68    Object(IndexMap<String, Scalar>),
69}
70
71/// # Why `Clone`, `PartialEq` and `Debug` are written out rather than derived
72///
73/// For the reason [`Drop`] is: a derived implementation of any of the three
74/// recurses once per level, and [`Scalar::Array`] and [`Scalar::Object`] are
75/// public and recursive, so a caller can assemble one deep enough to overflow
76/// the stack. That is an abort rather than a panic, and an abort cannot be
77/// caught -- which would make this crate's panic-freedom promise untrue for a
78/// value a host built through the public API.
79///
80/// The value grammar bounds what this crate *parses* at
81/// [`MAX_VALUE_DEPTH`](crate::grammar::MAX_VALUE_DEPTH) (`DIVERGENCES.md`
82/// entry 9). It bounds nothing a caller assembles in Rust.
83///
84/// Each of the three takes the shape its job allows, and they are three
85/// different shapes -- which is why there is no shared helper:
86///
87/// * **`PartialEq`** carries a worklist of pairs: compare one level, push the
88///   children pairwise, stop at the first inequality.
89/// * **`Clone`** is constructive, so a worklist is not enough -- a parent
90///   cannot be built until its children exist. It walks post-order onto an
91///   explicit plan, then rebuilds bottom-up off a stack of finished subtrees.
92///   No raw pointer into a half-built tree, which is what keeps
93///   `unsafe_code = "forbid"` intact.
94/// * **`Debug`** emits the derive's own text from a stack of pending tokens.
95///   That format is observable -- callers assert on it -- so the `debug_parity`
96///   tests compare every shape against a mirror type that still derives
97///   `Debug`, in both `{:?}` and `{:#?}`.
98impl Clone for Scalar {
99    fn clone(&self) -> Self {
100        let mut plan = vec![Step::Open(self)];
101        let mut done: Vec<Scalar> = Vec::new();
102
103        while let Some(step) = plan.pop() {
104            match step {
105                Step::Open(scalar) => match scalar {
106                    Scalar::Array(items) => {
107                        plan.push(Step::Close(scalar));
108                        // Reversed, so the stack yields children left to right
109                        // and `done` collects finished subtrees in order.
110                        for item in items.iter().rev() {
111                            plan.push(Step::Open(item));
112                        }
113                    }
114                    Scalar::Object(entries) => {
115                        plan.push(Step::Close(scalar));
116                        for (_, value) in entries.iter().rev() {
117                            plan.push(Step::Open(value));
118                        }
119                    }
120                    Scalar::Null => done.push(Scalar::Null),
121                    Scalar::Boolean(value) => done.push(Scalar::Boolean(*value)),
122                    Scalar::Number(value) => done.push(Scalar::Number(*value)),
123                    Scalar::String(value) => done.push(Scalar::String(value.clone())),
124                },
125                Step::Close(scalar) => match scalar {
126                    Scalar::Array(items) => {
127                        let start = done.len().saturating_sub(items.len());
128                        let children = done.split_off(start);
129                        done.push(Scalar::Array(children));
130                    }
131                    Scalar::Object(entries) => {
132                        let start = done.len().saturating_sub(entries.len());
133                        let values = done.split_off(start);
134                        done.push(Scalar::Object(
135                            entries.keys().cloned().zip(values).collect(),
136                        ));
137                    }
138                    // Only the two composites are ever closed.
139                    _ => {}
140                },
141            }
142        }
143
144        done.pop().unwrap_or(Scalar::Null)
145    }
146}
147
148/// One step of the post-order clone plan: see a node, then rebuild it.
149enum Step<'s> {
150    Open(&'s Scalar),
151    Close(&'s Scalar),
152}
153
154impl PartialEq for Scalar {
155    fn eq(&self, other: &Self) -> bool {
156        let mut work: Vec<(&Scalar, &Scalar)> = vec![(self, other)];
157        while let Some((left, right)) = work.pop() {
158            match (left, right) {
159                (Scalar::Null, Scalar::Null) => {}
160                (Scalar::Boolean(a), Scalar::Boolean(b)) => {
161                    if a != b {
162                        return false;
163                    }
164                }
165                (Scalar::Number(a), Scalar::Number(b)) => {
166                    if a != b {
167                        return false;
168                    }
169                }
170                (Scalar::String(a), Scalar::String(b)) => {
171                    if a != b {
172                        return false;
173                    }
174                }
175                (Scalar::Array(a), Scalar::Array(b)) => {
176                    if a.len() != b.len() {
177                        return false;
178                    }
179                    work.extend(a.iter().zip(b.iter()));
180                }
181                (Scalar::Object(a), Scalar::Object(b)) => {
182                    // Key lookup rather than a positional zip: `IndexMap`'s own
183                    // `PartialEq` compares as an unordered collection, and this
184                    // has to keep saying exactly what the derive said.
185                    if a.len() != b.len() {
186                        return false;
187                    }
188                    for (key, value) in a {
189                        match b.get(key) {
190                            Some(other) => work.push((value, other)),
191                            None => return false,
192                        }
193                    }
194                }
195                _ => return false,
196            }
197        }
198        true
199    }
200}
201
202impl std::fmt::Debug for Scalar {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        let alternate = f.alternate();
205        let mut stack: Vec<DebugTok<'_>> = vec![DebugTok::Node(self, 0)];
206
207        while let Some(token) = stack.pop() {
208            match token {
209                DebugTok::Text(text) => f.write_str(text)?,
210                DebugTok::Owned(text) => f.write_str(&text)?,
211                DebugTok::Line(depth) => {
212                    f.write_str("\n")?;
213                    for _ in 0..depth {
214                        f.write_str("    ")?;
215                    }
216                }
217                DebugTok::Node(scalar, depth) => {
218                    expand_scalar(f, &mut stack, scalar, depth, alternate)?;
219                }
220            }
221        }
222        Ok(())
223    }
224}
225
226/// One pending piece of `Debug` output.
227///
228/// The stack is what makes the walk iterative; `Line` carries its own depth
229/// because a token is emitted long after the node that queued it.
230enum DebugTok<'s> {
231    Node(&'s Scalar, usize),
232    Text(&'static str),
233    Owned(String),
234    Line(usize),
235}
236
237/// Write a scalar's opening text and queue the rest of it.
238///
239/// Leaves are written whole: their fields cannot recurse, so there is nothing
240/// to queue. Only `Array` and `Object` push.
241fn expand_scalar<'s>(
242    f: &mut std::fmt::Formatter<'_>,
243    stack: &mut Vec<DebugTok<'s>>,
244    scalar: &'s Scalar,
245    depth: usize,
246    alternate: bool,
247) -> std::fmt::Result {
248    /// The derive expands a tuple variant's field onto its own line under
249    /// `{:#?}`, even when the field is a bare `bool`.
250    fn leaf(
251        f: &mut std::fmt::Formatter<'_>,
252        name: &str,
253        body: &str,
254        depth: usize,
255        alternate: bool,
256    ) -> std::fmt::Result {
257        if alternate {
258            let pad = "    ".repeat(depth);
259            write!(f, "{name}(\n{pad}    {body},\n{pad})")
260        } else {
261            write!(f, "{name}({body})")
262        }
263    }
264
265    match scalar {
266        Scalar::Null => f.write_str("Null"),
267        Scalar::Boolean(value) => leaf(f, "Boolean", &format!("{value:?}"), depth, alternate),
268        Scalar::Number(value) => leaf(f, "Number", &format!("{value:?}"), depth, alternate),
269        Scalar::String(value) => leaf(f, "String", &format!("{value:?}"), depth, alternate),
270        Scalar::Array(items) => {
271            if items.is_empty() {
272                return leaf(f, "Array", "[]", depth, alternate);
273            }
274            f.write_str("Array(")?;
275            // Pushed in reverse: the stack emits them in the order written here.
276            let mut queued: Vec<DebugTok<'s>> = Vec::new();
277            if alternate {
278                queued.push(DebugTok::Line(depth + 1));
279                queued.push(DebugTok::Text("["));
280                for item in items {
281                    queued.push(DebugTok::Line(depth + 2));
282                    queued.push(DebugTok::Node(item, depth + 2));
283                    queued.push(DebugTok::Text(","));
284                }
285                queued.push(DebugTok::Line(depth + 1));
286                queued.push(DebugTok::Text("],"));
287                queued.push(DebugTok::Line(depth));
288                queued.push(DebugTok::Text(")"));
289            } else {
290                queued.push(DebugTok::Text("["));
291                for (index, item) in items.iter().enumerate() {
292                    if index > 0 {
293                        queued.push(DebugTok::Text(", "));
294                    }
295                    queued.push(DebugTok::Node(item, depth));
296                }
297                queued.push(DebugTok::Text("])"));
298            }
299            stack.extend(queued.into_iter().rev());
300            Ok(())
301        }
302        Scalar::Object(entries) => {
303            if entries.is_empty() {
304                return leaf(f, "Object", "{}", depth, alternate);
305            }
306            f.write_str("Object(")?;
307            let mut queued: Vec<DebugTok<'s>> = Vec::new();
308            if alternate {
309                queued.push(DebugTok::Line(depth + 1));
310                queued.push(DebugTok::Text("{"));
311                for (key, value) in entries {
312                    queued.push(DebugTok::Line(depth + 2));
313                    queued.push(DebugTok::Owned(format!("{key:?}: ")));
314                    queued.push(DebugTok::Node(value, depth + 2));
315                    queued.push(DebugTok::Text(","));
316                }
317                queued.push(DebugTok::Line(depth + 1));
318                queued.push(DebugTok::Text("},"));
319                queued.push(DebugTok::Line(depth));
320                queued.push(DebugTok::Text(")"));
321            } else {
322                queued.push(DebugTok::Text("{"));
323                for (index, (key, value)) in entries.iter().enumerate() {
324                    if index > 0 {
325                        queued.push(DebugTok::Text(", "));
326                    }
327                    queued.push(DebugTok::Owned(format!("{key:?}: ")));
328                    queued.push(DebugTok::Node(value, depth));
329                }
330                queued.push(DebugTok::Text("})"));
331            }
332            stack.extend(queued.into_iter().rev());
333            Ok(())
334        }
335    }
336}
337
338impl Scalar {
339    /// The scalar form of an AST value, or [`None`] if it has none.
340    ///
341    /// A [`Value::Function`] and a [`Value::Variable`] are unresolved
342    /// references, not data: they have no scalar spelling until the transform
343    /// stage resolves them against a config. Returning [`None`] rather than
344    /// inventing one is what keeps "this attribute was never resolved" from
345    /// silently rendering as a string.
346    #[must_use]
347    pub fn from_value(value: &Value) -> Option<Scalar> {
348        match value {
349            Value::Null => Some(Scalar::Null),
350            Value::Boolean(b) => Some(Scalar::Boolean(*b)),
351            Value::Number(n) => Some(Scalar::Number(*n)),
352            Value::String(s) => Some(Scalar::String(s.clone())),
353            Value::Array(items) => items
354                .iter()
355                .map(Scalar::from_value)
356                .collect::<Option<Vec<_>>>()
357                .map(Scalar::Array),
358            Value::Hash(entries) => entries
359                .iter()
360                .map(|(key, value)| Scalar::from_value(value).map(|value| (key.clone(), value)))
361                .collect::<Option<IndexMap<_, _>>>()
362                .map(Scalar::Object),
363            Value::Function(_) | Value::Variable(_) => None,
364        }
365    }
366}
367
368/// One node of a renderable tree: a tag, or a value.
369///
370/// Upstream's `RenderableTreeNode = Tag | Scalar`. A renderer walks these and a
371/// host may build them by hand, which is what makes a renderer outside this
372/// crate possible -- the React renderers upstream ships are not ported, and this
373/// is the type that keeps writing one an option rather than a fork.
374#[derive(Clone, Debug, PartialEq)]
375#[non_exhaustive]
376pub enum RenderableTreeNode {
377    /// An element: a name, attributes, and children.
378    ///
379    /// Boxed because [`Tag`] contains a `Vec<RenderableTreeNode>`, so an
380    /// unboxed variant would make the enum's size the tag's.
381    Tag(Box<Tag>),
382    /// A value rendered in place -- most often the string of a text node.
383    Scalar(Scalar),
384}
385
386impl RenderableTreeNode {
387    /// Wrap a tag.
388    #[must_use]
389    pub fn tag(tag: Tag) -> RenderableTreeNode {
390        RenderableTreeNode::Tag(Box::new(tag))
391    }
392
393    /// Wrap a string, which is what a text node renders to.
394    #[must_use]
395    pub fn text(text: impl Into<String>) -> RenderableTreeNode {
396        RenderableTreeNode::Scalar(Scalar::String(text.into()))
397    }
398}
399
400/// What a `transform` hook returns: one node, or a list of them.
401///
402/// Upstream's `RenderableTreeNodes = RenderableTreeNode | RenderableTreeNode[]`,
403/// and the plural matters. A schema with no `render` transforms to its children
404/// rather than to an element, so "this node became three nodes" has to be
405/// expressible; and a slot rendered into an attribute is a *list* of nodes,
406/// which the conformance corpus compares as a JSON array rather than as a
407/// single value. Flattening the two would change the tree the corpus grades.
408#[derive(Clone, Debug, PartialEq)]
409#[non_exhaustive]
410pub enum RenderableTreeNodes {
411    /// Exactly one node.
412    One(RenderableTreeNode),
413    /// Zero or more, in document order.
414    Many(Vec<RenderableTreeNode>),
415}
416
417impl RenderableTreeNodes {
418    /// The nodes as a list, whichever shape they arrived in.
419    ///
420    /// This is upstream's `flatMap` over a `MaybeArray`, which every consumer
421    /// that appends to a child list needs and which is easy to get wrong by
422    /// pushing a `Many` in as one child.
423    #[must_use]
424    pub fn into_vec(self) -> Vec<RenderableTreeNode> {
425        match self {
426            RenderableTreeNodes::One(node) => vec![node],
427            RenderableTreeNodes::Many(nodes) => nodes,
428        }
429    }
430}
431
432impl From<RenderableTreeNode> for RenderableTreeNodes {
433    fn from(node: RenderableTreeNode) -> RenderableTreeNodes {
434        RenderableTreeNodes::One(node)
435    }
436}
437
438impl From<Vec<RenderableTreeNode>> for RenderableTreeNodes {
439    fn from(nodes: Vec<RenderableTreeNode>) -> RenderableTreeNodes {
440        RenderableTreeNodes::Many(nodes)
441    }
442}
443
444impl From<Tag> for RenderableTreeNodes {
445    fn from(tag: Tag) -> RenderableTreeNodes {
446        RenderableTreeNodes::One(RenderableTreeNode::tag(tag))
447    }
448}
449
450/// An element in a renderable tree.
451///
452/// Mirrors upstream `src/tag.ts`. The name is what a renderer emits -- `p`,
453/// `article`, or whatever a schema's `render` said -- and it is a plain string
454/// rather than an HTML element type, because this crate decides no HTML policy.
455/// A host rendering to something that is not HTML puts its own names here.
456///
457/// # Why an attribute holds a whole subtree
458///
459/// Upstream types attributes as `Record<string, any>` and means it: an ordinary
460/// attribute is a scalar, but a rendered slot is put in the attribute map as the
461/// *transformed nodes* of that slot (`transformer.ts`, `attributes`). The
462/// corpus fixes this -- "Basic slot" expects `attributes: {bar: [{tag: p, ...}]}`
463/// -- so narrowing attributes to [`Scalar`] would fail cases that are otherwise
464/// correct. [`RenderableTreeNodes`] is the honest type, and a scalar attribute
465/// is `One(Scalar(..))`.
466pub struct Tag {
467    /// The element name. Upstream defaults it to `div`.
468    pub name: String,
469    /// The attributes, in authored order.
470    pub attributes: IndexMap<String, RenderableTreeNodes>,
471    /// The children, in document order.
472    pub children: Vec<RenderableTreeNode>,
473}
474
475/// The tags nested inside this one, in the order the traversals agree to use.
476///
477/// Attributes first, in authored order, then children. Every hand-written
478/// traversal walks this order, which is what lets [`Clone`] queue subtrees and
479/// then reclaim them positionally.
480fn nested_tags(tag: &Tag) -> Vec<&Tag> {
481    let mut out = Vec::new();
482    for (_, nodes) in &tag.attributes {
483        for node in nodes_slice(nodes) {
484            if let RenderableTreeNode::Tag(inner) = node {
485                out.push(inner.as_ref());
486            }
487        }
488    }
489    for node in &tag.children {
490        if let RenderableTreeNode::Tag(inner) = node {
491            out.push(inner.as_ref());
492        }
493    }
494    out
495}
496
497/// The nodes inside a [`RenderableTreeNodes`], as a slice either way.
498fn nodes_slice(nodes: &RenderableTreeNodes) -> &[RenderableTreeNode] {
499    match nodes {
500        RenderableTreeNodes::One(node) => std::slice::from_ref(node),
501        RenderableTreeNodes::Many(many) => many.as_slice(),
502    }
503}
504
505/// # Why the three traversals are written out rather than derived
506///
507/// The reasoning is on [`Scalar`], and applies here for the same reason it
508/// applies to [`Drop`]: a tag's children are tags, so a derived `Clone`,
509/// `PartialEq` or `Debug` recurses per level of a tree whose depth is
510/// attacker-controlled.
511///
512/// [`RenderableTreeNode`] and [`RenderableTreeNodes`] keep their derives, and
513/// that is safe *because* these exist: their recursion reaches a [`Tag`] or a
514/// [`Scalar`] in one step, and both stop there. Nothing here may call those
515/// derives on a nested tag, which is why the walks decompose them by hand.
516impl Clone for Tag {
517    fn clone(&self) -> Self {
518        enum Step<'t> {
519            Open(&'t Tag),
520            Close(&'t Tag),
521        }
522
523        let mut plan = vec![Step::Open(self)];
524        let mut done: Vec<Tag> = Vec::new();
525
526        while let Some(step) = plan.pop() {
527            match step {
528                Step::Open(tag) => {
529                    plan.push(Step::Close(tag));
530                    for nested in nested_tags(tag).into_iter().rev() {
531                        plan.push(Step::Open(nested));
532                    }
533                }
534                Step::Close(tag) => {
535                    let count = nested_tags(tag).len();
536                    let start = done.len().saturating_sub(count);
537                    let mut finished = done.split_off(start).into_iter();
538
539                    let mut attributes = IndexMap::new();
540                    for (key, nodes) in &tag.attributes {
541                        let rebuilt = match nodes {
542                            RenderableTreeNodes::One(node) => {
543                                RenderableTreeNodes::One(clone_node_taking(node, &mut finished))
544                            }
545                            RenderableTreeNodes::Many(many) => RenderableTreeNodes::Many(
546                                many.iter()
547                                    .map(|node| clone_node_taking(node, &mut finished))
548                                    .collect(),
549                            ),
550                        };
551                        attributes.insert(key.clone(), rebuilt);
552                    }
553                    let children = tag
554                        .children
555                        .iter()
556                        .map(|node| clone_node_taking(node, &mut finished))
557                        .collect();
558
559                    done.push(Tag {
560                        name: tag.name.clone(),
561                        attributes,
562                        children,
563                    });
564                }
565            }
566        }
567
568        done.pop().unwrap_or_else(|| Tag::new("div"))
569    }
570}
571
572/// Rebuild one child node, taking an already-finished tag when it is one.
573///
574/// The iterator is consumed in the same order [`nested_tags`] produced, which
575/// is what makes the positional hand-off correct.
576fn clone_node_taking(
577    node: &RenderableTreeNode,
578    finished: &mut impl Iterator<Item = Tag>,
579) -> RenderableTreeNode {
580    match node {
581        RenderableTreeNode::Tag(_) => finished.next().map_or_else(
582            || RenderableTreeNode::tag(Tag::new("div")),
583            |tag| RenderableTreeNode::Tag(Box::new(tag)),
584        ),
585        // `Scalar` carries its own iterative clone, so this recurses no further.
586        RenderableTreeNode::Scalar(scalar) => RenderableTreeNode::Scalar(scalar.clone()),
587    }
588}
589
590impl PartialEq for Tag {
591    fn eq(&self, other: &Self) -> bool {
592        let mut work: Vec<(&Tag, &Tag)> = vec![(self, other)];
593        while let Some((left, right)) = work.pop() {
594            if left.name != right.name
595                || left.attributes.len() != right.attributes.len()
596                || left.children.len() != right.children.len()
597            {
598                return false;
599            }
600            // Attributes are an `IndexMap`, whose own `PartialEq` is unordered.
601            for (key, nodes) in &left.attributes {
602                let Some(other_nodes) = right.attributes.get(key) else {
603                    return false;
604                };
605                if !push_node_pairs(nodes, other_nodes, &mut work) {
606                    return false;
607                }
608            }
609            for (a, b) in left.children.iter().zip(right.children.iter()) {
610                if !push_node_pair(a, b, &mut work) {
611                    return false;
612                }
613            }
614        }
615        true
616    }
617}
618
619/// Compare two attribute values shallowly, queueing any tag pair.
620///
621/// Returns `false` the moment they cannot be equal. `One` and `Many` are
622/// different variants and so are never equal, which is what the derive said.
623fn push_node_pairs<'t>(
624    left: &'t RenderableTreeNodes,
625    right: &'t RenderableTreeNodes,
626    work: &mut Vec<(&'t Tag, &'t Tag)>,
627) -> bool {
628    match (left, right) {
629        (RenderableTreeNodes::One(a), RenderableTreeNodes::One(b)) => push_node_pair(a, b, work),
630        (RenderableTreeNodes::Many(a), RenderableTreeNodes::Many(b)) => {
631            if a.len() != b.len() {
632                return false;
633            }
634            a.iter()
635                .zip(b.iter())
636                .all(|(x, y)| push_node_pair(x, y, work))
637        }
638        _ => false,
639    }
640}
641
642/// Compare two child nodes shallowly, queueing the pair when both are tags.
643fn push_node_pair<'t>(
644    left: &'t RenderableTreeNode,
645    right: &'t RenderableTreeNode,
646    work: &mut Vec<(&'t Tag, &'t Tag)>,
647) -> bool {
648    match (left, right) {
649        (RenderableTreeNode::Tag(a), RenderableTreeNode::Tag(b)) => {
650            work.push((a.as_ref(), b.as_ref()));
651            true
652        }
653        // `Scalar`'s own equality is iterative, so this recurses no further.
654        (RenderableTreeNode::Scalar(a), RenderableTreeNode::Scalar(b)) => a == b,
655        _ => false,
656    }
657}
658
659impl std::fmt::Debug for Tag {
660    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
661        let alternate = f.alternate();
662        let mut stack: Vec<TagTok<'_>> = vec![TagTok::Tag(self, 0)];
663
664        while let Some(token) = stack.pop() {
665            match token {
666                TagTok::Text(text) => f.write_str(text)?,
667                TagTok::Owned(text) => f.write_str(&text)?,
668                TagTok::Line(depth) => {
669                    f.write_str("\n")?;
670                    for _ in 0..depth {
671                        f.write_str("    ")?;
672                    }
673                }
674                TagTok::Tag(tag, depth) => expand_tag(f, &mut stack, tag, depth, alternate)?,
675                TagTok::Nodes(nodes, depth) => {
676                    expand_nodes(&mut stack, nodes, depth, alternate);
677                }
678                TagTok::Node(node, depth) => {
679                    expand_node(f, &mut stack, node, depth, alternate)?;
680                }
681            }
682        }
683        Ok(())
684    }
685}
686
687/// One pending piece of `Debug` output for a renderable tree.
688enum TagTok<'t> {
689    Tag(&'t Tag, usize),
690    Nodes(&'t RenderableTreeNodes, usize),
691    Node(&'t RenderableTreeNode, usize),
692    Text(&'static str),
693    Owned(String),
694    Line(usize),
695}
696
697/// Re-pad every line after the first, so a block formatted at column zero can
698/// be spliced in at `depth`.
699fn indent_block(body: &str, depth: usize) -> String {
700    let pad = "    ".repeat(depth);
701    body.replace('\n', &format!("\n{pad}"))
702}
703
704/// `Tag { name: .., attributes: .., children: .. }`, as the derive writes it.
705fn expand_tag<'t>(
706    f: &mut std::fmt::Formatter<'_>,
707    stack: &mut Vec<TagTok<'t>>,
708    tag: &'t Tag,
709    depth: usize,
710    alternate: bool,
711) -> std::fmt::Result {
712    let name = format!("{:?}", tag.name);
713    let mut queued: Vec<TagTok<'t>> = Vec::new();
714
715    if alternate {
716        f.write_str("Tag {")?;
717        queued.push(TagTok::Line(depth + 1));
718        queued.push(TagTok::Owned(format!("name: {name},")));
719        queued.push(TagTok::Line(depth + 1));
720        if tag.attributes.is_empty() {
721            queued.push(TagTok::Text("attributes: {},"));
722        } else {
723            queued.push(TagTok::Text("attributes: {"));
724            for (key, nodes) in &tag.attributes {
725                queued.push(TagTok::Line(depth + 2));
726                queued.push(TagTok::Owned(format!("{key:?}: ")));
727                queued.push(TagTok::Nodes(nodes, depth + 2));
728                queued.push(TagTok::Text(","));
729            }
730            queued.push(TagTok::Line(depth + 1));
731            queued.push(TagTok::Text("},"));
732        }
733        queued.push(TagTok::Line(depth + 1));
734        if tag.children.is_empty() {
735            queued.push(TagTok::Text("children: [],"));
736        } else {
737            queued.push(TagTok::Text("children: ["));
738            for child in &tag.children {
739                queued.push(TagTok::Line(depth + 2));
740                queued.push(TagTok::Node(child, depth + 2));
741                queued.push(TagTok::Text(","));
742            }
743            queued.push(TagTok::Line(depth + 1));
744            queued.push(TagTok::Text("],"));
745        }
746        queued.push(TagTok::Line(depth));
747        queued.push(TagTok::Text("}"));
748    } else {
749        write!(f, "Tag {{ name: {name}, attributes: ")?;
750        if tag.attributes.is_empty() {
751            queued.push(TagTok::Text("{}"));
752        } else {
753            queued.push(TagTok::Text("{"));
754            for (index, (key, nodes)) in tag.attributes.iter().enumerate() {
755                if index > 0 {
756                    queued.push(TagTok::Text(", "));
757                }
758                queued.push(TagTok::Owned(format!("{key:?}: ")));
759                queued.push(TagTok::Nodes(nodes, depth));
760            }
761            queued.push(TagTok::Text("}"));
762        }
763        queued.push(TagTok::Text(", children: ["));
764        for (index, child) in tag.children.iter().enumerate() {
765            if index > 0 {
766                queued.push(TagTok::Text(", "));
767            }
768            queued.push(TagTok::Node(child, depth));
769        }
770        queued.push(TagTok::Text("] }"));
771    }
772
773    stack.extend(queued.into_iter().rev());
774    Ok(())
775}
776
777/// `One(..)` or `Many([..])`.
778fn expand_nodes<'t>(
779    stack: &mut Vec<TagTok<'t>>,
780    nodes: &'t RenderableTreeNodes,
781    depth: usize,
782    alternate: bool,
783) {
784    let mut queued: Vec<TagTok<'t>> = Vec::new();
785    match nodes {
786        RenderableTreeNodes::One(node) => {
787            queued.push(TagTok::Text("One("));
788            if alternate {
789                queued.push(TagTok::Line(depth + 1));
790                queued.push(TagTok::Node(node, depth + 1));
791                queued.push(TagTok::Text(","));
792                queued.push(TagTok::Line(depth));
793            } else {
794                queued.push(TagTok::Node(node, depth));
795            }
796            queued.push(TagTok::Text(")"));
797        }
798        RenderableTreeNodes::Many(many) if many.is_empty() => {
799            queued.push(TagTok::Text(if alternate { "Many(" } else { "Many([])" }));
800            if alternate {
801                queued.push(TagTok::Line(depth + 1));
802                queued.push(TagTok::Text("[],"));
803                queued.push(TagTok::Line(depth));
804                queued.push(TagTok::Text(")"));
805            }
806        }
807        RenderableTreeNodes::Many(many) => {
808            queued.push(TagTok::Text("Many("));
809            if alternate {
810                queued.push(TagTok::Line(depth + 1));
811                queued.push(TagTok::Text("["));
812                for node in many {
813                    queued.push(TagTok::Line(depth + 2));
814                    queued.push(TagTok::Node(node, depth + 2));
815                    queued.push(TagTok::Text(","));
816                }
817                queued.push(TagTok::Line(depth + 1));
818                queued.push(TagTok::Text("],"));
819                queued.push(TagTok::Line(depth));
820            } else {
821                queued.push(TagTok::Text("["));
822                for (index, node) in many.iter().enumerate() {
823                    if index > 0 {
824                        queued.push(TagTok::Text(", "));
825                    }
826                    queued.push(TagTok::Node(node, depth));
827                }
828                queued.push(TagTok::Text("]"));
829            }
830            queued.push(TagTok::Text(")"));
831        }
832    }
833    stack.extend(queued.into_iter().rev());
834}
835
836/// `Tag(..)` or `Scalar(..)`.
837fn expand_node<'t>(
838    f: &mut std::fmt::Formatter<'_>,
839    stack: &mut Vec<TagTok<'t>>,
840    node: &'t RenderableTreeNode,
841    depth: usize,
842    alternate: bool,
843) -> std::fmt::Result {
844    match node {
845        RenderableTreeNode::Tag(inner) => {
846            let mut queued: Vec<TagTok<'t>> = Vec::new();
847            f.write_str("Tag(")?;
848            if alternate {
849                queued.push(TagTok::Line(depth + 1));
850                queued.push(TagTok::Tag(inner.as_ref(), depth + 1));
851                queued.push(TagTok::Text(","));
852                queued.push(TagTok::Line(depth));
853            } else {
854                queued.push(TagTok::Tag(inner.as_ref(), depth));
855            }
856            queued.push(TagTok::Text(")"));
857            stack.extend(queued.into_iter().rev());
858            Ok(())
859        }
860        // `Scalar` carries its own iterative `Debug`, so delegating stops here.
861        // The alternate block is re-indented, because it formats from column
862        // zero and is being spliced in one level down.
863        RenderableTreeNode::Scalar(scalar) => {
864            if alternate {
865                let pad = "    ".repeat(depth);
866                let block = indent_block(&format!("{scalar:#?}"), depth + 1);
867                write!(f, "Scalar(\n{pad}    {block},\n{pad})")
868            } else {
869                write!(f, "Scalar({scalar:?})")
870            }
871        }
872    }
873}
874
875impl Tag {
876    /// A tag with no attributes and no children.
877    #[must_use]
878    pub fn new(name: impl Into<String>) -> Tag {
879        Tag {
880            name: name.into(),
881            attributes: IndexMap::new(),
882            children: Vec::new(),
883        }
884    }
885
886    /// A tag with attributes and children.
887    ///
888    /// The argument order is upstream's `new Tag(name, attributes, children)`,
889    /// so a ported test reads next to the TypeScript it came from.
890    #[must_use]
891    pub fn with(
892        name: impl Into<String>,
893        attributes: IndexMap<String, RenderableTreeNodes>,
894        children: Vec<RenderableTreeNode>,
895    ) -> Tag {
896        Tag {
897            name: name.into(),
898            attributes,
899            children,
900        }
901    }
902
903    /// Set an attribute to a single value, in authored order.
904    pub fn set(&mut self, name: impl Into<String>, value: impl Into<RenderableTreeNodes>) {
905        self.attributes.insert(name.into(), value.into());
906    }
907
908    /// Append a child.
909    pub fn push(&mut self, child: RenderableTreeNode) {
910        self.children.push(child);
911    }
912}
913
914impl Default for Tag {
915    /// Upstream's default element is `div`, and schemas rely on it: a tag
916    /// constructed with no name renders as a `div` rather than as nothing.
917    fn default() -> Tag {
918        Tag::new("div")
919    }
920}
921
922/// Dropping a renderable tree is iterative, for the reason dropping an AST is.
923///
924/// [`Node`](crate::ast::Node) carries a manual `Drop` because nesting depth is
925/// attacker-controlled and the derived recursive drop aborts the process on a
926/// deep document. A renderable tree is *built from* that AST, one tag per
927/// nested tag, so it inherits the same exposure and needs the same guard. An
928/// abort cannot be caught, so the crate's panic-freedom promise is not true
929/// without it.
930///
931/// One `Drop` covers the whole tree. A [`RenderableTreeNode`] and a
932/// [`RenderableTreeNodes`] are shallow wrappers whose derived drops recurse
933/// exactly one level before reaching a [`Tag`], and this implementation unlinks
934/// every descendant onto the heap before any of them is dropped -- so each tag
935/// it drops is already empty and recurses no further. Putting a manual `Drop`
936/// on the enums instead would forbid moving a tag *out* of one, which is what
937/// a renderer does on every node.
938///
939/// [`Scalar`] carries its own guard, below, for a different reason: its nesting
940/// is bounded for values this crate builds and unbounded for values a caller
941/// builds.
942///
943/// The cost, stated because it is invisible until someone hits it: a type with a
944/// manual `Drop` cannot have a field moved out of it, so taking ownership of
945/// [`Tag::children`] needs [`std::mem::take`] rather than a partial move. That
946/// is the same tax `Node` charges, paid for the same reason.
947impl Drop for Tag {
948    fn drop(&mut self) {
949        let mut pending: Vec<Tag> = Vec::new();
950        unlink(self, &mut pending);
951        while let Some(mut tag) = pending.pop() {
952            unlink(&mut tag, &mut pending);
953            // `tag` is dropped here already emptied, so this recurses once.
954        }
955    }
956}
957
958/// Move every tag directly inside `tag` onto `pending`, leaving it empty.
959///
960/// Attributes are walked as well as children: a rendered slot is stored in the
961/// attribute map as that slot's transformed nodes, so a tree can be arbitrarily
962/// deep through attributes alone.
963fn unlink(tag: &mut Tag, pending: &mut Vec<Tag>) {
964    let children = std::mem::take(&mut tag.children);
965    let attributes = std::mem::take(&mut tag.attributes);
966    pending.extend(children.into_iter().filter_map(into_tag));
967    for (_, value) in attributes {
968        pending.extend(value.into_vec().into_iter().filter_map(into_tag));
969    }
970}
971
972/// The tag inside a node, if it is one. Scalars drop where they stand.
973fn into_tag(node: RenderableTreeNode) -> Option<Tag> {
974    match node {
975        RenderableTreeNode::Tag(tag) => Some(*tag),
976        RenderableTreeNode::Scalar(_) => None,
977    }
978}
979
980/// Dropping a scalar is iterative, for the reason dropping a [`Value`] is.
981///
982/// Scalar nesting inside this crate comes from the value grammar, which is
983/// bounded at [`MAX_VALUE_DEPTH`](crate::grammar::MAX_VALUE_DEPTH)
984/// (`DIVERGENCES.md` entry 9): every `Scalar` the crate builds passes through
985/// [`Scalar::from_value`], so no document can produce one deep enough to
986/// overflow a recursive drop.
987///
988/// **That bound does not bind a caller.** [`Scalar::Array`] and
989/// [`Scalar::Object`] are public and recursive, so a host can assemble one of
990/// any depth and a derived drop would abort the process on it. An abort cannot
991/// be caught, so the crate's panic-freedom promise does not survive one. Same
992/// exposure as [`Value`], same guard.
993impl Drop for Scalar {
994    fn drop(&mut self) {
995        let mut pending: Vec<Scalar> = Vec::new();
996        unlink_scalar(self, &mut pending);
997        while let Some(mut scalar) = pending.pop() {
998            unlink_scalar(&mut scalar, &mut pending);
999            // `scalar` is dropped here already emptied, so this recurses once.
1000        }
1001    }
1002}
1003
1004/// Move every scalar directly inside `scalar` onto `pending`, leaving it empty.
1005fn unlink_scalar(scalar: &mut Scalar, pending: &mut Vec<Scalar>) {
1006    match scalar {
1007        Scalar::Array(items) => pending.append(items),
1008        Scalar::Object(entries) => pending.extend(entries.drain(..).map(|(_, value)| value)),
1009        _ => {}
1010    }
1011}
1012
1013/// `Debug` output is observable -- callers assert on it -- so the hand-written
1014/// emitters are pinned against the derive rather than against a reading of it.
1015///
1016/// `mirror` holds structurally identical types that still `#[derive(Debug)]`.
1017/// Rust prints a variant by its own name and a struct by the last segment of
1018/// its path, so a mirror declared in a nested module formats identically to the
1019/// real type as long as the shapes agree. Any drift in the emitters shows up
1020/// here as a string mismatch, in both `{:?}` and `{:#?}`.
1021#[cfg(test)]
1022mod debug_parity {
1023    use super::*;
1024
1025    mod mirror {
1026        // Every field exists to be formatted by the derive and is never read
1027        // otherwise -- that is the whole point of the type.
1028        #![allow(dead_code)]
1029
1030        use indexmap::IndexMap;
1031
1032        #[derive(Debug)]
1033        pub enum Scalar {
1034            Null,
1035            Boolean(bool),
1036            Number(f64),
1037            String(String),
1038            Array(Vec<Scalar>),
1039            Object(IndexMap<String, Scalar>),
1040        }
1041    }
1042
1043    fn to_mirror(scalar: &Scalar) -> mirror::Scalar {
1044        match scalar {
1045            Scalar::Null => mirror::Scalar::Null,
1046            Scalar::Boolean(value) => mirror::Scalar::Boolean(*value),
1047            Scalar::Number(value) => mirror::Scalar::Number(*value),
1048            Scalar::String(value) => mirror::Scalar::String(value.clone()),
1049            Scalar::Array(items) => mirror::Scalar::Array(items.iter().map(to_mirror).collect()),
1050            Scalar::Object(entries) => mirror::Scalar::Object(
1051                entries
1052                    .iter()
1053                    .map(|(key, value)| (key.clone(), to_mirror(value)))
1054                    .collect(),
1055            ),
1056        }
1057    }
1058
1059    fn assert_parity(scalar: &Scalar) {
1060        let reference = to_mirror(scalar);
1061        assert_eq!(
1062            format!("{scalar:?}"),
1063            format!("{reference:?}"),
1064            "plain Debug diverged from the derive"
1065        );
1066        assert_eq!(
1067            format!("{scalar:#?}"),
1068            format!("{reference:#?}"),
1069            "alternate Debug diverged from the derive"
1070        );
1071    }
1072
1073    fn object(pairs: Vec<(&str, Scalar)>) -> Scalar {
1074        Scalar::Object(
1075            pairs
1076                .into_iter()
1077                .map(|(key, value)| (key.to_owned(), value))
1078                .collect(),
1079        )
1080    }
1081
1082    mod tag_mirror {
1083        #![allow(dead_code)]
1084
1085        use indexmap::IndexMap;
1086
1087        #[derive(Debug)]
1088        pub enum RenderableTreeNode {
1089            Tag(Box<Tag>),
1090            Scalar(crate::renderable::Scalar),
1091        }
1092
1093        #[derive(Debug)]
1094        pub enum RenderableTreeNodes {
1095            One(RenderableTreeNode),
1096            Many(Vec<RenderableTreeNode>),
1097        }
1098
1099        #[derive(Debug)]
1100        pub struct Tag {
1101            pub name: String,
1102            pub attributes: IndexMap<String, RenderableTreeNodes>,
1103            pub children: Vec<RenderableTreeNode>,
1104        }
1105    }
1106
1107    fn tag_to_mirror(tag: &Tag) -> tag_mirror::Tag {
1108        tag_mirror::Tag {
1109            name: tag.name.clone(),
1110            attributes: tag
1111                .attributes
1112                .iter()
1113                .map(|(key, nodes)| (key.clone(), nodes_to_mirror(nodes)))
1114                .collect(),
1115            children: tag.children.iter().map(node_to_mirror).collect(),
1116        }
1117    }
1118
1119    fn nodes_to_mirror(nodes: &RenderableTreeNodes) -> tag_mirror::RenderableTreeNodes {
1120        match nodes {
1121            RenderableTreeNodes::One(node) => {
1122                tag_mirror::RenderableTreeNodes::One(node_to_mirror(node))
1123            }
1124            RenderableTreeNodes::Many(many) => {
1125                tag_mirror::RenderableTreeNodes::Many(many.iter().map(node_to_mirror).collect())
1126            }
1127        }
1128    }
1129
1130    fn node_to_mirror(node: &RenderableTreeNode) -> tag_mirror::RenderableTreeNode {
1131        match node {
1132            RenderableTreeNode::Tag(inner) => {
1133                tag_mirror::RenderableTreeNode::Tag(Box::new(tag_to_mirror(inner)))
1134            }
1135            RenderableTreeNode::Scalar(scalar) => {
1136                tag_mirror::RenderableTreeNode::Scalar(scalar.clone())
1137            }
1138        }
1139    }
1140
1141    fn assert_tag_parity(tag: &Tag) {
1142        let reference = tag_to_mirror(tag);
1143        assert_eq!(format!("{tag:?}"), format!("{reference:?}"), "plain Debug");
1144        assert_eq!(
1145            format!("{tag:#?}"),
1146            format!("{reference:#?}"),
1147            "alternate Debug"
1148        );
1149    }
1150
1151    #[test]
1152    fn every_tag_shape_formats_as_the_derive_would() {
1153        let leaf = Tag::new("leaf");
1154
1155        let mut with_scalar_attr = Tag::new("p");
1156        with_scalar_attr.set("k", RenderableTreeNode::text("hi"));
1157
1158        let mut with_tag_attr = Tag::new("slotted");
1159        with_tag_attr.set("slot", RenderableTreeNode::tag(Tag::new("inner")));
1160
1161        let mut many_attr = Tag::new("many");
1162        many_attr.set(
1163            "list",
1164            vec![
1165                RenderableTreeNode::text("a"),
1166                RenderableTreeNode::tag(Tag::new("b")),
1167            ],
1168        );
1169
1170        let mut empty_many = Tag::new("emptymany");
1171        empty_many.set("list", Vec::new());
1172
1173        let nested = Tag::with(
1174            "outer",
1175            IndexMap::new(),
1176            vec![
1177                RenderableTreeNode::tag(Tag::with(
1178                    "middle",
1179                    IndexMap::new(),
1180                    vec![RenderableTreeNode::tag(leaf.clone())],
1181                )),
1182                RenderableTreeNode::Scalar(Scalar::Array(vec![Scalar::Null])),
1183            ],
1184        );
1185
1186        for shape in &[
1187            leaf,
1188            with_scalar_attr,
1189            with_tag_attr,
1190            many_attr,
1191            empty_many,
1192            nested,
1193        ] {
1194            assert_tag_parity(shape);
1195        }
1196    }
1197
1198    #[test]
1199    fn a_deep_tag_survives_all_three_traversals() {
1200        let mut tag = Tag::new("leaf");
1201        for _ in 0..100_000 {
1202            tag = Tag::with("a", IndexMap::new(), vec![RenderableTreeNode::tag(tag)]);
1203        }
1204        let copy = tag.clone();
1205        assert!(copy == tag, "an iterative clone must equal its source");
1206        assert!(format!("{tag:?}").starts_with("Tag { name: \"a\""));
1207    }
1208
1209    #[test]
1210    fn a_tag_deep_through_attributes_survives_all_three() {
1211        // Depth reached with no child at all, which is the shape `Drop` needed
1212        // its own guard for.
1213        let mut tag = Tag::new("leaf");
1214        for _ in 0..100_000 {
1215            let mut outer = Tag::new("a");
1216            outer.set("slot", RenderableTreeNode::tag(tag));
1217            tag = outer;
1218        }
1219        let copy = tag.clone();
1220        assert_eq!(copy, tag);
1221    }
1222
1223    #[test]
1224    fn tag_equality_distinguishes_one_from_many() {
1225        // The derive compares variants, so `One(x)` never equals `Many([x])`.
1226        let mut one = Tag::new("t");
1227        one.set("k", RenderableTreeNode::text("x"));
1228        let mut many = Tag::new("t");
1229        many.set("k", vec![RenderableTreeNode::text("x")]);
1230        assert_ne!(one, many);
1231    }
1232
1233    #[test]
1234    fn every_scalar_shape_formats_as_the_derive_would() {
1235        let shapes = vec![
1236            Scalar::Null,
1237            Scalar::Boolean(true),
1238            Scalar::Boolean(false),
1239            Scalar::Number(1.0),
1240            Scalar::Number(-0.5),
1241            Scalar::String("hi".to_owned()),
1242            // Escaping is the string's own Debug, not ours -- pinned anyway.
1243            Scalar::String("a \"quote\" and a \\ and a \n".to_owned()),
1244            Scalar::Array(Vec::new()),
1245            object(Vec::new()),
1246            Scalar::Array(vec![Scalar::Null]),
1247            Scalar::Array(vec![Scalar::Null, Scalar::Boolean(true)]),
1248            object(vec![("a", Scalar::Null)]),
1249            object(vec![("a", Scalar::Null), ("b", Scalar::Number(2.0))]),
1250            // Nesting through both composites, and an empty one inside a full.
1251            Scalar::Array(vec![
1252                Scalar::Array(vec![Scalar::Number(1.0)]),
1253                object(vec![("k", Scalar::Array(Vec::new()))]),
1254                Scalar::String("x".to_owned()),
1255            ]),
1256            object(vec![(
1257                "outer",
1258                object(vec![("inner", Scalar::Array(vec![Scalar::Null]))]),
1259            )]),
1260        ];
1261        for shape in &shapes {
1262            assert_parity(shape);
1263        }
1264    }
1265
1266    #[test]
1267    fn a_deep_scalar_formats_without_aborting() {
1268        // The reason the emitter exists. The mirror is not built here: a
1269        // recursive `to_mirror` would overflow before the assertion could run,
1270        // which is the defect restated.
1271        let mut scalar = Scalar::Null;
1272        for _ in 0..100_000 {
1273            scalar = Scalar::Array(vec![scalar]);
1274        }
1275        let rendered = format!("{scalar:?}");
1276        assert!(rendered.starts_with("Array([Array("));
1277        assert!(rendered.ends_with(")])"));
1278    }
1279
1280    #[test]
1281    fn a_deep_scalar_clones_and_compares_without_aborting() {
1282        let mut scalar = Scalar::Null;
1283        for _ in 0..100_000 {
1284            scalar = Scalar::Array(vec![scalar]);
1285        }
1286        let copy = scalar.clone();
1287        assert!(copy == scalar, "an iterative clone must equal its source");
1288    }
1289
1290    #[test]
1291    fn cloning_preserves_order_and_shape() {
1292        let original = object(vec![
1293            ("z", Scalar::Array(vec![Scalar::Number(1.0), Scalar::Null])),
1294            ("a", Scalar::String("x".to_owned())),
1295        ]);
1296        let copy = original.clone();
1297        assert_eq!(format!("{copy:?}"), format!("{original:?}"));
1298        let Scalar::Object(entries) = &copy else {
1299            panic!("expected an object")
1300        };
1301        assert_eq!(entries.keys().collect::<Vec<_>>(), ["z", "a"]);
1302    }
1303
1304    #[test]
1305    fn equality_ignores_object_order_as_indexmap_does() {
1306        // `IndexMap::eq` compares as an unordered collection. The hand-written
1307        // `PartialEq` has to keep saying that, so this pins it.
1308        let left = object(vec![("a", Scalar::Null), ("b", Scalar::Number(1.0))]);
1309        let right = object(vec![("b", Scalar::Number(1.0)), ("a", Scalar::Null)]);
1310        assert_eq!(left, right);
1311
1312        let different = object(vec![("a", Scalar::Null), ("b", Scalar::Number(2.0))]);
1313        assert_ne!(left, different);
1314        assert_ne!(left, object(vec![("a", Scalar::Null)]));
1315    }
1316}
1317
1318#[cfg(test)]
1319mod tests {
1320    use super::*;
1321
1322    #[test]
1323    fn the_default_element_is_a_div() {
1324        assert_eq!(Tag::default().name, "div");
1325    }
1326
1327    #[test]
1328    fn an_attribute_may_hold_a_rendered_subtree() {
1329        // The shape the corpus's "Basic slot" case expects: a slot transformed
1330        // into a list of nodes, stored under the slot's name.
1331        let mut foo = Tag::new("foo");
1332        let paragraph = Tag::with("p", IndexMap::new(), vec![RenderableTreeNode::text("hi")]);
1333        foo.set("bar", vec![RenderableTreeNode::tag(paragraph)]);
1334        assert!(matches!(
1335            foo.attributes.get("bar"),
1336            Some(RenderableTreeNodes::Many(nodes)) if nodes.len() == 1
1337        ));
1338    }
1339
1340    #[test]
1341    fn attribute_order_is_authored_order() {
1342        let mut tag = Tag::new("foo");
1343        tag.set("z", RenderableTreeNode::text("1"));
1344        tag.set("a", RenderableTreeNode::text("2"));
1345        let keys: Vec<&str> = tag.attributes.keys().map(String::as_str).collect();
1346        assert_eq!(keys, ["z", "a"]);
1347    }
1348
1349    #[test]
1350    fn scalars_come_from_resolved_values_only() {
1351        use crate::ast::Variable;
1352
1353        assert_eq!(
1354            Scalar::from_value(&Value::String("x".into())),
1355            Some(Scalar::String("x".into()))
1356        );
1357        assert_eq!(
1358            Scalar::from_value(&Value::Array(vec![Value::Number(1.0)])),
1359            Some(Scalar::Array(vec![Scalar::Number(1.0)]))
1360        );
1361        // Unresolved references have no scalar spelling.
1362        assert_eq!(
1363            Scalar::from_value(&Value::Variable(Variable::default())),
1364            None
1365        );
1366        // ... and neither does a collection containing one.
1367        assert_eq!(
1368            Scalar::from_value(&Value::Array(vec![Value::Variable(Variable::default())])),
1369            None
1370        );
1371    }
1372
1373    #[test]
1374    fn dropping_a_deep_tree_does_not_abort() {
1375        // Nesting depth is attacker-controlled: `{% a %}` repeated is one tag
1376        // per line, and every one of them becomes a tag here. A derived
1377        // recursive drop aborts the process on this, which no caller can catch.
1378        let mut tag = Tag::new("leaf");
1379        for _ in 0..100_000 {
1380            tag = Tag::with("a", IndexMap::new(), vec![RenderableTreeNode::tag(tag)]);
1381        }
1382        drop(tag);
1383    }
1384
1385    #[test]
1386    fn dropping_a_tree_nested_through_attributes_does_not_abort() {
1387        // A rendered slot lands in the attribute map, so depth can be reached
1388        // without a single child. Guarding only `children` would leave this.
1389        let mut tag = Tag::new("leaf");
1390        for _ in 0..100_000 {
1391            let mut outer = Tag::new("a");
1392            outer.set("slot", RenderableTreeNode::tag(tag));
1393            tag = outer;
1394        }
1395        drop(tag);
1396    }
1397
1398    #[test]
1399    fn dropping_a_deep_scalar_array_does_not_abort() {
1400        // The crate never builds one this deep -- the value grammar is bounded
1401        // at MAX_VALUE_DEPTH -- but `Scalar` is public and `Array` is
1402        // recursive, so a host can. A derived drop aborts here, and an abort is
1403        // not catchable, so the panic-freedom promise would not survive it.
1404        let mut scalar = Scalar::Null;
1405        for _ in 0..100_000 {
1406            scalar = Scalar::Array(vec![scalar]);
1407        }
1408        drop(scalar);
1409    }
1410
1411    #[test]
1412    fn dropping_a_deep_scalar_object_does_not_abort() {
1413        // The other recursive variant, which a guard over `Array` alone leaves.
1414        let mut scalar = Scalar::Null;
1415        for _ in 0..100_000 {
1416            let mut object = IndexMap::new();
1417            object.insert("k".to_string(), scalar);
1418            scalar = Scalar::Object(object);
1419        }
1420        drop(scalar);
1421    }
1422
1423    #[test]
1424    fn many_and_one_flatten_the_same_way() {
1425        let one = RenderableTreeNodes::One(RenderableTreeNode::text("a"));
1426        assert_eq!(one.into_vec().len(), 1);
1427        let many = RenderableTreeNodes::Many(vec![
1428            RenderableTreeNode::text("a"),
1429            RenderableTreeNode::text("b"),
1430        ]);
1431        assert_eq!(many.into_vec().len(), 2);
1432    }
1433}