Skip to main content

fml_engine/
engine.rs

1//! Running a StructureMap.
2//!
3//! Follows the HL7 reference engine (`StructureMapUtilities.transform` in org.hl7.fhir.core):
4//! groups run their extended group first, each rule runs once per source item that passes its
5//! filters, targets are created or reused (repeating elements get a new value, `share` reuses one),
6//! dependent groups receive variables by position, and a simple copy without a dependent group is
7//! completed by the default group for the source and target types.
8//!
9//! Where the Mapping Language IG says more than the reference engine does, the engine follows the
10//! IG: a rule may have several sources and then runs once per combination of their items; named
11//! groups and nested rules after `then` both run, the groups first; a source's `min..max` is
12//! enforced; `evaluate` with several values makes one target per value in a repeating element; a
13//! group name found in several imported maps is an error; `cast` takes `FHIR.` and `System.`
14//! types and checks the value.
15
16use std::collections::{HashMap, HashSet};
17use std::fmt;
18use std::future::Future;
19use std::pin::Pin;
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::sync::{Arc, Mutex};
22
23use fml_check::definitions::type_url;
24use fml_check::{Definitions, Kind, Lookup, Type, Types};
25use fml_model::model::{
26    Dependent, Group, Parameter, ParameterValue, Rule, Source, StructureMap, Target,
27};
28use fml_model::{SOURCE_ELEMENT_EXTENSION, TARGET_ELEMENT_EXTENSION};
29use serde_json::{Value, json};
30
31use crate::expressions::{Expressions, Item, bind_variables, path_start_names};
32use crate::ids::{IdGenerator, RandomIds};
33use crate::terminology::{ConceptMapResolver, NoConceptMaps, TranslateService, translate};
34use crate::trace::{Phase, Trace, TraceEvent, TraceStatus, TraceVariable};
35use crate::tree::{NodeId, Tree};
36use crate::{datetime, transforms};
37
38type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
39
40/// A failed transform.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Error {
43    pub kind: ErrorKind,
44    pub message: String,
45    /// The map the failure is in: its canonical URL.
46    pub map: Option<String>,
47    /// Where in that map, as a FHIRPath location: `StructureMap.group[0].rule[2].source[0]`.
48    pub position: Option<String>,
49    /// With tracing on, the events recorded up to the failure (status `stopped`).
50    pub trace: Option<Box<Trace>>,
51}
52
53/// Why a transform failed.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum ErrorKind {
56    /// The map cannot be run on this input.
57    Failed,
58    /// It ran past one of the [`Limits`].
59    Limit,
60    /// The caller cancelled it.
61    Cancelled,
62}
63
64impl Error {
65    fn new(message: impl Into<String>) -> Self {
66        Self {
67            kind: ErrorKind::Failed,
68            message: message.into(),
69            map: None,
70            position: None,
71            trace: None,
72        }
73    }
74
75    fn limit(message: impl Into<String>) -> Self {
76        Self {
77            kind: ErrorKind::Limit,
78            ..Self::new(message)
79        }
80    }
81
82    /// Records where the error happened, unless a deeper part already did.
83    fn at(mut self, map: &StructureMap, position: &str) -> Self {
84        if self.position.is_none() {
85            self.map = map.url.clone();
86            self.position = Some(position.to_owned());
87        }
88        self
89    }
90
91    fn in_rule(rule: &Rule, message: impl fmt::Display) -> Self {
92        match &rule.name {
93            Some(name) => Self::new(format!("rule `{name}`: {message}")),
94            None => Self::new(message.to_string()),
95        }
96    }
97}
98
99impl fmt::Display for Error {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        f.write_str(&self.message)
102    }
103}
104
105impl std::error::Error for Error {}
106
107type Result<T> = std::result::Result<T, Error>;
108
109/// Finds the maps a map imports, by canonical URL or `*` pattern.
110pub trait MapResolver {
111    fn resolve(&self, url: &str) -> Vec<Arc<StructureMap>>;
112}
113
114/// No imported maps.
115#[derive(Debug, Default, Clone, Copy)]
116pub struct NoMaps;
117
118impl MapResolver for NoMaps {
119    fn resolve(&self, _url: &str) -> Vec<Arc<StructureMap>> {
120        Vec::new()
121    }
122}
123
124/// A fixed set of maps, matched by URL; `*` in an import matches any run of characters.
125#[derive(Debug, Default, Clone)]
126pub struct MapSet {
127    maps: Vec<Arc<StructureMap>>,
128}
129
130impl MapSet {
131    pub fn new() -> Self {
132        Self::default()
133    }
134
135    pub fn add(&mut self, map: StructureMap) {
136        self.maps.push(Arc::new(map));
137    }
138}
139
140impl MapResolver for MapSet {
141    fn resolve(&self, url: &str) -> Vec<Arc<StructureMap>> {
142        self.maps
143            .iter()
144            .filter(|map| {
145                map.url
146                    .as_deref()
147                    .is_some_and(|candidate| match url.split_once('*') {
148                        Some((prefix, suffix)) => {
149                            candidate.len() >= prefix.len() + suffix.len()
150                                && candidate.starts_with(prefix)
151                                && candidate.ends_with(suffix)
152                        }
153                        None => candidate == url,
154                    })
155            })
156            .cloned()
157            .collect()
158    }
159}
160
161/// How much a transform may do before it stops with an [`ErrorKind::Limit`] error, and how much
162/// of its trace is kept.
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub struct Limits {
165    /// Groups calling groups.
166    pub max_depth: usize,
167    /// Rules run, counting each run of a rule once.
168    pub max_rules: u64,
169    /// Values created: target elements and intermediate values.
170    pub max_values: usize,
171    /// Trace events kept; later events are left out and the trace is `truncated`.
172    pub max_trace_events: usize,
173    /// Roughly the size of the kept trace events as JSON, in bytes.
174    pub max_trace_bytes: usize,
175}
176
177impl Default for Limits {
178    fn default() -> Self {
179        Self {
180            max_depth: 512,
181            max_rules: 20_000_000,
182            max_values: 2_000_000,
183            max_trace_events: 10_000,
184            max_trace_bytes: 8 * 1024 * 1024,
185        }
186    }
187}
188
189/// The result of a transform.
190#[derive(Debug, Clone, PartialEq)]
191pub struct Transformed {
192    /// The target, as FHIR JSON.
193    pub target: Value,
194    /// Messages from `log (…)` clauses.
195    pub log: Vec<String>,
196    /// Values left out of the target, such as a code a ConceptMap does not know (the reference
197    /// engine leaves those unset too).
198    pub warnings: Vec<String>,
199    /// Resources the map created outside the target (`create('Observation') as obs` with no
200    /// target element) and did not put into it: the `produced` structures of the map.
201    pub produced: Vec<Value>,
202    /// The debug trace, when tracing is on ([`Engine::with_trace`]).
203    pub trace: Option<Trace>,
204}
205
206/// Runs StructureMaps.
207pub struct Engine<'a, X> {
208    definitions: &'a dyn Definitions,
209    expressions: X,
210    maps: &'a dyn MapResolver,
211    concept_maps: &'a dyn ConceptMapResolver,
212    /// `$translate` for ConceptMaps the resolver does not have.
213    translator: Option<&'a dyn TranslateService>,
214    ids: &'a dyn IdGenerator,
215    limits: Limits,
216    cancel: Option<&'a AtomicBool>,
217    trace: bool,
218    /// Whether a code a ConceptMap gives no usable target for is left out with a warning
219    /// instead of failing the transform (see
220    /// [`with_lenient_translations`](Self::with_lenient_translations)).
221    lenient_translations: bool,
222    /// Whether a template's version (`extension`) must match too (see
223    /// [`with_template_versions`](Self::with_template_versions)).
224    template_versions: bool,
225    /// Group lookups, which look through every imported map: the maps do not change between
226    /// transforms, so each answer is computed once.
227    groups: Mutex<GroupCache>,
228}
229
230/// A target type with its FHIR type name.
231type TypedTarget = (Type, Option<String>);
232
233/// Cached lookups of groups in imported maps, by the importing map's URL and imports. Groups of
234/// the map being run are looked up in it each time, so a changed map with the same URL is not
235/// answered from the cache.
236#[derive(Default)]
237struct GroupCache {
238    by_name: HashMap<(String, String), Found>,
239    by_types: HashMap<(String, String, String), Option<GroupRef>>,
240    default_target: HashMap<(String, String), Option<TypedTarget>>,
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244enum Mode {
245    Input,
246    Output,
247    Shared,
248}
249
250/// Variables in scope: later entries hide earlier ones.
251#[derive(Debug, Clone, Default)]
252struct Vars {
253    entries: Vec<(Mode, String, NodeId)>,
254}
255
256impl Vars {
257    fn add(&mut self, mode: Mode, name: &str, node: NodeId) {
258        self.entries.push((mode, name.to_owned(), node));
259    }
260
261    fn get(&self, mode: Mode, name: &str) -> Option<NodeId> {
262        self.entries
263            .iter()
264            .rev()
265            .find(|(m, n, _)| *m == mode && n == name)
266            .map(|(_, _, node)| *node)
267    }
268
269    /// An input, or an output used as an input, as the reference engine allows.
270    fn value(&self, name: &str) -> Option<NodeId> {
271        self.get(Mode::Input, name)
272            .or_else(|| self.get(Mode::Output, name))
273    }
274
275    fn names(&self) -> impl Iterator<Item = &str> {
276        self.entries
277            .iter()
278            .filter(|(mode, _, _)| *mode != Mode::Shared)
279            .map(|(_, name, _)| name.as_str())
280    }
281}
282
283/// A group looked up by name.
284#[derive(Clone)]
285enum Found {
286    One(GroupRef),
287    None,
288    /// The URLs of the imported maps that each have the group.
289    Several(Vec<String>),
290}
291
292/// A group of a map.
293#[derive(Clone)]
294struct GroupRef {
295    map: Arc<StructureMap>,
296    index: usize,
297}
298
299impl GroupRef {
300    fn group(&self) -> &Group {
301        &self.map.group[self.index]
302    }
303}
304
305/// State of one transform.
306#[derive(Default)]
307struct Run {
308    tree: Tree,
309    log: Vec<String>,
310    warnings: Vec<String>,
311    depth: usize,
312    /// Recorded events, when tracing.
313    trace: Option<Vec<TraceEvent>>,
314    trace_depth: usize,
315    /// Resources created outside the target.
316    produced: Vec<NodeId>,
317    /// Evaluated `let` constants, by map URL and name.
318    constants: HashMap<(String, String), Vec<NodeId>>,
319    /// Constants being evaluated, to report one that refers to itself.
320    evaluating: HashSet<(String, String)>,
321    /// Rules run so far.
322    rules: u64,
323    /// Roughly the size of the recorded trace events, in bytes.
324    trace_bytes: usize,
325    /// Why later trace events were left out.
326    trace_truncated: Option<String>,
327    /// Elements written by a `single` target: element, owner and target.
328    single: HashSet<(NodeId, String, usize)>,
329    /// The `first` and `last` values of repeating elements, by owner and element.
330    order: HashMap<(NodeId, String), TargetOrder>,
331}
332
333/// Where the `first` and `last` targets of one repeating element put their values.
334#[derive(Default)]
335struct TargetOrder {
336    first: Option<usize>,
337    first_count: usize,
338    last: Option<usize>,
339    last_values: Vec<NodeId>,
340}
341const CORE: &str = "http://hl7.org/fhir/StructureDefinition/";
342
343impl<'a, X: Expressions> Engine<'a, X> {
344    pub fn new(definitions: &'a dyn Definitions, expressions: X) -> Self {
345        Self {
346            definitions,
347            expressions,
348            maps: &NoMaps,
349            concept_maps: &NoConceptMaps,
350            translator: None,
351            ids: &RandomIds,
352            limits: Limits::default(),
353            cancel: None,
354            trace: false,
355            template_versions: true,
356            lenient_translations: false,
357            groups: Mutex::new(GroupCache::default()),
358        }
359    }
360
361    fn cache(&self) -> std::sync::MutexGuard<'_, GroupCache> {
362        self.groups.lock().unwrap_or_else(|e| e.into_inner())
363    }
364
365    /// Records a debug trace of each transform: see [`Trace`].
366    pub fn with_trace(mut self, trace: bool) -> Self {
367        self.trace = trace;
368        self
369    }
370
371    /// Where `translate` finds ConceptMaps that are not contained in the map.
372    pub fn with_concept_maps(mut self, concept_maps: &'a dyn ConceptMapResolver) -> Self {
373        self.concept_maps = concept_maps;
374        self
375    }
376
377    /// A terminology service for `translate` with ConceptMaps neither the map nor the resolver
378    /// holds.
379    pub fn with_translator(mut self, translator: &'a dyn TranslateService) -> Self {
380        self.translator = Some(translator);
381        self
382    }
383
384    /// How much a transform may do; see [`Limits`].
385    pub fn with_limits(mut self, limits: Limits) -> Self {
386        self.limits = limits;
387        self
388    }
389
390    /// A flag the caller sets to stop running transforms; they fail with
391    /// [`ErrorKind::Cancelled`] at the next rule.
392    pub fn with_cancellation(mut self, cancel: &'a AtomicBool) -> Self {
393        self.cancel = Some(cancel);
394        self
395    }
396
397    /// Whether a value carries a template only with the template's version (`templateId`
398    /// `extension`), as a profile's identifier states it — the default — or with any version of
399    /// it (the same `root`). Converters that must recognise documents written against earlier
400    /// versions of the templates turn versions off.
401    pub fn with_template_versions(mut self, versions: bool) -> Self {
402        self.template_versions = versions;
403        self
404    }
405
406    /// What `translate` does with a code whose ConceptMap element has no usable target (R4
407    /// `unmatched` or `disjoint`, R5 `not-related-to`, or several): fail the transform, as the
408    /// reference engine does (the default), or leave the target unset and report a warning, as
409    /// for a code the ConceptMap does not list. Converters of whole documents leave it out.
410    pub fn with_lenient_translations(mut self, lenient: bool) -> Self {
411        self.lenient_translations = lenient;
412        self
413    }
414
415    /// Where `uuid()` and new resource ids come from; random by default.
416    pub fn with_ids(mut self, ids: &'a dyn IdGenerator) -> Self {
417        self.ids = ids;
418        self
419    }
420
421    /// Where the maps named in `imports` come from.
422    pub fn with_maps(mut self, maps: &'a dyn MapResolver) -> Self {
423        self.maps = maps;
424        self
425    }
426
427    fn types(&self) -> Types<'a> {
428        Types::new(self.definitions)
429    }
430
431    /// Transforms `source` with the map's first group; the target is created from the type of
432    /// that group's target parameter.
433    pub async fn transform(&self, map: &StructureMap, source: &Value) -> Result<Transformed> {
434        self.run(map, source, None).await
435    }
436
437    /// Transforms `source` into an existing `target`.
438    pub async fn transform_into(
439        &self,
440        map: &StructureMap,
441        source: &Value,
442        target: &Value,
443    ) -> Result<Transformed> {
444        self.run(map, source, Some(target)).await
445    }
446
447    async fn run(
448        &self,
449        map: &StructureMap,
450        source: &Value,
451        target: Option<&Value>,
452    ) -> Result<Transformed> {
453        let map = Arc::new(map.clone());
454        let group = map
455            .group
456            .first()
457            .ok_or_else(|| Error::new("the map has no groups"))?;
458        let input = |mode: &str| group.input.iter().find(|input| input.mode == mode);
459        let source_input =
460            input("source").ok_or_else(|| Error::new("the first group has no source"))?;
461        let target_input =
462            input("target").ok_or_else(|| Error::new("the first group has no target"))?;
463
464        let mut run = Run {
465            trace: self.trace.then(Vec::new),
466            ..Run::default()
467        };
468        let types = self.types();
469        let source_type = self.input_type(&map, source_input.type_name.as_deref());
470        let source_node = run.tree.load(source, source_type, &types);
471        if run.trace.is_some() {
472            // The trace says where an input variable is in the input (debug.md, variable.path).
473            // A logical model's type is a URL; its paths start with the structure's name, as
474            // the XML reader records positions (`ClinicalDocument.component…`).
475            let root_name = match run.tree.ty(source_node) {
476                Type::Node { structure, path }
477                    if path.is_empty() && structure.kind == Kind::Logical =>
478                {
479                    structure.name.clone()
480                }
481                _ => run
482                    .tree
483                    .fhir_type(source_node)
484                    .unwrap_or_else(|| "%resource".to_owned()),
485            };
486            run.tree.record_paths(source_node, &root_name);
487        }
488        let target_type = self.input_type(&map, target_input.type_name.as_deref());
489        let target_node = match target {
490            Some(target) => run.tree.load(target, target_type, &types),
491            None => {
492                if matches!(target_type, Type::Unknown) {
493                    return Err(Error::new(format!(
494                        "cannot create the target: type `{}` is not known",
495                        target_input.type_name.as_deref().unwrap_or("?")
496                    )));
497                }
498                run.tree.create(target_type, None)
499            }
500        };
501        let mut vars = Vars::default();
502        vars.add(Mode::Input, &source_input.name, source_node);
503        vars.add(Mode::Output, &target_input.name, target_node);
504        let entry = GroupRef {
505            map: map.clone(),
506            index: 0,
507        };
508        if let Err(mut error) = self.execute_group(&mut run, entry, vars, true).await {
509            let truncated = run.trace_truncated.take();
510            error.trace = run.trace.take().map(|events| {
511                Box::new(Trace {
512                    status: match truncated {
513                        Some(_) => TraceStatus::Truncated,
514                        None => TraceStatus::Stopped,
515                    },
516                    note: truncated,
517                    events,
518                })
519            });
520            return Err(error);
521        }
522        let in_target = run.tree.reachable(target_node);
523        let produced = run
524            .produced
525            .iter()
526            .filter(|node| !in_target.contains(node))
527            .map(|&node| run.tree.to_json(node))
528            .collect();
529        Ok(Transformed {
530            target: run.tree.to_json(target_node),
531            log: run.log,
532            warnings: run.warnings,
533            produced,
534            trace: run.trace.map(|events| Trace {
535                status: match run.trace_truncated {
536                    Some(_) => TraceStatus::Truncated,
537                    None => TraceStatus::Complete,
538                },
539                note: run.trace_truncated,
540                events,
541            }),
542        })
543    }
544
545    /// The type a group parameter or `create` names: a `uses` alias, a URL, or a type name.
546    fn input_type(&self, map: &StructureMap, name: Option<&str>) -> Type {
547        let Some(name) = name else {
548            return Type::Unknown;
549        };
550        self.types().structure(&self.type_name_url(map, name, CORE))
551    }
552
553    fn type_name_url(&self, map: &StructureMap, name: &str, context: &str) -> String {
554        if let Some(structure) = map
555            .structure
556            .iter()
557            .find(|s| s.alias.as_deref() == Some(name))
558        {
559            return structure.url.clone();
560        }
561        type_url(name, context)
562    }
563
564    // ---- Groups -----------------------------------------------------------------------------
565
566    fn execute_group<'r>(
567        &'r self,
568        run: &'r mut Run,
569        group: GroupRef,
570        vars: Vars,
571        at_root: bool,
572    ) -> BoxFuture<'r, Result<()>> {
573        Box::pin(async move {
574            run.depth += 1;
575            if run.depth > self.limits.max_depth {
576                return Err(Error::limit(format!(
577                    "groups nest more than {} deep; is a group calling itself without end?",
578                    self.limits.max_depth
579                )));
580            }
581            let map = group.map.clone();
582            let definition = group.group();
583            let position = format!("StructureMap.group[{}]", group.index);
584            self.record(run, Phase::Enter, &map, &position, &vars, Vec::new());
585            if let Some(extends) = &definition.extends {
586                let base = self.group_by_name(&map, extends)?;
587                self.execute_group(run, base, vars.clone(), false).await?;
588            }
589            let mut vars = vars;
590            for (index, rule) in definition.rule.iter().enumerate() {
591                let rule_position = format!("{position}.rule[{index}]");
592                self.execute_rule(run, &group, rule, &rule_position, &mut vars, at_root)
593                    .await?;
594            }
595            self.record(run, Phase::Exit, &map, &position, &vars, Vec::new());
596            run.depth -= 1;
597            Ok(())
598        })
599    }
600
601    /// The group `name`, in the map or the maps it imports.
602    fn group_by_name(&self, map: &Arc<StructureMap>, name: &str) -> Result<GroupRef> {
603        if let Some(index) = map.group.iter().position(|g| g.name == name) {
604            return Ok(GroupRef {
605                map: map.clone(),
606                index,
607            });
608        }
609        let key = (imports_key(map), name.to_owned());
610        let cached = self.cache().by_name.get(&key).cloned();
611        let found = match cached {
612            Some(found) => found,
613            None => {
614                let found = self.find_imported_group_by_name(map, name);
615                self.cache().by_name.insert(key, found.clone());
616                found
617            }
618        };
619        match found {
620            Found::One(group) => Ok(group),
621            Found::None => Err(Error::new(format!(
622                "no group `{name}` in {} or the maps it imports",
623                map.url.as_deref().unwrap_or("the map")
624            ))),
625            Found::Several(urls) => Err(Error::new(format!(
626                "group `{name}` is in several imported maps ({}); a group name must be unique",
627                urls.join(", ")
628            ))),
629        }
630    }
631
632    /// The group in exactly one of the maps `map` imports (fml.md: a group name must be unique
633    /// among the imported maps).
634    fn find_imported_group_by_name(&self, map: &Arc<StructureMap>, name: &str) -> Found {
635        let mut found: Vec<GroupRef> = Vec::new();
636        for import in &map.import {
637            for imported in self.maps.resolve(import) {
638                if imported.url == map.url || found.iter().any(|g| g.map.url == imported.url) {
639                    continue;
640                }
641                if let Some(index) = imported.group.iter().position(|g| g.name == name) {
642                    found.push(GroupRef {
643                        map: imported.clone(),
644                        index,
645                    });
646                }
647            }
648        }
649        match found.len() {
650            0 => Found::None,
651            1 => Found::One(found.remove(0)),
652            _ => Found::Several(
653                found
654                    .iter()
655                    .map(|g| g.map.url.clone().unwrap_or_default())
656                    .collect(),
657            ),
658        }
659    }
660
661    /// The default group for a source and target type: one with a type mode whose parameter
662    /// types match.
663    fn group_by_types(
664        &self,
665        map: &Arc<StructureMap>,
666        source: Option<&str>,
667        target: Option<&str>,
668    ) -> Option<GroupRef> {
669        let (source, target) = (source?, target?);
670        if let Some(index) = self.default_group_index(map, source, target) {
671            return Some(GroupRef {
672                map: map.clone(),
673                index,
674            });
675        }
676        let key = (imports_key(map), source.to_owned(), target.to_owned());
677        if let Some(found) = self.cache().by_types.get(&key) {
678            return found.clone();
679        }
680        let found = self.find_imported_group_by_types(map, source, target);
681        self.cache().by_types.insert(key, found.clone());
682        found
683    }
684
685    /// The default group of `map` itself for the types.
686    fn default_group_index(&self, map: &StructureMap, source: &str, target: &str) -> Option<usize> {
687        map.group.iter().position(|group| {
688            group.type_mode.is_some()
689                && matches!(group.input.as_slice(), [s, t]
690                    if s.mode == "source" && t.mode == "target"
691                    && self.stated_type_is(map, s.type_name.as_deref(), source)
692                    && !self.is_template_profile(map, s.type_name.as_deref())
693                    && self.stated_type_is(map, t.type_name.as_deref(), target))
694        })
695    }
696
697    /// Whether a group parameter's type is a profile applied by template id: such groups are
698    /// chosen by the value's templates, not by its type alone.
699    fn is_template_profile(&self, map: &StructureMap, stated: Option<&str>) -> bool {
700        stated.is_some_and(|stated| {
701            !self
702                .definitions
703                .templates(&self.type_name_url(map, stated, CORE))
704                .is_empty()
705        })
706    }
707
708    fn find_imported_group_by_types(
709        &self,
710        map: &Arc<StructureMap>,
711        source: &str,
712        target: &str,
713    ) -> Option<GroupRef> {
714        for import in &map.import {
715            for imported in self.maps.resolve(import) {
716                if imported.url == map.url {
717                    continue;
718                }
719                if let Some(index) = self.default_group_index(&imported, source, target) {
720                    return Some(GroupRef {
721                        map: imported,
722                        index,
723                    });
724                }
725            }
726        }
727        None
728    }
729
730    /// The type a source type filter names: through a `uses` alias to the structure's type
731    /// (`: CodeableConceptR4` is `CodeableConcept`), or the name as written.
732    fn stated_type_name(&self, map: &StructureMap, stated: &str) -> String {
733        map.structure
734            .iter()
735            .find(|s| s.alias.as_deref() == Some(stated))
736            .and_then(|s| self.definitions.structure(&s.url))
737            .map_or_else(|| stated.to_owned(), |s| s.type_name.clone())
738    }
739
740    /// Whether a value has the type a source type filter states: the type itself (by type name,
741    /// or for a logical model also by structure name, `CD`), and for a profile applied by
742    /// template id, one of the profile's templates.
743    fn has_stated_type(&self, run: &Run, map: &StructureMap, node: NodeId, stated: &str) -> bool {
744        let wanted = self.stated_type_name(map, stated);
745        let same = run.tree.fhir_type(node).as_deref() == Some(wanted.as_str())
746            || matches!(run.tree.ty(node), Type::Node { structure, path }
747                if path.is_empty()
748                    && structure.kind == Kind::Logical
749                    && (structure.name == wanted || structure.url == wanted));
750        same && self.has_templates_of(run, map, node, stated)
751    }
752
753    /// Whether a value carries one of the templates of the profile `stated` names; true when the
754    /// profile is not applied by template id.
755    fn has_templates_of(&self, run: &Run, map: &StructureMap, node: NodeId, stated: &str) -> bool {
756        let url = self.type_name_url(map, stated, CORE);
757        let templates = self.definitions.templates(&url);
758        if templates.is_empty() {
759            return true;
760        }
761        run.tree.children(node, "templateId").into_iter().any(|id| {
762            let text = |name: &str| {
763                run.tree
764                    .children(id, name)
765                    .first()
766                    .and_then(|&n| run.tree.text(n))
767            };
768            let (root, extension) = (text("root"), text("extension"));
769            root.is_some_and(|root| {
770                templates.iter().any(|t| {
771                    if self.template_versions {
772                        t.matches(&root, extension.as_deref())
773                    } else {
774                        t.root == root
775                    }
776                })
777            })
778        })
779    }
780
781    /// The default group for a value whose type has profiles applied by template id: the first
782    /// with a type mode whose source is such a profile the value carries a template of, and whose
783    /// target is the target's type.
784    fn group_by_template(
785        &self,
786        run: &Run,
787        map: &Arc<StructureMap>,
788        source: NodeId,
789        target: Option<&str>,
790    ) -> Option<GroupRef> {
791        let target = target?;
792        let fits = |owner: &StructureMap, group: &Group| -> bool {
793            group.type_mode.is_some()
794                && matches!(group.input.as_slice(), [s, t]
795                    if s.mode == "source" && t.mode == "target"
796                    && s.type_name.as_deref().is_some_and(|stated| {
797                        !self.definitions.templates(&self.type_name_url(owner, stated, CORE)).is_empty()
798                            && self.has_stated_type(run, owner, source, stated)
799                    })
800                    && self.stated_type_is(owner, t.type_name.as_deref(), target))
801        };
802        if let Some(index) = map.group.iter().position(|g| fits(map, g)) {
803            return Some(GroupRef {
804                map: map.clone(),
805                index,
806            });
807        }
808        for import in &map.import {
809            for imported in self.maps.resolve(import) {
810                if imported.url == map.url {
811                    continue;
812                }
813                if let Some(index) = imported.group.iter().position(|g| fits(&imported, g)) {
814                    return Some(GroupRef {
815                        map: imported,
816                        index,
817                    });
818                }
819            }
820        }
821        None
822    }
823
824    fn stated_type_is(&self, map: &StructureMap, stated: Option<&str>, actual: &str) -> bool {
825        let Some(stated) = stated else { return false };
826        if stated == actual {
827            return true;
828        }
829        let url = self.type_name_url(map, stated, CORE);
830        self.definitions
831            .structure(&url)
832            .is_some_and(|s| s.type_name == actual)
833    }
834
835    // ---- Rules ------------------------------------------------------------------------------
836
837    fn execute_rule<'r>(
838        &'r self,
839        run: &'r mut Run,
840        group: &'r GroupRef,
841        rule: &'r Rule,
842        position: &'r str,
843        vars: &'r mut Vars,
844        at_root: bool,
845    ) -> BoxFuture<'r, Result<()>> {
846        Box::pin(async move {
847            self.check_limits(run)?;
848            self.record(run, Phase::Enter, &group.map, position, vars, Vec::new());
849            let mut bindings = vec![vars.clone()];
850            for (index, source) in rule.source.iter().enumerate() {
851                let logged = run.log.len();
852                let mut next = Vec::new();
853                for binding in &bindings {
854                    next.extend(
855                        self.process_source(run, group, rule, source, binding)
856                            .await
857                            .map_err(|e| {
858                                e.at(&group.map, &format!("{position}.source[{index}]"))
859                            })?,
860                    );
861                }
862                bindings = next;
863                if run.trace.is_some() {
864                    let messages = run.log[logged..].to_vec();
865                    let shown = bindings.first().cloned().unwrap_or_else(|| vars.clone());
866                    let step = format!("{position}.source[{index}]");
867                    self.record(run, Phase::Step, &group.map, &step, &shown, messages);
868                }
869            }
870            let source_variable = match rule.source.as_slice() {
871                [single] => single.variable.as_deref(),
872                _ => None,
873            };
874            for mut binding in bindings {
875                for (index, target) in rule.target.iter().enumerate() {
876                    self.process_target(
877                        run,
878                        group,
879                        rule,
880                        target,
881                        &mut binding,
882                        vars,
883                        source_variable,
884                        at_root,
885                    )
886                    .await
887                    .map_err(|e| e.at(&group.map, &format!("{position}.target[{index}]")))?;
888                    if run.trace.is_some() {
889                        let step = format!("{position}.target[{index}]");
890                        self.record(run, Phase::Step, &group.map, &step, &binding, Vec::new());
891                    }
892                }
893                // The named groups run first, then the nested rules (fml.md, "Dependent Rules").
894                for dependent in &rule.dependent {
895                    self.execute_dependency(run, group, rule, dependent, &binding)
896                        .await
897                        .map_err(|e| e.at(&group.map, position))?;
898                }
899                for (index, child) in rule.rule.iter().enumerate() {
900                    let child_position = format!("{position}.rule[{index}]");
901                    self.execute_rule(run, group, child, &child_position, &mut binding, false)
902                        .await?;
903                }
904                if rule.dependent.is_empty()
905                    && rule.rule.is_empty()
906                    && let Some((source, target)) = simple_copy(rule)
907                {
908                    self.default_copy(run, group, rule, source, target, &binding)
909                        .await
910                        .map_err(|e| e.at(&group.map, position))?;
911                }
912            }
913            self.record(run, Phase::Exit, &group.map, position, vars, Vec::new());
914            Ok(())
915        })
916    }
917
918    /// Stops the transform when the caller cancelled it or it ran past its limits.
919    fn check_limits(&self, run: &mut Run) -> Result<()> {
920        if self
921            .cancel
922            .is_some_and(|cancel| cancel.load(Ordering::Relaxed))
923        {
924            return Err(Error {
925                kind: ErrorKind::Cancelled,
926                ..Error::new("the transform was cancelled")
927            });
928        }
929        run.rules += 1;
930        if run.rules > self.limits.max_rules {
931            return Err(Error::limit(format!(
932                "the transform ran more than {} rules",
933                self.limits.max_rules
934            )));
935        }
936        if run.tree.len() > self.limits.max_values {
937            return Err(Error::limit(format!(
938                "the transform created more than {} values",
939                self.limits.max_values
940            )));
941        }
942        Ok(())
943    }
944
945    /// Records a trace event, when tracing.
946    fn record(
947        &self,
948        run: &mut Run,
949        phase: Phase,
950        map: &StructureMap,
951        position: &str,
952        vars: &Vars,
953        messages: Vec<String>,
954    ) {
955        if run.trace.is_none() {
956            return;
957        }
958        if phase == Phase::Exit {
959            run.trace_depth = run.trace_depth.saturating_sub(1);
960        }
961        if run.trace_truncated.is_some() {
962            if phase == Phase::Enter {
963                run.trace_depth += 1;
964            }
965            return;
966        }
967        let mut seen = HashSet::new();
968        let mut variables: Vec<TraceVariable> = vars
969            .entries
970            .iter()
971            .rev()
972            .filter(|(mode, name, _)| *mode != Mode::Shared && seen.insert(name.clone()))
973            .map(|(mode, name, node)| TraceVariable {
974                name: name.clone(),
975                context_type: match mode {
976                    Mode::Input => "input",
977                    _ => "output",
978                }
979                .to_owned(),
980                value: run.tree.to_json(*node),
981                data_type: self.item(run, *node).type_name,
982                path: run.tree.path(*node).map(str::to_owned),
983            })
984            .collect();
985        variables.reverse();
986        let event = TraceEvent {
987            phase,
988            depth: run.trace_depth,
989            source: map.url.clone(),
990            position: position.to_owned(),
991            variables,
992            messages,
993        };
994        if run.trace_truncated.is_none() {
995            let size = event.approximate_size();
996            let count = run.trace.as_ref().map_or(0, Vec::len);
997            if count >= self.limits.max_trace_events {
998                run.trace_truncated = Some(format!(
999                    "the trace keeps its first {} events",
1000                    self.limits.max_trace_events
1001                ));
1002            } else if run.trace_bytes + size > self.limits.max_trace_bytes {
1003                run.trace_truncated = Some(format!(
1004                    "the trace keeps its first {count} events, about {} bytes",
1005                    run.trace_bytes
1006                ));
1007            } else if let Some(events) = &mut run.trace {
1008                run.trace_bytes += size;
1009                events.push(event);
1010            }
1011        }
1012        if phase == Phase::Enter {
1013            run.trace_depth += 1;
1014        }
1015    }
1016
1017    /// `src.a as s -> tgt.a as t` with nothing else to do: the default group for the two types
1018    /// maps one into the other, or the value is copied when the types are the same.
1019    async fn default_copy(
1020        &self,
1021        run: &mut Run,
1022        group: &GroupRef,
1023        rule: &Rule,
1024        source: &str,
1025        target: &str,
1026        vars: &Vars,
1027    ) -> Result<()> {
1028        let source = vars
1029            .get(Mode::Input, source)
1030            .ok_or_else(|| Error::in_rule(rule, format!("no source variable `{source}`")))?;
1031        let target = vars
1032            .get(Mode::Output, target)
1033            .ok_or_else(|| Error::in_rule(rule, format!("no target variable `{target}`")))?;
1034        let source_type = run.tree.fhir_type(source);
1035        let target_type = run.tree.fhir_type(target);
1036        let chosen = self
1037            .group_by_template(run, &group.map, source, target_type.as_deref())
1038            .or_else(|| {
1039                self.group_by_types(&group.map, source_type.as_deref(), target_type.as_deref())
1040            });
1041        if let Some(default) = chosen {
1042            let inputs = &default.group().input;
1043            let mut call = Vars::default();
1044            call.add(Mode::Input, &inputs[0].name, source);
1045            call.add(Mode::Output, &inputs[1].name, target);
1046            return self.execute_group(run, default, call, false).await;
1047        }
1048        if source_type == target_type || target_type.is_none() {
1049            run.tree.assign(target, source);
1050            return Ok(());
1051        }
1052        Err(Error::in_rule(
1053            rule,
1054            format!(
1055                "no default group maps {} to {}",
1056                source_type.as_deref().unwrap_or("?"),
1057                target_type.as_deref().unwrap_or("?")
1058            ),
1059        ))
1060    }
1061
1062    async fn execute_dependency(
1063        &self,
1064        run: &mut Run,
1065        group: &GroupRef,
1066        rule: &Rule,
1067        dependent: &Dependent,
1068        vars: &Vars,
1069    ) -> Result<()> {
1070        let callee = self.group_by_name(&group.map, &dependent.name)?;
1071        let inputs = &callee.group().input;
1072        if inputs.len() != dependent.parameter.len() {
1073            return Err(Error::in_rule(
1074                rule,
1075                format!(
1076                    "group `{}` takes {} arguments, the call passes {}",
1077                    dependent.name,
1078                    inputs.len(),
1079                    dependent.parameter.len()
1080                ),
1081            ));
1082        }
1083        let mut call = Vars::default();
1084        for (input, parameter) in inputs.iter().zip(&dependent.parameter) {
1085            let name = parameter_name(parameter);
1086            let (mode, node) = if input.mode == "source" {
1087                // A literal argument (fml.md: a parameter is an id or a literal) is a value of
1088                // its own; a string names a variable when one has that name.
1089                let node = match &parameter.value {
1090                    ParameterValue::Id(name) => vars.value(name),
1091                    ParameterValue::String(text) => vars
1092                        .value(text)
1093                        .or_else(|| Some(self.string(run, text.clone(), "string"))),
1094                    ParameterValue::Boolean(b) => {
1095                        Some(self.literal(run, Value::Bool(*b), "boolean"))
1096                    }
1097                    ParameterValue::Integer(n) => {
1098                        Some(self.literal(run, Value::from(*n), "integer"))
1099                    }
1100                    ParameterValue::Decimal(n) => {
1101                        Some(self.literal(run, Value::Number(n.clone()), "decimal"))
1102                    }
1103                    ParameterValue::Date(text) => Some(self.string(run, text.clone(), "date")),
1104                    ParameterValue::Time(text) => Some(self.string(run, text.clone(), "time")),
1105                    ParameterValue::DateTime(text) => {
1106                        Some(self.string(run, text.clone(), "dateTime"))
1107                    }
1108                };
1109                (Mode::Input, node)
1110            } else {
1111                (Mode::Output, vars.get(Mode::Output, &name))
1112            };
1113            let node = node.ok_or_else(|| {
1114                Error::in_rule(
1115                    rule,
1116                    format!(
1117                        "`{}` passes `{name}` as {} `{}`, but it has no value",
1118                        dependent.name, input.mode, input.name
1119                    ),
1120                )
1121            })?;
1122            call.add(mode, &input.name, node);
1123        }
1124        self.execute_group(run, callee, call, false).await
1125    }
1126
1127    // ---- Sources ----------------------------------------------------------------------------
1128
1129    async fn process_source(
1130        &self,
1131        run: &mut Run,
1132        group: &GroupRef,
1133        rule: &Rule,
1134        source: &Source,
1135        vars: &Vars,
1136    ) -> Result<Vec<Vars>> {
1137        let context = vars.get(Mode::Input, &source.context).ok_or_else(|| {
1138            Error::in_rule(
1139                rule,
1140                format!("unknown source variable `{}`", source.context),
1141            )
1142        })?;
1143        let mut items = vec![context];
1144        for element in element_path(
1145            source.element.as_deref(),
1146            &source.extension,
1147            SOURCE_ELEMENT_EXTENSION,
1148        ) {
1149            items = items
1150                .iter()
1151                .flat_map(|&item| run.tree.children(item, &element))
1152                .collect();
1153        }
1154        check_cardinality(rule, source, items.len())?;
1155        // The type filter selects the exact type; the default stands in when nothing is left.
1156        if let Some(type_name) = &source.type_name {
1157            items.retain(|&item| self.has_stated_type(run, &group.map, item, type_name));
1158        }
1159        if items.is_empty()
1160            && let Some(default) = &source.default_value
1161        {
1162            items = self.evaluate(run, group, default, None, vars).await?;
1163        }
1164        let with_item = |item: NodeId| {
1165            let mut bound = vars.clone();
1166            if let Some(variable) = &source.variable {
1167                bound.add(Mode::Input, variable, item);
1168            }
1169            bound
1170        };
1171        if let Some(condition) = &source.condition {
1172            let mut kept = Vec::with_capacity(items.len());
1173            for item in items {
1174                if self
1175                    .evaluate_boolean(run, group, condition, Some(item), &with_item(item))
1176                    .await?
1177                {
1178                    kept.push(item);
1179                }
1180            }
1181            items = kept;
1182        }
1183        if let Some(check) = &source.check {
1184            for &item in &items {
1185                if !self
1186                    .evaluate_boolean(run, group, check, Some(item), &with_item(item))
1187                    .await?
1188                {
1189                    return Err(Error::in_rule(rule, format!("check `{check}` failed")));
1190                }
1191            }
1192        }
1193        if let Some(log) = &source.log_message {
1194            let mut messages = Vec::new();
1195            for &item in &items {
1196                let values = self
1197                    .evaluate(run, group, log, Some(item), &with_item(item))
1198                    .await?;
1199                messages.extend(values.into_iter().filter_map(|v| run.tree.text(v)));
1200            }
1201            if !messages.is_empty() {
1202                run.log.push(messages.join(","));
1203            }
1204        }
1205        match source.list_mode.as_deref() {
1206            _ if items.is_empty() => {}
1207            Some("first") => items.truncate(1),
1208            Some("not_first") => {
1209                items.remove(0);
1210            }
1211            Some("last") => items = vec![*items.last().unwrap()],
1212            Some("not_last") => {
1213                items.pop();
1214            }
1215            Some("only_one") if items.len() > 1 => {
1216                return Err(Error::in_rule(
1217                    rule,
1218                    format!("`{}` has more than one item", source.context),
1219                ));
1220            }
1221            _ => {}
1222        }
1223        Ok(items.into_iter().map(with_item).collect())
1224    }
1225
1226    // ---- Targets ----------------------------------------------------------------------------
1227
1228    #[allow(clippy::too_many_arguments)]
1229    async fn process_target(
1230        &self,
1231        run: &mut Run,
1232        group: &GroupRef,
1233        rule: &Rule,
1234        target: &Target,
1235        vars: &mut Vars,
1236        shared: &mut Vars,
1237        source_variable: Option<&str>,
1238        at_root: bool,
1239    ) -> Result<()> {
1240        let types = self.types();
1241        let elements = element_path(
1242            target.element.as_deref(),
1243            &target.extension,
1244            TARGET_ELEMENT_EXTENSION,
1245        );
1246        let element = elements.last().cloned();
1247        let mut destination = match &target.context {
1248            Some(context) => Some(vars.get(Mode::Output, context).ok_or_else(|| {
1249                Error::in_rule(rule, format!("unknown target variable `{context}`"))
1250            })?),
1251            None => None,
1252        };
1253        let sharing = target.list_mode.iter().any(|m| m == "share");
1254        let mut value = match (&target.transform, sharing, &target.list_rule_id) {
1255            (None, true, Some(id)) => shared.get(Mode::Shared, id),
1256            _ => None,
1257        };
1258        if let Some(dest) = destination
1259            && value.is_none()
1260        {
1261            let mut dest = dest;
1262            for intermediate in elements.iter().take(elements.len().saturating_sub(1)) {
1263                dest = run
1264                    .tree
1265                    .make_property(dest, intermediate, &types)
1266                    .map_err(|e| Error::in_rule(rule, e))?;
1267            }
1268            destination = Some(dest);
1269        }
1270        if target.transform.is_some() {
1271            let produced = if target.transform.as_deref() == Some("evaluate") {
1272                let values = self
1273                    .evaluate_transform(run, group, rule, target, vars)
1274                    .await?;
1275                if values.len() > 1 {
1276                    self.check_several(
1277                        run,
1278                        rule,
1279                        target,
1280                        destination,
1281                        element.as_deref(),
1282                        values.len(),
1283                    )?;
1284                }
1285                values
1286            } else {
1287                self.run_transform(
1288                    run,
1289                    group,
1290                    rule,
1291                    target,
1292                    vars,
1293                    destination,
1294                    element.as_deref(),
1295                    source_variable,
1296                    at_root,
1297                )
1298                .await?
1299                .into_iter()
1300                .collect()
1301            };
1302            if let Some(dest) = destination {
1303                for &item in &produced {
1304                    match &element {
1305                        Some(element) => {
1306                            run.tree
1307                                .set_property(dest, element, item, &types)
1308                                .map_err(|e| Error::in_rule(rule, e))?;
1309                            order_target(run, rule, target, dest, element, item)?;
1310                        }
1311                        None => run.tree.assign(dest, item),
1312                    }
1313                }
1314            }
1315            value = produced.last().copied();
1316        } else if let (Some(dest), None) = (destination, value) {
1317            value = Some(match &element {
1318                Some(element) => {
1319                    let created = run
1320                        .tree
1321                        .make_property(dest, element, &types)
1322                        .map_err(|e| Error::in_rule(rule, e))?;
1323                    if let (true, Some(id)) = (sharing, &target.list_rule_id) {
1324                        // Siblings find it in the enclosing scope, nested rules in this one.
1325                        shared.add(Mode::Shared, id, created);
1326                        vars.add(Mode::Shared, id, created);
1327                    }
1328                    order_target(run, rule, target, dest, element, created)?;
1329                    created
1330                }
1331                None => dest,
1332            });
1333        }
1334        if let (Some(variable), Some(value)) = (&target.variable, value) {
1335            vars.add(Mode::Output, variable, value);
1336        }
1337        Ok(())
1338    }
1339
1340    /// `evaluate(context, expression)` or `(expression)`: every value the expression gives.
1341    async fn evaluate_transform(
1342        &self,
1343        run: &mut Run,
1344        group: &GroupRef,
1345        rule: &Rule,
1346        target: &Target,
1347        vars: &Vars,
1348    ) -> Result<Vec<NodeId>> {
1349        let parameters = &target.parameter;
1350        let expression = match parameters.last().map(|p| &p.value) {
1351            Some(ParameterValue::String(expression)) => expression,
1352            _ => return Err(Error::in_rule(rule, "evaluate needs an expression")),
1353        };
1354        let focus = match parameters.as_slice() {
1355            [context, _] => Some(self.parameter_node(run, group, context, vars).await?),
1356            _ => None,
1357        };
1358        self.evaluate(run, group, expression, focus, vars).await
1359    }
1360
1361    /// Several values from `evaluate` make one target each, which needs a repeating element
1362    /// (transforms.md, "evaluate"); a variable cannot name them all.
1363    fn check_several(
1364        &self,
1365        run: &Run,
1366        rule: &Rule,
1367        target: &Target,
1368        destination: Option<NodeId>,
1369        element: Option<&str>,
1370        count: usize,
1371    ) -> Result<()> {
1372        let fail = |message: String| Err(Error::in_rule(rule, message));
1373        let (Some(dest), Some(element)) = (destination, element) else {
1374            return fail(format!(
1375                "evaluate gave {count} values where one was expected"
1376            ));
1377        };
1378        let owner = run.tree.ty(dest).display();
1379        match self.types().lookup(run.tree.ty(dest), element) {
1380            Lookup::Found(found) if found.element.array => {}
1381            Lookup::Found(_) => {
1382                return fail(format!(
1383                    "evaluate gave {count} values, but `{owner}.{element}` does not repeat"
1384                ));
1385            }
1386            _ => {
1387                return fail(format!(
1388                    "evaluate gave {count} values, and whether `{owner}.{element}` repeats is not known"
1389                ));
1390            }
1391        }
1392        if let Some(variable) = &target.variable {
1393            return fail(format!(
1394                "evaluate gave {count} values; variable `{variable}` can only name one"
1395            ));
1396        }
1397        Ok(())
1398    }
1399
1400    #[allow(clippy::too_many_arguments)]
1401    async fn run_transform(
1402        &self,
1403        run: &mut Run,
1404        group: &GroupRef,
1405        rule: &Rule,
1406        target: &Target,
1407        vars: &Vars,
1408        destination: Option<NodeId>,
1409        element: Option<&str>,
1410        source_variable: Option<&str>,
1411        _at_root: bool,
1412    ) -> Result<Option<NodeId>> {
1413        let transform = target.transform.as_deref().unwrap_or("copy");
1414        let parameters = &target.parameter;
1415        let fail = |message: String| Error::in_rule(rule, message);
1416        let types = self.types();
1417        match transform {
1418            "create" => {
1419                let ty = match parameters.first() {
1420                    Some(parameter) => {
1421                        let name = self.parameter_text(run, group, parameter, vars).await?;
1422                        let context = destination
1423                            .and_then(|d| run.tree.ty(d).structure().map(|s| s.url.clone()))
1424                            .unwrap_or_else(|| CORE.to_owned());
1425                        // A type of the element written to (`value = create('PQ')` into a
1426                        // logical model's element that has several), else by name or alias.
1427                        let of_element = match (destination, element) {
1428                            (Some(d), Some(element)) => match types.lookup(run.tree.ty(d), element)
1429                            {
1430                                Lookup::Found(found) => types.named_type(&found, &name),
1431                                _ => Type::Unknown,
1432                            },
1433                            _ => Type::Unknown,
1434                        };
1435                        let ty = match of_element {
1436                            Type::Unknown => {
1437                                types.structure(&self.type_name_url(&group.map, &name, &context))
1438                            }
1439                            known => known,
1440                        };
1441                        (ty, Some(name))
1442                    }
1443                    None => self
1444                        .implied_type(run, group, destination, element, source_variable, vars)
1445                        .map_err(fail)?,
1446                };
1447                let created = run.tree.create(ty.0, ty.1);
1448                // A resource created outside any target element is one the map produces.
1449                if destination.is_none() && run.tree.ty(created).is_resource() {
1450                    run.produced.push(created);
1451                }
1452                Ok(Some(created))
1453            }
1454            "copy" => {
1455                let parameter = parameters
1456                    .first()
1457                    .ok_or_else(|| fail("copy needs a value".into()))?;
1458                let node = self.parameter_node(run, group, parameter, vars).await?;
1459                Ok(Some(run.tree.deep_copy(node)))
1460            }
1461            "truncate" => {
1462                let text = self.argument_text(run, group, parameters, 0, vars).await?;
1463                let length = self.argument_text(run, group, parameters, 1, vars).await?;
1464                let length: usize = length
1465                    .trim()
1466                    .parse()
1467                    .map_err(|_| fail(format!("truncate needs a length, not `{length}`")))?;
1468                let truncated = text.chars().take(length).collect();
1469                Ok(Some(self.string(run, truncated, "string")))
1470            }
1471            "cast" => {
1472                let source = self
1473                    .parameter_node(
1474                        run,
1475                        group,
1476                        parameters
1477                            .first()
1478                            .ok_or_else(|| fail("cast needs a value".into()))?,
1479                        vars,
1480                    )
1481                    .await?;
1482                let type_name = if parameters.len() > 1 {
1483                    transforms::cast_type(
1484                        &self.argument_text(run, group, parameters, 1, vars).await?,
1485                    )
1486                } else {
1487                    // Without a type, the value takes the type of the element it is written to.
1488                    self.implied_type(run, group, destination, element, None, vars)
1489                        .ok()
1490                        .and_then(|(_, name)| name)
1491                        .ok_or_else(|| fail("cast needs a type: cast(value, 'type')".into()))?
1492                };
1493                if !run.tree.is_primitive(source) {
1494                    // A complex value casts only to its own type.
1495                    return match run.tree.fhir_type(source) {
1496                        Some(own) if own == type_name => Ok(Some(run.tree.deep_copy(source))),
1497                        own => Err(fail(format!(
1498                            "cannot cast a {} to {type_name}",
1499                            own.as_deref().unwrap_or("complex value")
1500                        ))),
1501                    };
1502                }
1503                let text = run.tree.text(source).unwrap_or_default();
1504                let value = transforms::cast(&text, &type_name).map_err(fail)?;
1505                let ty = types.structure(&type_url(&type_name, CORE));
1506                Ok(Some(run.tree.primitive(value, ty, Some(type_name))))
1507            }
1508            "append" => {
1509                let mut text = String::new();
1510                for index in 0..parameters.len() {
1511                    text.push_str(
1512                        &self
1513                            .argument_text(run, group, parameters, index, vars)
1514                            .await?,
1515                    );
1516                }
1517                Ok(Some(self.string(run, text, "string")))
1518            }
1519            "translate" => self.translate(run, group, rule, parameters, vars).await,
1520            "reference" | "pointer" => {
1521                let parameter = parameters
1522                    .first()
1523                    .ok_or_else(|| fail(format!("{transform} needs a resource")))?;
1524                let resource = self.parameter_node(run, group, parameter, vars).await?;
1525                let resource_type = run
1526                    .tree
1527                    .fhir_type(resource)
1528                    .filter(|_| {
1529                        run.tree.ty(resource).is_resource() || !run.tree.is_primitive(resource)
1530                    })
1531                    .ok_or_else(|| fail(format!("{transform} needs a resource")))?;
1532                let id = match run
1533                    .tree
1534                    .children(resource, "id")
1535                    .first()
1536                    .and_then(|&id| run.tree.text(id))
1537                {
1538                    Some(id) => id,
1539                    None => {
1540                        let id = self.ids.uuid();
1541                        run.tree
1542                            .set_text(resource, "id", &id, &types)
1543                            .map_err(fail)?;
1544                        id
1545                    }
1546                };
1547                Ok(Some(if transform == "reference" {
1548                    self.string(run, format!("{resource_type}/{id}"), "string")
1549                } else {
1550                    self.string(run, format!("urn:uuid:{id}"), "uri")
1551                }))
1552            }
1553            "uuid" => Ok(Some(self.string(run, self.ids.uuid(), "id"))),
1554            "toDateTime" | "toDate" | "toTime" => {
1555                let text = self.argument_text(run, group, parameters, 0, vars).await?;
1556                let format = match parameters.get(1) {
1557                    Some(_) => Some(self.argument_text(run, group, parameters, 1, vars).await?),
1558                    None => None,
1559                };
1560                let (value, type_name) = match transform {
1561                    "toDateTime" => (datetime::to_date_time(&text, format.as_deref()), "dateTime"),
1562                    "toDate" => (datetime::to_date(&text, format.as_deref()), "date"),
1563                    _ => (datetime::to_time(&text, format.as_deref()), "time"),
1564                };
1565                Ok(Some(self.string(run, value.map_err(fail)?, type_name)))
1566            }
1567            "unixToDateTime" | "unixToDate" | "unixToTime" => {
1568                let text = self.argument_text(run, group, parameters, 0, vars).await?;
1569                let millis: i64 = text.trim().parse().map_err(|_| {
1570                    fail(format!("`{text}` is not a Unix timestamp in milliseconds"))
1571                })?;
1572                let timezone = match parameters.get(1) {
1573                    Some(_) => Some(self.argument_text(run, group, parameters, 1, vars).await?),
1574                    None => None,
1575                };
1576                let (value, type_name) = match transform {
1577                    "unixToDateTime" => (
1578                        datetime::unix_to_date_time(millis, timezone.as_deref()),
1579                        "dateTime",
1580                    ),
1581                    "unixToDate" => (datetime::unix_to_date(millis, timezone.as_deref()), "date"),
1582                    _ => (datetime::unix_to_time(millis, timezone.as_deref()), "time"),
1583                };
1584                Ok(Some(self.string(run, value.map_err(fail)?, type_name)))
1585            }
1586            "escape" => {
1587                let text = self.argument_text(run, group, parameters, 0, vars).await?;
1588                let (from, to) = match parameters.len() {
1589                    3 => (
1590                        self.argument_text(run, group, parameters, 1, vars).await?,
1591                        self.argument_text(run, group, parameters, 2, vars).await?,
1592                    ),
1593                    _ => (
1594                        "none".to_owned(),
1595                        self.argument_text(run, group, parameters, 1, vars).await?,
1596                    ),
1597                };
1598                let escaped = datetime::escape(&text, &from, &to).map_err(fail)?;
1599                Ok(Some(self.string(run, escaped, "string")))
1600            }
1601            "cc" | "c" | "id" | "cp" | "qty" => {
1602                let mut arguments = Vec::with_capacity(parameters.len());
1603                for index in 0..parameters.len() {
1604                    arguments.push(
1605                        self.argument_text(run, group, parameters, index, vars)
1606                            .await?,
1607                    );
1608                }
1609                let (json, type_name) = match transform {
1610                    "cc" => (transforms::codeable_concept(&arguments), "CodeableConcept"),
1611                    "c" => match arguments.as_slice() {
1612                        [system, code, rest @ ..] => {
1613                            (transforms::coding(system, code, rest.first()), "Coding")
1614                        }
1615                        _ => return Err(fail("c needs a system and a code".into())),
1616                    },
1617                    "id" => (transforms::identifier(&arguments), "Identifier"),
1618                    "cp" => (transforms::contact_point(&arguments), "ContactPoint"),
1619                    _ => (transforms::quantity(&arguments).map_err(fail)?, "Quantity"),
1620                };
1621                let context = destination
1622                    .and_then(|d| run.tree.ty(d).structure().map(|s| s.url.clone()))
1623                    .unwrap_or_else(|| CORE.to_owned());
1624                let ty = types.structure(&type_url(type_name, &context));
1625                let node = run.tree.load(&json, ty, &types);
1626                Ok(Some(node))
1627            }
1628            other => Err(fail(format!("the {other} transform is not supported"))),
1629        }
1630    }
1631
1632    /// The type `create()` makes without a type argument: the element's only type, or the
1633    /// source's type.
1634    fn implied_type(
1635        &self,
1636        run: &Run,
1637        group: &GroupRef,
1638        destination: Option<NodeId>,
1639        element: Option<&str>,
1640        source_variable: Option<&str>,
1641        vars: &Vars,
1642    ) -> std::result::Result<(Type, Option<String>), String> {
1643        let types = self.types();
1644        if let (Some(dest), Some(element)) = (destination, element)
1645            && let Lookup::Found(found) = types.lookup(run.tree.ty(dest), element)
1646            && let [single] = found.element.types.as_slice()
1647            && single != "Resource"
1648            && single != "*"
1649        {
1650            return Ok((types.element_type(&found, None), Some(single.clone())));
1651        }
1652        if let Some(source) = source_variable.and_then(|s| vars.get(Mode::Input, s)) {
1653            // The type the default group for the source's type produces, as the reference engine
1654            // does: a contained R4 Medication becomes the R5 Medication its group maps to.
1655            if let Some(source_type) = run.tree.fhir_type(source)
1656                && let Some(produced) = self.default_target_type(group, &source_type)
1657            {
1658                return Ok(produced);
1659            }
1660            return Ok((run.tree.ty(source).clone(), run.tree.fhir_type(source)));
1661        }
1662        Err("cannot tell what type to create: name it, as in create('Type')".into())
1663    }
1664
1665    /// The target type of the `<<type+>>` group whose source is `source_type`, in the map or the
1666    /// maps it imports.
1667    fn default_target_type(&self, group: &GroupRef, source_type: &str) -> Option<TypedTarget> {
1668        if let Some(found) = self.default_target_in(&group.map, source_type) {
1669            return Some(found);
1670        }
1671        let key = (imports_key(&group.map), source_type.to_owned());
1672        if let Some(found) = self.cache().default_target.get(&key) {
1673            return found.clone();
1674        }
1675        let found = self.find_default_target_type(group, source_type);
1676        self.cache().default_target.insert(key, found.clone());
1677        found
1678    }
1679
1680    /// The target type of a `<<type+>>` group of `map` itself whose source is `source_type`.
1681    fn default_target_in(&self, map: &StructureMap, source_type: &str) -> Option<TypedTarget> {
1682        map.group.iter().find_map(|candidate| {
1683            if candidate.type_mode.as_deref() != Some("type-and-types") {
1684                return None;
1685            }
1686            let [source, target] = candidate.input.as_slice() else {
1687                return None;
1688            };
1689            if source.mode != "source"
1690                || target.mode != "target"
1691                || !self.stated_type_is(map, source.type_name.as_deref(), source_type)
1692            {
1693                return None;
1694            }
1695            let stated = target.type_name.as_deref()?;
1696            let ty = self
1697                .types()
1698                .structure(&self.type_name_url(map, stated, CORE));
1699            let name = ty.fhir_type().unwrap_or_else(|| stated.to_owned());
1700            Some((ty, Some(name)))
1701        })
1702    }
1703
1704    fn find_default_target_type(&self, group: &GroupRef, source_type: &str) -> Option<TypedTarget> {
1705        group.map.import.iter().find_map(|import| {
1706            self.maps
1707                .resolve(import)
1708                .iter()
1709                .filter(|imported| imported.url != group.map.url)
1710                .find_map(|imported| self.default_target_in(imported, source_type))
1711        })
1712    }
1713
1714    /// `translate(value, map, output)`: a code, Coding or CodeableConcept translated with a
1715    /// ConceptMap contained in the map (`'#id'`) or found by the ConceptMap resolver.
1716    async fn translate(
1717        &self,
1718        run: &mut Run,
1719        group: &GroupRef,
1720        rule: &Rule,
1721        parameters: &[Parameter],
1722        vars: &Vars,
1723    ) -> Result<Option<NodeId>> {
1724        let fail = |message: String| Error::in_rule(rule, message);
1725        let source = self
1726            .parameter_node(
1727                run,
1728                group,
1729                parameters
1730                    .first()
1731                    .ok_or_else(|| fail("translate needs a value".into()))?,
1732                vars,
1733            )
1734            .await?;
1735        let map_url = self.argument_text(run, group, parameters, 1, vars).await?;
1736        let output = self.argument_text(run, group, parameters, 2, vars).await?;
1737        let concept_map = match map_url.strip_prefix('#') {
1738            Some(id) => Some(
1739                group
1740                    .map
1741                    .contained
1742                    .iter()
1743                    .find(|r| r["resourceType"] == "ConceptMap" && r["id"] == id)
1744                    .cloned()
1745                    .map(Arc::new)
1746                    .ok_or_else(|| fail(format!("no contained ConceptMap `{id}`")))?,
1747            ),
1748            None => self.concept_maps.resolve(&map_url),
1749        };
1750        if concept_map.is_none() && self.translator.is_none() {
1751            return Err(fail(format!("ConceptMap `{map_url}` not found")));
1752        }
1753        let codes = self.codes_of(run, source);
1754        let mut translation = None;
1755        let mut reported = false;
1756        for (system, code) in &codes {
1757            let found = match (&concept_map, self.translator) {
1758                (Some(concept_map), _) => translate(concept_map, system.as_deref(), code),
1759                (None, Some(translator)) => {
1760                    translator
1761                        .translate(&map_url, system.as_deref(), code)
1762                        .await
1763                }
1764                (None, None) => unreachable!("checked above"),
1765            };
1766            let found = match found {
1767                Ok(found) => found,
1768                // A code the ConceptMap marks as having no counterpart: left out, like a code it
1769                // does not know, when the host asked for that.
1770                Err(e) if self.lenient_translations => {
1771                    let rule_name = rule.name.as_deref().unwrap_or("?");
1772                    run.warnings
1773                        .push(format!("rule `{rule_name}`: {e} in `{map_url}`; left out"));
1774                    reported = true;
1775                    None
1776                }
1777                Err(e) => return Err(fail(format!("{e} in `{map_url}`"))),
1778            };
1779            if let Some(found) = found {
1780                translation = Some(found);
1781                break;
1782            }
1783        }
1784        let Some(translation) = translation else {
1785            if reported {
1786                return Ok(None);
1787            }
1788            let shown: Vec<&str> = codes.iter().map(|(_, c)| c.as_str()).collect();
1789            let rule_name = rule.name.as_deref().unwrap_or("?");
1790            run.warnings.push(format!(
1791                "rule `{rule_name}`: `{}` has no translation in `{map_url}`; left out",
1792                shown.join(", ")
1793            ));
1794            return Ok(None);
1795        };
1796
1797        let types = self.types();
1798        let mut coding = serde_json::Map::new();
1799        if let Some(system) = &translation.system {
1800            coding.insert("system".into(), system.clone().into());
1801        }
1802        if let Some(code) = &translation.code {
1803            coding.insert("code".into(), code.clone().into());
1804        }
1805        if let Some(display) = &translation.display {
1806            coding.insert("display".into(), display.clone().into());
1807        }
1808        let text = |value: &Option<String>| value.clone().map_or(Value::Null, Value::String);
1809        let (json, type_name) = match output.as_str() {
1810            "code" => (text(&translation.code), "code"),
1811            "system" => (text(&translation.system), "uri"),
1812            "display" => (text(&translation.display), "string"),
1813            "Coding" => (Value::Object(coding), "Coding"),
1814            "CodeableConcept" => (
1815                json!({ "coding": [Value::Object(coding)] }),
1816                "CodeableConcept",
1817            ),
1818            other => return Err(fail(format!("unknown translate output `{other}`"))),
1819        };
1820        let ty = types.structure(&type_url(type_name, CORE));
1821        Ok(Some(if json.is_object() {
1822            run.tree.load(&json, ty, &types)
1823        } else {
1824            run.tree.primitive(json, ty, Some(type_name.to_owned()))
1825        }))
1826    }
1827
1828    /// The (system, code) pairs a value holds: a code, a Coding, or a CodeableConcept's codings.
1829    fn codes_of(&self, run: &Run, node: NodeId) -> Vec<(Option<String>, String)> {
1830        let text = |id: NodeId, name: &str| {
1831            run.tree
1832                .children(id, name)
1833                .first()
1834                .and_then(|&child| run.tree.text(child))
1835        };
1836        if run.tree.is_primitive(node) {
1837            return run
1838                .tree
1839                .text(node)
1840                .map(|code| (None, code))
1841                .into_iter()
1842                .collect();
1843        }
1844        let codings = run.tree.children(node, "coding");
1845        let codings = if codings.is_empty() {
1846            vec![node]
1847        } else {
1848            codings
1849        };
1850        codings
1851            .into_iter()
1852            .filter_map(|coding| Some((text(coding, "system"), text(coding, "code")?)))
1853            .collect()
1854    }
1855
1856    // ---- Parameters -------------------------------------------------------------------------
1857
1858    /// A parameter as a node: a variable (or constant), or a new primitive for a literal.
1859    async fn parameter_node(
1860        &self,
1861        run: &mut Run,
1862        group: &GroupRef,
1863        parameter: &Parameter,
1864        vars: &Vars,
1865    ) -> Result<NodeId> {
1866        let types = self.types();
1867        let (value, type_name) = match &parameter.value {
1868            ParameterValue::Id(name) => return self.variable(run, group, name, vars).await,
1869            ParameterValue::String(v) => (Value::String(v.clone()), "string"),
1870            ParameterValue::Boolean(v) => (Value::Bool(*v), "boolean"),
1871            ParameterValue::Integer(v) => (Value::from(*v), "integer"),
1872            ParameterValue::Decimal(v) => (Value::Number(v.clone()), "decimal"),
1873            ParameterValue::Date(v) => (Value::String(v.clone()), "date"),
1874            ParameterValue::Time(v) => (Value::String(v.clone()), "time"),
1875            ParameterValue::DateTime(v) => (Value::String(v.clone()), "dateTime"),
1876        };
1877        let ty = types.structure(&type_url(type_name, CORE));
1878        Ok(run.tree.primitive(value, ty, Some(type_name.to_owned())))
1879    }
1880
1881    /// A variable, or failing that a constant of the map.
1882    async fn variable(
1883        &self,
1884        run: &mut Run,
1885        group: &GroupRef,
1886        name: &str,
1887        vars: &Vars,
1888    ) -> Result<NodeId> {
1889        if let Some(node) = vars.value(name) {
1890            return Ok(node);
1891        }
1892        // A dotted id such as `src.code` is a path from a variable.
1893        if let Some((variable, path)) = name.split_once('.')
1894            && let Some(mut node) = vars.value(variable)
1895        {
1896            for segment in path.split('.') {
1897                node = *run
1898                    .tree
1899                    .children(node, segment)
1900                    .first()
1901                    .ok_or_else(|| Error::new(format!("`{name}` has no value")))?;
1902            }
1903            return Ok(node);
1904        }
1905        let values = self.constant(run, group, name).await?;
1906        match values.as_slice() {
1907            [single] => Ok(*single),
1908            [] => Err(Error::new(format!("unknown variable `{name}`"))),
1909            _ => Err(Error::new(format!("constant `{name}` has several values"))),
1910        }
1911    }
1912
1913    async fn parameter_text(
1914        &self,
1915        run: &mut Run,
1916        group: &GroupRef,
1917        parameter: &Parameter,
1918        vars: &Vars,
1919    ) -> Result<String> {
1920        if let ParameterValue::String(text) = &parameter.value {
1921            return Ok(text.clone());
1922        }
1923        let node = self.parameter_node(run, group, parameter, vars).await?;
1924        Ok(run.tree.text(node).unwrap_or_default())
1925    }
1926
1927    async fn argument_text(
1928        &self,
1929        run: &mut Run,
1930        group: &GroupRef,
1931        parameters: &[Parameter],
1932        index: usize,
1933        vars: &Vars,
1934    ) -> Result<String> {
1935        let parameter = parameters
1936            .get(index)
1937            .ok_or_else(|| Error::new(format!("missing argument {}", index + 1)))?;
1938        self.parameter_text(run, group, parameter, vars).await
1939    }
1940
1941    fn literal(&self, run: &mut Run, value: Value, type_name: &str) -> NodeId {
1942        let ty = self.types().structure(&type_url(type_name, CORE));
1943        run.tree.primitive(value, ty, Some(type_name.to_owned()))
1944    }
1945
1946    fn string(&self, run: &mut Run, text: String, type_name: &str) -> NodeId {
1947        let ty = self.types().structure(&type_url(type_name, CORE));
1948        run.tree
1949            .primitive(Value::String(text), ty, Some(type_name.to_owned()))
1950    }
1951
1952    // ---- FHIRPath ---------------------------------------------------------------------------
1953
1954    /// The value of the map's `let name = …;`, evaluated once.
1955    fn constant<'r>(
1956        &'r self,
1957        run: &'r mut Run,
1958        group: &'r GroupRef,
1959        name: &'r str,
1960    ) -> BoxFuture<'r, Result<Vec<NodeId>>> {
1961        Box::pin(async move {
1962            let key = (group.map.url.clone().unwrap_or_default(), name.to_owned());
1963            if let Some(values) = run.constants.get(&key) {
1964                return Ok(values.clone());
1965            }
1966            let Some(constant) = group.map.constants.iter().find(|c| c.name == name) else {
1967                return Ok(Vec::new());
1968            };
1969            if !run.evaluating.insert(key.clone()) {
1970                return Err(Error::new(format!("constant `{name}` refers to itself")));
1971            }
1972            let values = self
1973                .evaluate(run, group, &constant.value, None, &Vars::default())
1974                .await;
1975            run.evaluating.remove(&key);
1976            let values = values?;
1977            run.constants.insert(key, values.clone());
1978            Ok(values)
1979        })
1980    }
1981
1982    fn evaluate<'r>(
1983        &'r self,
1984        run: &'r mut Run,
1985        group: &'r GroupRef,
1986        expression: &'r str,
1987        focus: Option<NodeId>,
1988        vars: &'r Vars,
1989    ) -> BoxFuture<'r, Result<Vec<NodeId>>> {
1990        Box::pin(async move {
1991            // Only the variables the expression names: serializing every variable in scope for
1992            // every expression would copy the whole input each time.
1993            let referenced = path_start_names(expression);
1994            let mut names: HashSet<String> = vars
1995                .names()
1996                .filter(|name| referenced.contains(*name))
1997                .map(str::to_owned)
1998                .collect();
1999            let mut variables = Vec::new();
2000            for name in &names {
2001                if let Some(node) = vars.value(name) {
2002                    variables.push((name.clone(), self.item(run, node)));
2003                }
2004            }
2005            for constant in &group.map.constants {
2006                if names.contains(&constant.name) || expression == constant.value {
2007                    continue;
2008                }
2009                if referenced.contains(&constant.name) {
2010                    let values = self.constant(run, group, &constant.name).await?;
2011                    if let [value] = values.as_slice() {
2012                        variables.push((constant.name.clone(), self.item(run, *value)));
2013                        names.insert(constant.name.clone());
2014                    }
2015                }
2016            }
2017            let bound = bind_variables(expression, &names);
2018            let focus = focus.map(|node| self.item(run, node));
2019            let results = self
2020                .expressions
2021                .evaluate(&bound, focus.as_ref(), &variables)
2022                .await
2023                .map_err(|e| Error::new(format!("`{expression}`: {e}")))?;
2024            let types = self.types();
2025            Ok(results
2026                .into_iter()
2027                .map(|item| {
2028                    let ty = item
2029                        .type_name
2030                        .as_deref()
2031                        .map(|t| types.structure(&type_url(t, CORE)))
2032                        .unwrap_or(Type::Unknown);
2033                    if item.json.is_object() {
2034                        run.tree.load(&item.json, ty, &types)
2035                    } else {
2036                        run.tree.primitive(item.json, ty, item.type_name)
2037                    }
2038                })
2039                .collect())
2040        })
2041    }
2042
2043    async fn evaluate_boolean(
2044        &self,
2045        run: &mut Run,
2046        group: &GroupRef,
2047        expression: &str,
2048        focus: Option<NodeId>,
2049        vars: &Vars,
2050    ) -> Result<bool> {
2051        let values = self.evaluate(run, group, expression, focus, vars).await?;
2052        Ok(match values.as_slice() {
2053            [] => false,
2054            [single] => run.tree.value(*single) != Some(&Value::Bool(false)),
2055            _ => true,
2056        })
2057    }
2058
2059    /// A node for FHIRPath. A backbone element is named by its path (`QuestionnaireResponse.item`),
2060    /// as FHIRPath models it, so its elements keep their FHIR types.
2061    fn item(&self, run: &Run, node: NodeId) -> Item {
2062        let type_name = match run.tree.ty(node) {
2063            backbone @ Type::Node { path, .. } if !path.is_empty() => Some(backbone.display()),
2064            _ => run.tree.fhir_type(node),
2065        };
2066        Item {
2067            json: run.tree.to_json(node),
2068            type_name,
2069        }
2070    }
2071}
2072
2073/// The element path of a source or target: the element and the segments in the element
2074/// extension.
2075fn element_path(
2076    element: Option<&str>,
2077    extensions: &[fml_model::model::Extension],
2078    url: &str,
2079) -> Vec<String> {
2080    element
2081        .map(str::to_owned)
2082        .into_iter()
2083        .chain(
2084            extensions
2085                .iter()
2086                .filter(|e| e.url == url)
2087                .filter_map(|e| e.value_string.clone()),
2088        )
2089        .collect()
2090}
2091
2092/// Target list modes (the `StructureMapTargetListMode` codes; the IG leaves them to be
2093/// documented): a `first` rule's values go before the others in a repeating element and a `last`
2094/// rule's after them, one rule each per element; `single` allows one value per element.
2095fn order_target(
2096    run: &mut Run,
2097    rule: &Rule,
2098    target: &Target,
2099    dest: NodeId,
2100    element: &str,
2101    value: NodeId,
2102) -> Result<()> {
2103    let identity = std::ptr::from_ref(target) as usize;
2104    let mode = target
2105        .list_mode
2106        .iter()
2107        .find(|mode| matches!(mode.as_str(), "first" | "last" | "single"))
2108        .map(String::as_str);
2109    if mode == Some("single") && !run.single.insert((dest, element.to_owned(), identity)) {
2110        return Err(Error::in_rule(
2111            rule,
2112            format!("target `{element}` is `single`, but the rule makes more than one"),
2113        ));
2114    }
2115    let key = (dest, element.to_owned());
2116    let order = match (mode, run.order.contains_key(&key)) {
2117        (Some("first" | "last"), _) | (_, true) => run.order.entry(key).or_default(),
2118        _ => return Ok(()),
2119    };
2120    match mode {
2121        Some("first") => {
2122            if order.first.is_some_and(|first| first != identity) {
2123                return Err(Error::in_rule(
2124                    rule,
2125                    format!("two rules put their values `first` in `{element}`"),
2126                ));
2127            }
2128            order.first = Some(identity);
2129            let at = order.first_count;
2130            order.first_count += 1;
2131            run.tree.move_value(dest, element, value, Some(at));
2132        }
2133        Some("last") => {
2134            if order.last.is_some_and(|last| last != identity) {
2135                return Err(Error::in_rule(
2136                    rule,
2137                    format!("two rules put their values `last` in `{element}`"),
2138                ));
2139            }
2140            order.last = Some(identity);
2141            order.last_values.push(value);
2142        }
2143        _ => {}
2144    }
2145    let last_values = order.last_values.clone();
2146    for last in last_values {
2147        run.tree.move_value(dest, element, last, None);
2148    }
2149    Ok(())
2150}
2151
2152/// A source's `min..max`: the element must have that many values (fml.md, "Source Content").
2153fn check_cardinality(rule: &Rule, source: &Source, count: usize) -> Result<()> {
2154    let below = source
2155        .min
2156        .is_some_and(|min| i64::try_from(count).is_ok_and(|count| count < min));
2157    let above = source
2158        .max
2159        .as_deref()
2160        .and_then(|max| max.parse::<usize>().ok())
2161        .is_some_and(|max| count > max);
2162    if below || above {
2163        let path = std::iter::once(source.context.as_str())
2164            .chain(source.element.as_deref())
2165            .collect::<Vec<_>>()
2166            .join(".");
2167        return Err(Error::in_rule(
2168            rule,
2169            format!(
2170                "`{path}` has {count} values, outside {}..{}",
2171                source.min.unwrap_or(0),
2172                source.max.as_deref().unwrap_or("*")
2173            ),
2174        ));
2175    }
2176    Ok(())
2177}
2178
2179/// The key of a map's imports in the group cache: its URL and what it imports.
2180fn imports_key(map: &StructureMap) -> String {
2181    let mut key = map.url.clone().unwrap_or_default();
2182    for import in &map.import {
2183        key.push('\n');
2184        key.push_str(import);
2185    }
2186    key
2187}
2188
2189/// A dependent group argument's variable name.
2190fn parameter_name(parameter: &Parameter) -> String {
2191    match &parameter.value {
2192        ParameterValue::Id(name) | ParameterValue::String(name) => name.clone(),
2193        other => format!("{other:?}"),
2194    }
2195}
2196
2197/// The variables of a rule that copies one value to one target with nothing else to do.
2198fn simple_copy(rule: &Rule) -> Option<(&str, &str)> {
2199    let ([source], [target]) = (rule.source.as_slice(), rule.target.as_slice()) else {
2200        return None;
2201    };
2202    (target.transform.as_deref() == Some("create") && target.parameter.is_empty()).then_some(())?;
2203    Some((source.variable.as_deref()?, target.variable.as_deref()?))
2204}