Skip to main content

fml_engine/
tree.rs

1//! The values a map works on: source and target content as one tree of typed nodes.
2//!
3//! Variables point at nodes, so a target created by one rule and filled by the next is the same
4//! node. Each node knows its FHIR type where the definitions tell it, which the engine needs for
5//! type filters (`src.value : string`), choice elements (`value` holding a Reference is written as
6//! `valueReference`), repeating elements, and default groups chosen by type.
7
8use fml_check::{Lookup, Type, Types};
9
10/// The property of a value in JSON that names its type, for elements of logical models whose
11/// values say their type (`xsi:type` in XML).
12pub const TYPE_KEY: &str = "@type";
13use serde_json::{Map, Value};
14
15/// A node in the [`Tree`].
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct NodeId(usize);
18
19#[derive(Debug, Clone)]
20struct Node {
21    ty: Type,
22    /// The FHIR type when no definition is known for it (a FHIRPath result, for example).
23    type_name: Option<String>,
24    /// A resource: written with `resourceType`.
25    resource: bool,
26    data: Data,
27}
28
29#[derive(Debug, Clone)]
30enum Data {
31    /// A primitive value (`Null` until it is assigned), and its element's `id` and `extension`,
32    /// which FHIR JSON writes as the `_name` sibling.
33    Primitive(Value, Vec<Property>),
34    Complex(Vec<Property>),
35}
36
37#[derive(Debug, Clone)]
38struct Property {
39    /// The element name; for a choice, without the type suffix (`value`).
40    name: String,
41    choice: bool,
42    /// A logical model's element with several types and no `[x]`: written under its name, each
43    /// value naming its type in `@type`.
44    typed: bool,
45    /// The element's own type, when it has one: a value of another type (a subtype) names it.
46    declared: Option<String>,
47    many: bool,
48    /// Position in the definition, for writing elements in order.
49    index: Option<usize>,
50    values: Vec<NodeId>,
51}
52
53/// An element of a node, as the definitions describe it.
54struct Slot {
55    name: String,
56    choice: bool,
57    typed: bool,
58    many: bool,
59    index: Option<usize>,
60    /// The element's type when it has a single one.
61    ty: Type,
62}
63
64#[derive(Debug, Default, Clone)]
65pub struct Tree {
66    nodes: Vec<Node>,
67    /// Where each node of the input is in it, as a FHIRPath (`Patient.name[0].given[1]`); only
68    /// when [`record_paths`](Self::record_paths) was called.
69    paths: std::collections::HashMap<NodeId, String>,
70}
71
72impl Tree {
73    /// Records the location of every node under `root` (the input), starting with `root_name`
74    /// (`Patient`): repeating elements get an index, choices their base name (`value`).
75    pub fn record_paths(&mut self, root: NodeId, root_name: &str) {
76        let mut pending = vec![(root, root_name.to_owned())];
77        while let Some((node, path)) = pending.pop() {
78            for property in self.properties(node) {
79                for (index, &child) in property.values.iter().enumerate() {
80                    let child_path = if property.many {
81                        format!("{path}.{}[{index}]", property.name)
82                    } else {
83                        format!("{path}.{}", property.name)
84                    };
85                    pending.push((child, child_path));
86                }
87            }
88            self.paths.insert(node, path);
89        }
90    }
91
92    /// The location of an input node, if [`record_paths`](Self::record_paths) saw it.
93    pub fn path(&self, id: NodeId) -> Option<&str> {
94        self.paths.get(&id).map(String::as_str)
95    }
96
97    /// The number of nodes created.
98    pub fn len(&self) -> usize {
99        self.nodes.len()
100    }
101
102    fn push(&mut self, node: Node) -> NodeId {
103        self.nodes.push(node);
104        NodeId(self.nodes.len() - 1)
105    }
106
107    /// An empty node of a type: complex, or primitive when the type is a primitive.
108    pub fn create(&mut self, ty: Type, type_name: Option<String>) -> NodeId {
109        let data = if ty.is_primitive() {
110            Data::Primitive(Value::Null, Vec::new())
111        } else {
112            Data::Complex(Vec::new())
113        };
114        self.push(Node {
115            resource: ty.is_resource(),
116            ty,
117            type_name,
118            data,
119        })
120    }
121
122    /// A primitive node holding `value`.
123    pub fn primitive(&mut self, value: Value, ty: Type, type_name: Option<String>) -> NodeId {
124        self.push(Node {
125            ty,
126            type_name,
127            resource: false,
128            data: Data::Primitive(value, Vec::new()),
129        })
130    }
131
132    pub fn fhir_type(&self, id: NodeId) -> Option<String> {
133        let node = &self.nodes[id.0];
134        node.ty.fhir_type().or_else(|| node.type_name.clone())
135    }
136
137    pub fn ty(&self, id: NodeId) -> &Type {
138        &self.nodes[id.0].ty
139    }
140
141    pub fn is_primitive(&self, id: NodeId) -> bool {
142        matches!(self.nodes[id.0].data, Data::Primitive(..))
143    }
144
145    /// The primitive value of a node, `None` for complex nodes.
146    pub fn value(&self, id: NodeId) -> Option<&Value> {
147        match &self.nodes[id.0].data {
148            Data::Primitive(value, _) => Some(value),
149            Data::Complex(_) => None,
150        }
151    }
152
153    /// The primitive value as text, as FHIRPath's string conversion gives it.
154    pub fn text(&self, id: NodeId) -> Option<String> {
155        match self.value(id)? {
156            Value::String(s) => Some(s.clone()),
157            Value::Null => None,
158            other => Some(other.to_string()),
159        }
160    }
161
162    // ---- Loading ----------------------------------------------------------------------------
163
164    /// Loads JSON as a node of type `ty`; nested elements get the types the definitions give.
165    pub fn load(&mut self, json: &Value, ty: Type, types: &Types<'_>) -> NodeId {
166        // `resourceType` marks a resource, unless the type has an element of that name
167        // (ExampleScenario.instance.resourceType names the type of a resource it talks about).
168        let marks_resource = json.get("resourceType").is_some()
169            && !matches!(types.lookup(&ty, "resourceType"), Lookup::Found(_));
170        if !marks_resource && json.is_object() {
171            return self.load_object(json, ty, None, types);
172        }
173        let ty = match (&ty, json.get("resourceType").and_then(Value::as_str)) {
174            // A resource in a `Resource`-typed element, or a top-level input: its own type.
175            (_, Some(resource_type)) if ty.fhir_type().as_deref() != Some(resource_type) => {
176                let context = ty
177                    .structure()
178                    .map(|s| s.url.clone())
179                    .unwrap_or_else(|| "http://hl7.org/fhir/StructureDefinition/".to_owned());
180                match types.structure(&fml_check::definitions::type_url(resource_type, &context)) {
181                    Type::Unknown => ty,
182                    known => known,
183                }
184            }
185            _ => ty,
186        };
187        let type_name = json
188            .get("resourceType")
189            .and_then(Value::as_str)
190            .map(str::to_owned);
191        if !json.is_object() {
192            return self.primitive(json.clone(), ty, type_name);
193        }
194        self.load_object(json, ty, type_name, types)
195    }
196
197    /// Loads a JSON object; `resource_type` is set when it is a resource.
198    fn load_object(
199        &mut self,
200        json: &Value,
201        ty: Type,
202        resource_type: Option<String>,
203        types: &Types<'_>,
204    ) -> NodeId {
205        let Value::Object(object) = json else {
206            return self.primitive(json.clone(), ty, None);
207        };
208        let is_resource = resource_type.is_some();
209        let id = self.push(Node {
210            resource: is_resource || ty.is_resource(),
211            ty: ty.clone(),
212            type_name: resource_type,
213            data: Data::Complex(Vec::new()),
214        });
215        for (key, value) in object {
216            if key == TYPE_KEY {
217                continue;
218            }
219            if (is_resource && key == "resourceType")
220                || (key.starts_with('_') && object.contains_key(&key[1..]))
221            {
222                continue;
223            }
224            // `_name` without `name`: a primitive that has only an id or extensions.
225            if let Some(base) = key.strip_prefix('_') {
226                let (slot, child_ty, _) = self.slot_for_key(&ty, base, types);
227                let elements: Vec<&Value> = match value {
228                    Value::Array(items) => items.iter().collect(),
229                    single => vec![single],
230                };
231                let children: Vec<NodeId> = elements
232                    .into_iter()
233                    .map(|element| {
234                        let child = self.primitive(Value::Null, child_ty.clone(), None);
235                        self.load_primitive_element(child, element, types);
236                        child
237                    })
238                    .collect();
239                let many = slot.many || value.is_array();
240                self.properties_mut(id).push(Property {
241                    name: slot.name,
242                    choice: slot.choice,
243                    typed: slot.typed,
244                    declared: declared_key(&slot.ty),
245                    many,
246                    index: slot.index,
247                    values: children,
248                });
249                continue;
250            }
251            let (slot, child_ty, element) = self.slot_for_key(&ty, key, types);
252            let values: Vec<&Value> = match value {
253                Value::Array(items) => items.iter().collect(),
254                single => vec![single],
255            };
256            let mut children = Vec::with_capacity(values.len());
257            for item in values {
258                // A value that names its type (`@type`) has that type.
259                let item_ty = match (item.get(TYPE_KEY).and_then(Value::as_str), &element) {
260                    (Some(name), Some(element)) => match types.named_type(element, name) {
261                        Type::Unknown => child_ty.clone(),
262                        named => named,
263                    },
264                    _ => child_ty.clone(),
265                };
266                children.push(self.load(item, item_ty, types));
267            }
268            // The primitives' `id` and `extension`, from the `_name` sibling.
269            if let Some(elements) = object.get(&format!("_{key}")) {
270                let elements: Vec<&Value> = match elements {
271                    Value::Array(items) => items.iter().collect(),
272                    single => vec![single],
273                };
274                for (&child, element) in children.iter().zip(elements) {
275                    self.load_primitive_element(child, element, types);
276                }
277            }
278            let many = slot.many || value.is_array();
279            self.properties_mut(id).push(Property {
280                name: slot.name,
281                choice: slot.choice,
282                typed: slot.typed,
283                declared: declared_key(&slot.ty),
284                many,
285                index: slot.index,
286                values: children,
287            });
288        }
289        id
290    }
291
292    /// Loads a primitive's `_name` object (`id`, `extension`) into the primitive node.
293    fn load_primitive_element(&mut self, primitive: NodeId, element: &Value, types: &Types<'_>) {
294        if !element.is_object() || !self.is_primitive(primitive) {
295            return;
296        }
297        let holder = self.load(element, self.ty(primitive).clone(), types);
298        let properties = std::mem::take(self.properties_mut(holder));
299        *self.properties_mut(primitive) = properties;
300    }
301
302    /// The element a JSON key holds: `valueString` is the choice `value` with type `string`.
303    fn slot_for_key(
304        &self,
305        ty: &Type,
306        key: &str,
307        types: &Types<'_>,
308    ) -> (Slot, Type, Option<fml_check::ElementRef>) {
309        if let Lookup::Found(found) = types.lookup(ty, key) {
310            if let Some(base) = &found.element.choice_of {
311                let base_slot = match types.lookup(ty, base) {
312                    Lookup::Found(base_found) => element_order(ty, &base_found, types),
313                    _ => element_order(ty, &found, types),
314                };
315                let child = types.element_type(&found, None);
316                return (
317                    Slot {
318                        name: base.clone(),
319                        choice: true,
320                        typed: false,
321                        many: false,
322                        index: base_slot,
323                        ty: child.clone(),
324                    },
325                    child,
326                    Some(*found),
327                );
328            }
329            let child = types.element_type(&found, None);
330            return (
331                Slot {
332                    name: key.to_owned(),
333                    choice: !found.element.choices.is_empty(),
334                    typed: found.element.is_polymorphic(),
335                    many: found.element.array,
336                    index: element_order(ty, &found, types),
337                    ty: child.clone(),
338                },
339                child,
340                Some(*found),
341            );
342        }
343        (
344            Slot {
345                name: key.to_owned(),
346                choice: false,
347                typed: false,
348                many: false,
349                index: None,
350                ty: Type::Unknown,
351            },
352            Type::Unknown,
353            None,
354        )
355    }
356
357    // ---- Navigation -------------------------------------------------------------------------
358
359    /// The elements of a node; for a primitive, those of its element (`id`, `extension`).
360    fn properties(&self, id: NodeId) -> &[Property] {
361        match &self.nodes[id.0].data {
362            Data::Complex(properties) | Data::Primitive(_, properties) => properties,
363        }
364    }
365
366    fn properties_mut(&mut self, id: NodeId) -> &mut Vec<Property> {
367        match &mut self.nodes[id.0].data {
368            Data::Complex(properties) | Data::Primitive(_, properties) => properties,
369        }
370    }
371
372    /// The children named `name`. A choice is found by its name (`value`) or by the name with
373    /// the type (`valueString`). A primitive's `value` is the primitive itself.
374    pub fn children(&self, id: NodeId, name: &str) -> Vec<NodeId> {
375        if name == "value" && self.is_primitive(id) {
376            return vec![id];
377        }
378        let mut out = Vec::new();
379        for property in self.properties(id) {
380            if property.name == name {
381                out.extend(&property.values);
382            } else if property.choice && name.starts_with(&property.name) {
383                for &value in &property.values {
384                    let suffix = &name[property.name.len()..];
385                    if self
386                        .fhir_type(value)
387                        .is_some_and(|t| capitalize(&t) == suffix)
388                    {
389                        out.push(value);
390                    }
391                }
392            }
393        }
394        out
395    }
396
397    // ---- Writing ----------------------------------------------------------------------------
398
399    /// The element `name` of `id`, as the definitions describe it.
400    fn slot(&self, id: NodeId, name: &str, types: &Types<'_>) -> Result<Slot, String> {
401        let ty = self.ty(id).clone();
402        match types.lookup(&ty, name) {
403            Lookup::Found(found) => Ok(Slot {
404                name: name.to_owned(),
405                choice: !found.element.choices.is_empty(),
406                typed: found.element.is_polymorphic(),
407                many: found.element.array,
408                index: element_order(&ty, &found, types),
409                ty: types.element_type(&found, None),
410            }),
411            Lookup::Missing => Err(format!("`{}` has no element `{name}`", ty.display())),
412            Lookup::Unknown => {
413                // No definition: keep what is there. An element already holding several values
414                // repeats.
415                let existing = self.properties(id).iter().find(|p| p.name == name);
416                Ok(Slot {
417                    name: name.to_owned(),
418                    choice: false,
419                    typed: false,
420                    many: existing.is_some_and(|p| p.many),
421                    index: None,
422                    ty: Type::Unknown,
423                })
424            }
425        }
426    }
427
428    /// The target element `name` of `id` to write into: a new value for a repeating element, the
429    /// existing (or a new) value for a single one.
430    pub fn make_property(
431        &mut self,
432        id: NodeId,
433        name: &str,
434        types: &Types<'_>,
435    ) -> Result<NodeId, String> {
436        if self.is_primitive(id) && name == "value" {
437            return Ok(id);
438        }
439        let slot = self.slot(id, name, types)?;
440        if slot.choice || slot.typed {
441            return Err(format!(
442                "`{}.{name}` can have several types; give the type with a transform such as create('Type')",
443                self.ty(id).display()
444            ));
445        }
446        if !slot.many
447            && let Some(existing) = self
448                .properties(id)
449                .iter()
450                .find(|p| p.name == name)
451                .and_then(|p| p.values.first().copied())
452        {
453            return Ok(existing);
454        }
455        let child = self.create(slot.ty.clone(), None);
456        self.attach(id, slot, child);
457        Ok(child)
458    }
459
460    /// Sets the element `name` of `id` to `value`: appended to a repeating element, replacing a
461    /// single one. A primitive value takes the element's type.
462    pub fn set_property(
463        &mut self,
464        id: NodeId,
465        name: &str,
466        value: NodeId,
467        types: &Types<'_>,
468    ) -> Result<(), String> {
469        // A primitive's `value` is the primitive itself.
470        if self.is_primitive(id) && name == "value" {
471            let assigned = self.value(value).cloned().unwrap_or(Value::Null);
472            if let Data::Primitive(current, _) = &mut self.nodes[id.0].data {
473                *current = assigned;
474            }
475            return Ok(());
476        }
477        let slot = self.slot(id, name, types)?;
478        if (slot.choice || slot.typed) && self.fhir_type(value).is_none() {
479            return Err(format!(
480                "cannot tell which type of `{}.{name}` the value has",
481                self.ty(id).display()
482            ));
483        }
484        if !slot.choice && self.is_primitive(value) && !matches!(slot.ty, Type::Unknown) {
485            self.nodes[value.0].ty = slot.ty.clone();
486        }
487        if !slot.many {
488            self.properties_mut(id).retain(|p| p.name != name);
489        }
490        self.attach(id, slot, value);
491        Ok(())
492    }
493
494    fn attach(&mut self, id: NodeId, slot: Slot, child: NodeId) {
495        let properties = self.properties_mut(id);
496        match properties.iter_mut().find(|p| p.name == slot.name) {
497            Some(property) => property.values.push(child),
498            None => properties.push(Property {
499                declared: declared_key(&slot.ty),
500                name: slot.name,
501                choice: slot.choice,
502                typed: slot.typed,
503                many: slot.many,
504                index: slot.index,
505                values: vec![child],
506            }),
507        }
508    }
509
510    /// Moves `value` within the repeating element `name` of `id`: to `index`, or to the end.
511    pub fn move_value(&mut self, id: NodeId, name: &str, value: NodeId, index: Option<usize>) {
512        let Some(property) = self
513            .properties_mut(id)
514            .iter_mut()
515            .find(|p| p.name == name && p.many)
516        else {
517            return;
518        };
519        let Some(at) = property.values.iter().position(|&v| v == value) else {
520            return;
521        };
522        property.values.remove(at);
523        let index = index.map_or(property.values.len(), |i| i.min(property.values.len()));
524        property.values.insert(index, value);
525    }
526
527    /// Copies the content of `from` into `to`, as when a value is assigned to an existing target.
528    pub fn assign(&mut self, to: NodeId, from: NodeId) {
529        let copy = self.deep_copy(from);
530        let node = self.nodes[copy.0].clone();
531        let target = &mut self.nodes[to.0];
532        target.data = node.data;
533        if matches!(target.ty, Type::Unknown) {
534            target.ty = node.ty;
535            target.type_name = node.type_name;
536        }
537    }
538
539    /// A copy of the subtree at `id`.
540    pub fn deep_copy(&mut self, id: NodeId) -> NodeId {
541        let node = self.nodes[id.0].clone();
542        let copy_properties = |properties: Vec<Property>, tree: &mut Self| -> Vec<Property> {
543            properties
544                .into_iter()
545                .map(|property| Property {
546                    values: property
547                        .values
548                        .iter()
549                        .map(|&value| tree.deep_copy(value))
550                        .collect(),
551                    ..property
552                })
553                .collect()
554        };
555        let data = match node.data {
556            Data::Primitive(value, properties) => {
557                Data::Primitive(value, copy_properties(properties, self))
558            }
559            Data::Complex(properties) => Data::Complex(copy_properties(properties, self)),
560        };
561        self.push(Node { data, ..node })
562    }
563
564    /// Sets a primitive child, such as a resource's `id`.
565    pub fn set_text(
566        &mut self,
567        id: NodeId,
568        name: &str,
569        text: &str,
570        types: &Types<'_>,
571    ) -> Result<(), String> {
572        let value = self.primitive(
573            Value::String(text.to_owned()),
574            Type::Unknown,
575            Some("string".to_owned()),
576        );
577        self.set_property(id, name, value, types)
578    }
579
580    /// The nodes reachable from `root`, `root` included.
581    pub fn reachable(&self, root: NodeId) -> std::collections::HashSet<NodeId> {
582        let mut seen = std::collections::HashSet::new();
583        let mut stack = vec![root];
584        while let Some(node) = stack.pop() {
585            if seen.insert(node) {
586                for property in self.properties(node) {
587                    stack.extend(&property.values);
588                }
589            }
590        }
591        seen
592    }
593
594    // ---- Output -----------------------------------------------------------------------------
595
596    /// The node as FHIR JSON, elements in definition order.
597    pub fn to_json(&self, id: NodeId) -> Value {
598        let node = &self.nodes[id.0];
599        match &node.data {
600            Data::Primitive(value, _) => value.clone(),
601            Data::Complex(properties) => {
602                let mut object = Map::new();
603                if (node.resource || node.ty.is_resource())
604                    && let Some(name) = self.fhir_type(id)
605                {
606                    object.insert("resourceType".to_owned(), Value::String(name));
607                }
608                self.write_properties(properties, &mut object);
609                Value::Object(object)
610            }
611        }
612    }
613
614    /// Puts `@type` first in the JSON of a value whose type the element does not give: any value
615    /// of a polymorphic element, or a value of a type other than the element's own.
616    fn name_type(&self, property: &Property, value: NodeId, json: &mut Value) {
617        let node = &self.nodes[value.0];
618        if node.resource {
619            return;
620        }
621        let Value::Object(object) = json else {
622            return;
623        };
624        let differs = property.declared.as_ref().is_some_and(|declared| {
625            !matches!(node.ty, Type::Unknown) && *declared != node.ty.key()
626        });
627        if !(property.typed || differs) {
628            return;
629        }
630        let Some(name) = type_label(&node.ty).or_else(|| node.type_name.clone()) else {
631            return;
632        };
633        let mut named = Map::with_capacity(object.len() + 1);
634        named.insert(TYPE_KEY.to_owned(), Value::String(name));
635        named.extend(std::mem::take(object));
636        *object = named;
637    }
638
639    /// A primitive's `id` and `extension` as the `_name` object, if it has any.
640    fn primitive_element(&self, id: NodeId) -> Option<Value> {
641        match &self.nodes[id.0].data {
642            Data::Primitive(_, properties) if !properties.is_empty() => {
643                let mut object = Map::new();
644                self.write_properties(properties, &mut object);
645                (!object.is_empty()).then_some(Value::Object(object))
646            }
647            _ => None,
648        }
649    }
650
651    fn write_properties(&self, properties: &[Property], object: &mut Map<String, Value>) {
652        let mut ordered: Vec<&Property> = properties.iter().collect();
653        ordered.sort_by_key(|p| p.index.unwrap_or(usize::MAX));
654        for property in ordered {
655            let values: Vec<(NodeId, Value, Option<Value>)> = property
656                .values
657                .iter()
658                .map(|&value| {
659                    let mut json = self.to_json(value);
660                    if !property.choice {
661                        self.name_type(property, value, &mut json);
662                    }
663                    (value, json, self.primitive_element(value))
664                })
665                .filter(|(_, json, element)| !is_empty(json) || element.is_some())
666                .collect();
667            if values.is_empty() {
668                continue;
669            }
670            let mut put = |key: String, json: Value, element: Option<Value>| {
671                if !json.is_null() {
672                    object.insert(key.clone(), json);
673                }
674                if let Some(element) = element {
675                    object.insert(format!("_{key}"), element);
676                }
677            };
678            if property.choice {
679                for (value, json, element) in values {
680                    let suffix = self
681                        .fhir_type(value)
682                        .map(|t| capitalize(&t))
683                        .unwrap_or_default();
684                    put(format!("{}{suffix}", property.name), json, element);
685                }
686            } else if property.many {
687                let any_element = values.iter().any(|(_, _, e)| e.is_some());
688                let any_value = values.iter().any(|(_, json, _)| !json.is_null());
689                let (jsons, elements): (Vec<Value>, Vec<Value>) = values
690                    .into_iter()
691                    .map(|(_, json, element)| (json, element.unwrap_or(Value::Null)))
692                    .unzip();
693                if any_value {
694                    object.insert(property.name.clone(), Value::Array(jsons));
695                }
696                if any_element {
697                    object.insert(format!("_{}", property.name), Value::Array(elements));
698                }
699            } else {
700                let (_, json, element) = values.into_iter().last().expect("not empty");
701                put(property.name.clone(), json, element);
702            }
703        }
704    }
705}
706
707/// Where an element goes when a node is written: elements a base structure defines come before
708/// the structure's own (`Resource.meta` before `Patient.identifier`), each in definition order.
709fn element_order(ty: &Type, found: &fml_check::ElementRef, types: &Types<'_>) -> Option<usize> {
710    let index = found.element.index?;
711    let Type::Node { structure, path } = ty else {
712        return Some(index);
713    };
714    if !path.is_empty() {
715        return Some(index);
716    }
717    // How far up the base chain the element's owner is.
718    let mut depth = 0;
719    let mut current = Some(structure.clone());
720    while let Some(structure) = current {
721        if structure.url == found.owner.url || depth >= 32 {
722            break;
723        }
724        depth += 1;
725        current = structure
726            .base
727            .as_deref()
728            .and_then(|base| types.definitions.structure(base));
729    }
730    Some((64 - depth.min(63)) * 1_000_000 + index)
731}
732
733/// The key of a slot's type, when it is known.
734fn declared_key(ty: &Type) -> Option<String> {
735    match ty {
736        Type::Unknown => None,
737        known => Some(known.key()),
738    }
739}
740
741/// Values left out of the output: unassigned primitives and elements with no content.
742fn is_empty(json: &Value) -> bool {
743    match json {
744        Value::Null => true,
745        Value::Object(map) => map.is_empty(),
746        Value::Array(items) => items.is_empty(),
747        _ => false,
748    }
749}
750
751/// How `@type` names a type: a logical model's types by structure name (`IVL_TS`), others by
752/// type name. Backbone elements have no name of their own.
753fn type_label(ty: &Type) -> Option<String> {
754    match ty {
755        Type::Node { structure, path } if path.is_empty() => {
756            Some(if structure.kind == fml_check::Kind::Logical {
757                structure.name.clone()
758            } else {
759                structure.type_name.clone()
760            })
761        }
762        _ => None,
763    }
764}
765
766pub(crate) fn capitalize(text: &str) -> String {
767    let mut chars = text.chars();
768    match chars.next() {
769        Some(first) => first.to_uppercase().chain(chars).collect(),
770        None => String::new(),
771    }
772}