Skip to main content

sim_lib_scene/
build.rs

1//! Scene construction helpers.
2//!
3//! Re-exports the `sim-value` builders and adds a few common scene node shapes
4//! plus a reserved-key guard. The guard turns the `kind` footgun into an
5//! immediate, clear failure: `kind` is the scene-node tag, so a plain data map
6//! must not carry a `kind` key (use a different field name). [`data_map`]
7//! debug-asserts that.
8
9use sim_kernel::Expr;
10
11pub use sim_value::build::{float, int, list, map, sym, text, vector};
12
13use crate::model::node;
14
15/// Keys reserved for scene-node structure; plain data maps must not use them.
16pub const RESERVED_DATA_KEYS: &[&str] = &["kind"];
17
18/// A `scene/stack` node with a direction and children.
19pub fn stack(dir: &str, children: Vec<Expr>) -> Expr {
20    node(
21        "stack",
22        vec![("dir", sym(dir)), ("children", list(children))],
23    )
24}
25
26/// A `scene/box` node with a role and children.
27pub fn box_(role: &str, children: Vec<Expr>) -> Expr {
28    node(
29        "box",
30        vec![("role", sym(role)), ("children", list(children))],
31    )
32}
33
34/// A `scene/badge` node. Status carries a text token, never color alone.
35pub fn badge(status: &str, label: &str) -> Expr {
36    node(
37        "badge",
38        vec![("status", sym(status)), ("label", text(label))],
39    )
40}
41
42/// A `scene/badge-cluster` node containing visible status badges.
43pub fn badge_cluster(badges: Vec<Expr>) -> Expr {
44    node("badge-cluster", vec![("badges", list(badges))])
45}
46
47/// A `scene/text` node.
48pub fn text_node(content: impl Into<String>) -> Expr {
49    node("text", vec![("text", text(content.into()))])
50}
51
52/// Build a plain data map, asserting (in debug) that it carries no reserved
53/// scene-node key.
54pub fn data_map(entries: Vec<(&str, Expr)>) -> Expr {
55    debug_assert!(
56        entries
57            .iter()
58            .all(|(key, _)| !RESERVED_DATA_KEYS.contains(key)),
59        "data_map: a plain data map must not use a reserved scene-node key (e.g. 'kind'); \
60         rename the field"
61    );
62    map(entries)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn scene_shape_helpers_validate() {
71        let scene = stack(
72            "column",
73            vec![box_(
74                "summary",
75                vec![text_node("hi"), badge_cluster(vec![badge("ok", "done")])],
76            )],
77        );
78        crate::model::validate_scene(&scene).expect("helper scenes validate");
79    }
80
81    #[test]
82    fn data_map_allows_non_reserved_keys() {
83        let value = data_map(vec![("style", sym("line")), ("at", int(3))]);
84        assert!(matches!(value, Expr::Map(_)));
85    }
86
87    #[test]
88    #[should_panic(expected = "reserved scene-node key")]
89    fn data_map_rejects_a_reserved_key_in_debug() {
90        let _ = data_map(vec![("kind", sym("line"))]);
91    }
92}