1use std::collections::{HashMap, HashSet};
11use std::fmt;
12use std::future::Future;
13use std::pin::Pin;
14use std::sync::{Arc, Mutex};
15
16use fml_check::definitions::type_url;
17use fml_check::{Definitions, Lookup, Type, Types};
18use fml_model::model::{
19 Dependent, Group, Parameter, ParameterValue, Rule, Source, StructureMap, Target,
20};
21use fml_model::{SOURCE_ELEMENT_EXTENSION, TARGET_ELEMENT_EXTENSION};
22use serde_json::{Value, json};
23
24use crate::expressions::{Expressions, Item, bind_variables, path_start_names};
25use crate::terminology::{ConceptMapResolver, NoConceptMaps, translate};
26use crate::trace::{Phase, Trace, TraceEvent, TraceStatus, TraceVariable};
27use crate::tree::{NodeId, Tree};
28use crate::{datetime, transforms};
29
30type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct Error {
35 pub message: String,
36 pub trace: Option<Box<Trace>>,
38}
39
40impl Error {
41 fn new(message: impl Into<String>) -> Self {
42 Self {
43 message: message.into(),
44 trace: None,
45 }
46 }
47
48 fn in_rule(rule: &Rule, message: impl fmt::Display) -> Self {
49 match &rule.name {
50 Some(name) => Self::new(format!("rule `{name}`: {message}")),
51 None => Self::new(message.to_string()),
52 }
53 }
54}
55
56impl fmt::Display for Error {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 f.write_str(&self.message)
59 }
60}
61
62impl std::error::Error for Error {}
63
64type Result<T> = std::result::Result<T, Error>;
65
66pub trait MapResolver {
68 fn resolve(&self, url: &str) -> Vec<Arc<StructureMap>>;
69}
70
71#[derive(Debug, Default, Clone, Copy)]
73pub struct NoMaps;
74
75impl MapResolver for NoMaps {
76 fn resolve(&self, _url: &str) -> Vec<Arc<StructureMap>> {
77 Vec::new()
78 }
79}
80
81#[derive(Debug, Default, Clone)]
83pub struct MapSet {
84 maps: Vec<Arc<StructureMap>>,
85}
86
87impl MapSet {
88 pub fn new() -> Self {
89 Self::default()
90 }
91
92 pub fn add(&mut self, map: StructureMap) {
93 self.maps.push(Arc::new(map));
94 }
95}
96
97impl MapResolver for MapSet {
98 fn resolve(&self, url: &str) -> Vec<Arc<StructureMap>> {
99 self.maps
100 .iter()
101 .filter(|map| {
102 map.url
103 .as_deref()
104 .is_some_and(|candidate| match url.split_once('*') {
105 Some((prefix, suffix)) => {
106 candidate.len() > url.len()
107 && candidate.starts_with(prefix)
108 && candidate.ends_with(suffix)
109 }
110 None => candidate == url,
111 })
112 })
113 .cloned()
114 .collect()
115 }
116}
117
118#[derive(Debug, Clone, PartialEq)]
120pub struct Transformed {
121 pub target: Value,
123 pub log: Vec<String>,
125 pub warnings: Vec<String>,
128 pub produced: Vec<Value>,
131 pub trace: Option<Trace>,
133}
134
135pub struct Engine<'a, X> {
137 definitions: &'a dyn Definitions,
138 expressions: X,
139 maps: &'a dyn MapResolver,
140 concept_maps: &'a dyn ConceptMapResolver,
141 trace: bool,
142 groups: Mutex<GroupCache>,
145}
146
147type TypedTarget = (Type, Option<String>);
149
150#[derive(Default)]
152struct GroupCache {
153 by_name: HashMap<(String, String), Option<GroupRef>>,
154 by_types: HashMap<(String, String, String), Option<GroupRef>>,
155 default_target: HashMap<(String, String), Option<TypedTarget>>,
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159enum Mode {
160 Input,
161 Output,
162 Shared,
163}
164
165#[derive(Debug, Clone, Default)]
167struct Vars {
168 entries: Vec<(Mode, String, NodeId)>,
169}
170
171impl Vars {
172 fn add(&mut self, mode: Mode, name: &str, node: NodeId) {
173 self.entries.push((mode, name.to_owned(), node));
174 }
175
176 fn get(&self, mode: Mode, name: &str) -> Option<NodeId> {
177 self.entries
178 .iter()
179 .rev()
180 .find(|(m, n, _)| *m == mode && n == name)
181 .map(|(_, _, node)| *node)
182 }
183
184 fn value(&self, name: &str) -> Option<NodeId> {
186 self.get(Mode::Input, name)
187 .or_else(|| self.get(Mode::Output, name))
188 }
189
190 fn names(&self) -> impl Iterator<Item = &str> {
191 self.entries
192 .iter()
193 .filter(|(mode, _, _)| *mode != Mode::Shared)
194 .map(|(_, name, _)| name.as_str())
195 }
196}
197
198#[derive(Clone)]
200struct GroupRef {
201 map: Arc<StructureMap>,
202 index: usize,
203}
204
205impl GroupRef {
206 fn group(&self) -> &Group {
207 &self.map.group[self.index]
208 }
209}
210
211#[derive(Default)]
213struct Run {
214 tree: Tree,
215 log: Vec<String>,
216 warnings: Vec<String>,
217 depth: usize,
218 trace: Option<Vec<TraceEvent>>,
220 trace_depth: usize,
221 produced: Vec<NodeId>,
223 constants: HashMap<(String, String), Vec<NodeId>>,
225}
226
227const MAX_DEPTH: usize = 512;
228const CORE: &str = "http://hl7.org/fhir/StructureDefinition/";
229
230impl<'a, X: Expressions> Engine<'a, X> {
231 pub fn new(definitions: &'a dyn Definitions, expressions: X) -> Self {
232 Self {
233 definitions,
234 expressions,
235 maps: &NoMaps,
236 concept_maps: &NoConceptMaps,
237 trace: false,
238 groups: Mutex::new(GroupCache::default()),
239 }
240 }
241
242 fn cache(&self) -> std::sync::MutexGuard<'_, GroupCache> {
243 self.groups.lock().unwrap_or_else(|e| e.into_inner())
244 }
245
246 pub fn with_trace(mut self, trace: bool) -> Self {
248 self.trace = trace;
249 self
250 }
251
252 pub fn with_concept_maps(mut self, concept_maps: &'a dyn ConceptMapResolver) -> Self {
254 self.concept_maps = concept_maps;
255 self
256 }
257
258 pub fn with_maps(mut self, maps: &'a dyn MapResolver) -> Self {
260 self.maps = maps;
261 self
262 }
263
264 fn types(&self) -> Types<'a> {
265 Types::new(self.definitions)
266 }
267
268 pub async fn transform(&self, map: &StructureMap, source: &Value) -> Result<Transformed> {
271 self.run(map, source, None).await
272 }
273
274 pub async fn transform_into(
276 &self,
277 map: &StructureMap,
278 source: &Value,
279 target: &Value,
280 ) -> Result<Transformed> {
281 self.run(map, source, Some(target)).await
282 }
283
284 async fn run(
285 &self,
286 map: &StructureMap,
287 source: &Value,
288 target: Option<&Value>,
289 ) -> Result<Transformed> {
290 let map = Arc::new(map.clone());
291 let group = map
292 .group
293 .first()
294 .ok_or_else(|| Error::new("the map has no groups"))?;
295 let input = |mode: &str| group.input.iter().find(|input| input.mode == mode);
296 let source_input =
297 input("source").ok_or_else(|| Error::new("the first group has no source"))?;
298 let target_input =
299 input("target").ok_or_else(|| Error::new("the first group has no target"))?;
300
301 let mut run = Run {
302 trace: self.trace.then(Vec::new),
303 ..Run::default()
304 };
305 let types = self.types();
306 let source_type = self.input_type(&map, source_input.type_name.as_deref());
307 let source_node = run.tree.load(source, source_type, &types);
308 let target_type = self.input_type(&map, target_input.type_name.as_deref());
309 let target_node = match target {
310 Some(target) => run.tree.load(target, target_type, &types),
311 None => {
312 if matches!(target_type, Type::Unknown) {
313 return Err(Error::new(format!(
314 "cannot create the target: type `{}` is not known",
315 target_input.type_name.as_deref().unwrap_or("?")
316 )));
317 }
318 run.tree.create(target_type, None)
319 }
320 };
321 let mut vars = Vars::default();
322 vars.add(Mode::Input, &source_input.name, source_node);
323 vars.add(Mode::Output, &target_input.name, target_node);
324 let entry = GroupRef {
325 map: map.clone(),
326 index: 0,
327 };
328 if let Err(mut error) = self.execute_group(&mut run, entry, vars, true).await {
329 error.trace = run.trace.take().map(|events| {
330 Box::new(Trace {
331 status: TraceStatus::Stopped,
332 events,
333 })
334 });
335 return Err(error);
336 }
337 let in_target = run.tree.reachable(target_node);
338 let produced = run
339 .produced
340 .iter()
341 .filter(|node| !in_target.contains(node))
342 .map(|&node| run.tree.to_json(node))
343 .collect();
344 Ok(Transformed {
345 target: run.tree.to_json(target_node),
346 log: run.log,
347 warnings: run.warnings,
348 produced,
349 trace: run.trace.map(|events| Trace {
350 status: TraceStatus::Complete,
351 events,
352 }),
353 })
354 }
355
356 fn input_type(&self, map: &StructureMap, name: Option<&str>) -> Type {
358 let Some(name) = name else {
359 return Type::Unknown;
360 };
361 self.types().structure(&self.type_name_url(map, name, CORE))
362 }
363
364 fn type_name_url(&self, map: &StructureMap, name: &str, context: &str) -> String {
365 if let Some(structure) = map
366 .structure
367 .iter()
368 .find(|s| s.alias.as_deref() == Some(name))
369 {
370 return structure.url.clone();
371 }
372 type_url(name, context)
373 }
374
375 fn execute_group<'r>(
378 &'r self,
379 run: &'r mut Run,
380 group: GroupRef,
381 vars: Vars,
382 at_root: bool,
383 ) -> BoxFuture<'r, Result<()>> {
384 Box::pin(async move {
385 run.depth += 1;
386 if run.depth > MAX_DEPTH {
387 return Err(Error::new(format!(
388 "groups nest more than {MAX_DEPTH} deep; is a group calling itself without end?"
389 )));
390 }
391 let map = group.map.clone();
392 let definition = group.group();
393 let position = format!("StructureMap.group[{}]", group.index);
394 self.record(run, Phase::Enter, &map, &position, &vars, Vec::new());
395 if let Some(extends) = &definition.extends {
396 let base = self.group_by_name(&map, extends)?;
397 self.execute_group(run, base, vars.clone(), false).await?;
398 }
399 let mut vars = vars;
400 for (index, rule) in definition.rule.iter().enumerate() {
401 let rule_position = format!("{position}.rule[{index}]");
402 self.execute_rule(run, &group, rule, &rule_position, &mut vars, at_root)
403 .await?;
404 }
405 self.record(run, Phase::Exit, &map, &position, &vars, Vec::new());
406 run.depth -= 1;
407 Ok(())
408 })
409 }
410
411 fn group_by_name(&self, map: &Arc<StructureMap>, name: &str) -> Result<GroupRef> {
413 let key = (map.url.clone().unwrap_or_default(), name.to_owned());
414 let cached = self.cache().by_name.get(&key).cloned();
415 let found = match cached {
416 Some(found) => found,
417 None => {
418 let found = self.find_group_by_name(map, name);
419 self.cache().by_name.insert(key, found.clone());
420 found
421 }
422 };
423 found.ok_or_else(|| {
424 Error::new(format!(
425 "no group `{name}` in {} or the maps it imports",
426 map.url.as_deref().unwrap_or("the map")
427 ))
428 })
429 }
430
431 fn find_group_by_name(&self, map: &Arc<StructureMap>, name: &str) -> Option<GroupRef> {
432 if let Some(index) = map.group.iter().position(|g| g.name == name) {
433 return Some(GroupRef {
434 map: map.clone(),
435 index,
436 });
437 }
438 for import in &map.import {
439 for imported in self.maps.resolve(import) {
440 if imported.url == map.url {
441 continue;
442 }
443 if let Some(index) = imported.group.iter().position(|g| g.name == name) {
444 return Some(GroupRef {
445 map: imported.clone(),
446 index,
447 });
448 }
449 }
450 }
451 None
452 }
453
454 fn group_by_types(
457 &self,
458 map: &Arc<StructureMap>,
459 source: Option<&str>,
460 target: Option<&str>,
461 ) -> Option<GroupRef> {
462 let (source, target) = (source?, target?);
463 let key = (
464 map.url.clone().unwrap_or_default(),
465 source.to_owned(),
466 target.to_owned(),
467 );
468 if let Some(found) = self.cache().by_types.get(&key) {
469 return found.clone();
470 }
471 let found = self.find_group_by_types(map, source, target);
472 self.cache().by_types.insert(key, found.clone());
473 found
474 }
475
476 fn find_group_by_types(
477 &self,
478 map: &Arc<StructureMap>,
479 source: &str,
480 target: &str,
481 ) -> Option<GroupRef> {
482 let matching = |candidate: &Arc<StructureMap>| {
483 candidate.group.iter().position(|group| {
484 group.type_mode.is_some()
485 && matches!(group.input.as_slice(), [s, t]
486 if s.mode == "source" && t.mode == "target"
487 && self.stated_type_is(candidate, s.type_name.as_deref(), source)
488 && self.stated_type_is(candidate, t.type_name.as_deref(), target))
489 })
490 };
491 if let Some(index) = matching(map) {
492 return Some(GroupRef {
493 map: map.clone(),
494 index,
495 });
496 }
497 for import in &map.import {
498 for imported in self.maps.resolve(import) {
499 if imported.url == map.url {
500 continue;
501 }
502 if let Some(index) = matching(&imported) {
503 return Some(GroupRef {
504 map: imported,
505 index,
506 });
507 }
508 }
509 }
510 None
511 }
512
513 fn stated_type_is(&self, map: &StructureMap, stated: Option<&str>, actual: &str) -> bool {
514 let Some(stated) = stated else { return false };
515 if stated == actual {
516 return true;
517 }
518 let url = self.type_name_url(map, stated, CORE);
519 self.definitions
520 .structure(&url)
521 .is_some_and(|s| s.type_name == actual)
522 }
523
524 fn execute_rule<'r>(
527 &'r self,
528 run: &'r mut Run,
529 group: &'r GroupRef,
530 rule: &'r Rule,
531 position: &'r str,
532 vars: &'r mut Vars,
533 at_root: bool,
534 ) -> BoxFuture<'r, Result<()>> {
535 Box::pin(async move {
536 self.record(run, Phase::Enter, &group.map, position, vars, Vec::new());
537 let mut bindings = vec![vars.clone()];
538 for (index, source) in rule.source.iter().enumerate() {
539 let logged = run.log.len();
540 let mut next = Vec::new();
541 for binding in &bindings {
542 next.extend(
543 self.process_source(run, group, rule, source, binding)
544 .await?,
545 );
546 }
547 bindings = next;
548 if run.trace.is_some() {
549 let messages = run.log[logged..].to_vec();
550 let shown = bindings.first().cloned().unwrap_or_else(|| vars.clone());
551 let step = format!("{position}.source[{index}]");
552 self.record(run, Phase::Step, &group.map, &step, &shown, messages);
553 }
554 }
555 let source_variable = match rule.source.as_slice() {
556 [single] => single.variable.as_deref(),
557 _ => None,
558 };
559 for mut binding in bindings {
560 for (index, target) in rule.target.iter().enumerate() {
561 self.process_target(
562 run,
563 group,
564 rule,
565 target,
566 &mut binding,
567 vars,
568 source_variable,
569 at_root,
570 )
571 .await?;
572 if run.trace.is_some() {
573 let step = format!("{position}.target[{index}]");
574 self.record(run, Phase::Step, &group.map, &step, &binding, Vec::new());
575 }
576 }
577 if !rule.rule.is_empty() {
578 for (index, child) in rule.rule.iter().enumerate() {
579 let child_position = format!("{position}.rule[{index}]");
580 self.execute_rule(run, group, child, &child_position, &mut binding, false)
581 .await?;
582 }
583 } else if !rule.dependent.is_empty() {
584 for dependent in &rule.dependent {
585 self.execute_dependency(run, group, rule, dependent, &binding)
586 .await?;
587 }
588 } else if let Some((source, target)) = simple_copy(rule) {
589 self.default_copy(run, group, rule, source, target, &binding)
590 .await?;
591 }
592 }
593 self.record(run, Phase::Exit, &group.map, position, vars, Vec::new());
594 Ok(())
595 })
596 }
597
598 fn record(
600 &self,
601 run: &mut Run,
602 phase: Phase,
603 map: &StructureMap,
604 position: &str,
605 vars: &Vars,
606 messages: Vec<String>,
607 ) {
608 if run.trace.is_none() {
609 return;
610 }
611 if phase == Phase::Exit {
612 run.trace_depth = run.trace_depth.saturating_sub(1);
613 }
614 let mut seen = HashSet::new();
615 let mut variables: Vec<TraceVariable> = vars
616 .entries
617 .iter()
618 .rev()
619 .filter(|(mode, name, _)| *mode != Mode::Shared && seen.insert(name.clone()))
620 .map(|(mode, name, node)| TraceVariable {
621 name: name.clone(),
622 context_type: match mode {
623 Mode::Input => "input",
624 _ => "output",
625 }
626 .to_owned(),
627 value: run.tree.to_json(*node),
628 data_type: self.item(run, *node).type_name,
629 })
630 .collect();
631 variables.reverse();
632 let event = TraceEvent {
633 phase,
634 depth: run.trace_depth,
635 source: map.url.clone(),
636 position: position.to_owned(),
637 variables,
638 messages,
639 };
640 if let Some(events) = &mut run.trace {
641 events.push(event);
642 }
643 if phase == Phase::Enter {
644 run.trace_depth += 1;
645 }
646 }
647
648 async fn default_copy(
651 &self,
652 run: &mut Run,
653 group: &GroupRef,
654 rule: &Rule,
655 source: &str,
656 target: &str,
657 vars: &Vars,
658 ) -> Result<()> {
659 let source = vars
660 .get(Mode::Input, source)
661 .ok_or_else(|| Error::in_rule(rule, format!("no source variable `{source}`")))?;
662 let target = vars
663 .get(Mode::Output, target)
664 .ok_or_else(|| Error::in_rule(rule, format!("no target variable `{target}`")))?;
665 let source_type = run.tree.fhir_type(source);
666 let target_type = run.tree.fhir_type(target);
667 if let Some(default) =
668 self.group_by_types(&group.map, source_type.as_deref(), target_type.as_deref())
669 {
670 let inputs = &default.group().input;
671 let mut call = Vars::default();
672 call.add(Mode::Input, &inputs[0].name, source);
673 call.add(Mode::Output, &inputs[1].name, target);
674 return self.execute_group(run, default, call, false).await;
675 }
676 if source_type == target_type || target_type.is_none() {
677 run.tree.assign(target, source);
678 return Ok(());
679 }
680 Err(Error::in_rule(
681 rule,
682 format!(
683 "no default group maps {} to {}",
684 source_type.as_deref().unwrap_or("?"),
685 target_type.as_deref().unwrap_or("?")
686 ),
687 ))
688 }
689
690 async fn execute_dependency(
691 &self,
692 run: &mut Run,
693 group: &GroupRef,
694 rule: &Rule,
695 dependent: &Dependent,
696 vars: &Vars,
697 ) -> Result<()> {
698 let callee = self.group_by_name(&group.map, &dependent.name)?;
699 let inputs = &callee.group().input;
700 if inputs.len() != dependent.parameter.len() {
701 return Err(Error::in_rule(
702 rule,
703 format!(
704 "group `{}` takes {} arguments, the call passes {}",
705 dependent.name,
706 inputs.len(),
707 dependent.parameter.len()
708 ),
709 ));
710 }
711 let mut call = Vars::default();
712 for (input, parameter) in inputs.iter().zip(&dependent.parameter) {
713 let name = parameter_name(parameter);
714 let (mode, node) = if input.mode == "source" {
715 (Mode::Input, vars.value(&name))
716 } else {
717 (Mode::Output, vars.get(Mode::Output, &name))
718 };
719 let node = node.ok_or_else(|| {
720 Error::in_rule(
721 rule,
722 format!(
723 "`{}` passes `{name}` as {} `{}`, but it has no value",
724 dependent.name, input.mode, input.name
725 ),
726 )
727 })?;
728 call.add(mode, &input.name, node);
729 }
730 self.execute_group(run, callee, call, false).await
731 }
732
733 async fn process_source(
736 &self,
737 run: &mut Run,
738 group: &GroupRef,
739 rule: &Rule,
740 source: &Source,
741 vars: &Vars,
742 ) -> Result<Vec<Vars>> {
743 let context = vars.get(Mode::Input, &source.context).ok_or_else(|| {
744 Error::in_rule(
745 rule,
746 format!("unknown source variable `{}`", source.context),
747 )
748 })?;
749 let mut items = vec![context];
750 for element in element_path(
751 source.element.as_deref(),
752 &source.extension,
753 SOURCE_ELEMENT_EXTENSION,
754 ) {
755 items = items
756 .iter()
757 .flat_map(|&item| run.tree.children(item, &element))
758 .collect();
759 }
760 if items.is_empty()
761 && let Some(default) = &source.default_value
762 {
763 items = self.evaluate(run, group, default, None, vars).await?;
764 }
765 if let Some(type_name) = &source.type_name {
766 items.retain(|&item| run.tree.fhir_type(item).as_deref() == Some(type_name.as_str()));
767 }
768 let with_item = |item: NodeId| {
769 let mut bound = vars.clone();
770 if let Some(variable) = &source.variable {
771 bound.add(Mode::Input, variable, item);
772 }
773 bound
774 };
775 if let Some(condition) = &source.condition {
776 let mut kept = Vec::with_capacity(items.len());
777 for item in items {
778 if self
779 .evaluate_boolean(run, group, condition, Some(item), &with_item(item))
780 .await?
781 {
782 kept.push(item);
783 }
784 }
785 items = kept;
786 }
787 if let Some(check) = &source.check {
788 for &item in &items {
789 if !self
790 .evaluate_boolean(run, group, check, Some(item), &with_item(item))
791 .await?
792 {
793 return Err(Error::in_rule(rule, format!("check `{check}` failed")));
794 }
795 }
796 }
797 if let Some(log) = &source.log_message {
798 let mut messages = Vec::new();
799 for &item in &items {
800 let values = self
801 .evaluate(run, group, log, Some(item), &with_item(item))
802 .await?;
803 messages.extend(values.into_iter().filter_map(|v| run.tree.text(v)));
804 }
805 if !messages.is_empty() {
806 run.log.push(messages.join(","));
807 }
808 }
809 match source.list_mode.as_deref() {
810 _ if items.is_empty() => {}
811 Some("first") => items.truncate(1),
812 Some("not_first") => {
813 items.remove(0);
814 }
815 Some("last") => items = vec![*items.last().unwrap()],
816 Some("not_last") => {
817 items.pop();
818 }
819 Some("only_one") if items.len() > 1 => {
820 return Err(Error::in_rule(
821 rule,
822 format!("`{}` has more than one item", source.context),
823 ));
824 }
825 _ => {}
826 }
827 Ok(items.into_iter().map(with_item).collect())
828 }
829
830 #[allow(clippy::too_many_arguments)]
833 async fn process_target(
834 &self,
835 run: &mut Run,
836 group: &GroupRef,
837 rule: &Rule,
838 target: &Target,
839 vars: &mut Vars,
840 shared: &mut Vars,
841 source_variable: Option<&str>,
842 at_root: bool,
843 ) -> Result<()> {
844 let types = self.types();
845 let elements = element_path(
846 target.element.as_deref(),
847 &target.extension,
848 TARGET_ELEMENT_EXTENSION,
849 );
850 let element = elements.last().cloned();
851 let mut destination = match &target.context {
852 Some(context) => Some(vars.get(Mode::Output, context).ok_or_else(|| {
853 Error::in_rule(rule, format!("unknown target variable `{context}`"))
854 })?),
855 None => None,
856 };
857 let sharing = target.list_mode.iter().any(|m| m == "share");
858 let mut value = match (&target.transform, sharing, &target.list_rule_id) {
859 (None, true, Some(id)) => shared.get(Mode::Shared, id),
860 _ => None,
861 };
862 if let Some(dest) = destination
863 && value.is_none()
864 {
865 let mut dest = dest;
866 for intermediate in elements.iter().take(elements.len().saturating_sub(1)) {
867 dest = run
868 .tree
869 .make_property(dest, intermediate, &types)
870 .map_err(|e| Error::in_rule(rule, e))?;
871 }
872 destination = Some(dest);
873 }
874 if target.transform.is_some() {
875 value = self
876 .run_transform(
877 run,
878 group,
879 rule,
880 target,
881 vars,
882 destination,
883 element.as_deref(),
884 source_variable,
885 at_root,
886 )
887 .await?;
888 if let (Some(produced), Some(dest)) = (value, destination) {
889 match &element {
890 Some(element) => run
891 .tree
892 .set_property(dest, element, produced, &types)
893 .map_err(|e| Error::in_rule(rule, e))?,
894 None => run.tree.assign(dest, produced),
895 }
896 }
897 } else if let (Some(dest), None) = (destination, value) {
898 value = Some(match (&element, sharing, &target.list_rule_id) {
899 (Some(element), true, Some(id)) => {
900 let created = run
901 .tree
902 .make_property(dest, element, &types)
903 .map_err(|e| Error::in_rule(rule, e))?;
904 shared.add(Mode::Shared, id, created);
905 created
906 }
907 (Some(element), _, _) => run
908 .tree
909 .make_property(dest, element, &types)
910 .map_err(|e| Error::in_rule(rule, e))?,
911 (None, _, _) => dest,
912 });
913 }
914 if let (Some(variable), Some(value)) = (&target.variable, value) {
915 vars.add(Mode::Output, variable, value);
916 }
917 Ok(())
918 }
919
920 #[allow(clippy::too_many_arguments)]
921 async fn run_transform(
922 &self,
923 run: &mut Run,
924 group: &GroupRef,
925 rule: &Rule,
926 target: &Target,
927 vars: &Vars,
928 destination: Option<NodeId>,
929 element: Option<&str>,
930 source_variable: Option<&str>,
931 _at_root: bool,
932 ) -> Result<Option<NodeId>> {
933 let transform = target.transform.as_deref().unwrap_or("copy");
934 let parameters = &target.parameter;
935 let fail = |message: String| Error::in_rule(rule, message);
936 let types = self.types();
937 match transform {
938 "create" => {
939 let ty = match parameters.first() {
940 Some(parameter) => {
941 let name = self.parameter_text(run, group, parameter, vars).await?;
942 let context = destination
943 .and_then(|d| run.tree.ty(d).structure().map(|s| s.url.clone()))
944 .unwrap_or_else(|| CORE.to_owned());
945 let ty = types.structure(&self.type_name_url(&group.map, &name, &context));
946 (ty, Some(name))
947 }
948 None => self
949 .implied_type(run, group, destination, element, source_variable, vars)
950 .map_err(fail)?,
951 };
952 let created = run.tree.create(ty.0, ty.1);
953 if destination.is_none() && run.tree.ty(created).is_resource() {
955 run.produced.push(created);
956 }
957 Ok(Some(created))
958 }
959 "copy" => {
960 let parameter = parameters
961 .first()
962 .ok_or_else(|| fail("copy needs a value".into()))?;
963 let node = self.parameter_node(run, group, parameter, vars).await?;
964 Ok(Some(run.tree.deep_copy(node)))
965 }
966 "evaluate" => {
967 let expression = match parameters.last().map(|p| &p.value) {
968 Some(ParameterValue::String(expression)) => expression.clone(),
969 _ => return Err(fail("evaluate needs an expression".into())),
970 };
971 let focus = if parameters.len() == 2 {
972 Some(
973 self.parameter_node(run, group, ¶meters[0], vars)
974 .await?,
975 )
976 } else {
977 None
978 };
979 let values = self.evaluate(run, group, &expression, focus, vars).await?;
980 match values.as_slice() {
981 [] => Ok(None),
982 [single] => Ok(Some(*single)),
983 many => Err(fail(format!(
984 "`{expression}` gave {} values where one was expected",
985 many.len()
986 ))),
987 }
988 }
989 "truncate" => {
990 let text = self.argument_text(run, group, parameters, 0, vars).await?;
991 let length = self.argument_text(run, group, parameters, 1, vars).await?;
992 let truncated = match length.parse::<usize>() {
993 Ok(length) => text.chars().take(length).collect(),
994 Err(_) => text,
995 };
996 Ok(Some(self.string(run, truncated, "string")))
997 }
998 "cast" => {
999 let text = self.argument_text(run, group, parameters, 0, vars).await?;
1000 let type_name = if parameters.len() > 1 {
1001 self.argument_text(run, group, parameters, 1, vars).await?
1002 } else {
1003 self.implied_type(run, group, destination, element, None, vars)
1005 .ok()
1006 .and_then(|(_, name)| name)
1007 .ok_or_else(|| fail("cast needs a type: cast(value, 'type')".into()))?
1008 };
1009 let value = transforms::cast(&text, &type_name).map_err(fail)?;
1010 let ty = types.structure(&type_url(&type_name, CORE));
1011 Ok(Some(run.tree.primitive(value, ty, Some(type_name))))
1012 }
1013 "append" => {
1014 let mut text = String::new();
1015 for index in 0..parameters.len() {
1016 text.push_str(
1017 &self
1018 .argument_text(run, group, parameters, index, vars)
1019 .await?,
1020 );
1021 }
1022 Ok(Some(self.string(run, text, "string")))
1023 }
1024 "translate" => self.translate(run, group, rule, parameters, vars).await,
1025 "reference" | "pointer" => {
1026 let parameter = parameters
1027 .first()
1028 .ok_or_else(|| fail(format!("{transform} needs a resource")))?;
1029 let resource = self.parameter_node(run, group, parameter, vars).await?;
1030 let resource_type = run
1031 .tree
1032 .fhir_type(resource)
1033 .filter(|_| {
1034 run.tree.ty(resource).is_resource() || !run.tree.is_primitive(resource)
1035 })
1036 .ok_or_else(|| fail(format!("{transform} needs a resource")))?;
1037 let id = match run
1038 .tree
1039 .children(resource, "id")
1040 .first()
1041 .and_then(|&id| run.tree.text(id))
1042 {
1043 Some(id) => id,
1044 None => {
1045 let id = uuid::Uuid::new_v4().to_string();
1046 run.tree
1047 .set_text(resource, "id", &id, &types)
1048 .map_err(fail)?;
1049 id
1050 }
1051 };
1052 Ok(Some(if transform == "reference" {
1053 self.string(run, format!("{resource_type}/{id}"), "string")
1054 } else {
1055 self.string(run, format!("urn:uuid:{id}"), "uri")
1056 }))
1057 }
1058 "uuid" => Ok(Some(self.string(
1059 run,
1060 uuid::Uuid::new_v4().to_string(),
1061 "id",
1062 ))),
1063 "toDateTime" | "toDate" | "toTime" => {
1064 let text = self.argument_text(run, group, parameters, 0, vars).await?;
1065 let format = match parameters.get(1) {
1066 Some(_) => Some(self.argument_text(run, group, parameters, 1, vars).await?),
1067 None => None,
1068 };
1069 let (value, type_name) = match transform {
1070 "toDateTime" => (datetime::to_date_time(&text, format.as_deref()), "dateTime"),
1071 "toDate" => (datetime::to_date(&text, format.as_deref()), "date"),
1072 _ => (datetime::to_time(&text, format.as_deref()), "time"),
1073 };
1074 Ok(Some(self.string(run, value.map_err(fail)?, type_name)))
1075 }
1076 "unixToDateTime" | "unixToDate" | "unixToTime" => {
1077 let text = self.argument_text(run, group, parameters, 0, vars).await?;
1078 let seconds: i64 = text
1079 .trim()
1080 .parse()
1081 .map_err(|_| fail(format!("`{text}` is not a Unix timestamp in seconds")))?;
1082 let timezone = match parameters.get(1) {
1083 Some(_) => Some(self.argument_text(run, group, parameters, 1, vars).await?),
1084 None => None,
1085 };
1086 let (value, type_name) = match transform {
1087 "unixToDateTime" => (
1088 datetime::unix_to_date_time(seconds, timezone.as_deref()),
1089 "dateTime",
1090 ),
1091 "unixToDate" => (datetime::unix_to_date(seconds, timezone.as_deref()), "date"),
1092 _ => (datetime::unix_to_time(seconds, timezone.as_deref()), "time"),
1093 };
1094 Ok(Some(self.string(run, value.map_err(fail)?, type_name)))
1095 }
1096 "escape" => {
1097 let text = self.argument_text(run, group, parameters, 0, vars).await?;
1098 let (from, to) = match parameters.len() {
1099 3 => (
1100 self.argument_text(run, group, parameters, 1, vars).await?,
1101 self.argument_text(run, group, parameters, 2, vars).await?,
1102 ),
1103 _ => (
1104 "none".to_owned(),
1105 self.argument_text(run, group, parameters, 1, vars).await?,
1106 ),
1107 };
1108 let escaped = datetime::escape(&text, &from, &to).map_err(fail)?;
1109 Ok(Some(self.string(run, escaped, "string")))
1110 }
1111 "cc" | "c" | "id" | "cp" | "qty" => {
1112 let mut arguments = Vec::with_capacity(parameters.len());
1113 for index in 0..parameters.len() {
1114 arguments.push(
1115 self.argument_text(run, group, parameters, index, vars)
1116 .await?,
1117 );
1118 }
1119 let (json, type_name) = match transform {
1120 "cc" => (transforms::codeable_concept(&arguments), "CodeableConcept"),
1121 "c" => match arguments.as_slice() {
1122 [system, code, rest @ ..] => {
1123 (transforms::coding(system, code, rest.first()), "Coding")
1124 }
1125 _ => return Err(fail("c needs a system and a code".into())),
1126 },
1127 "id" => (transforms::identifier(&arguments), "Identifier"),
1128 "cp" => (transforms::contact_point(&arguments), "ContactPoint"),
1129 _ => (transforms::quantity(&arguments).map_err(fail)?, "Quantity"),
1130 };
1131 let context = destination
1132 .and_then(|d| run.tree.ty(d).structure().map(|s| s.url.clone()))
1133 .unwrap_or_else(|| CORE.to_owned());
1134 let ty = types.structure(&type_url(type_name, &context));
1135 let node = run.tree.load(&json, ty, &types);
1136 Ok(Some(node))
1137 }
1138 other => Err(fail(format!("the {other} transform is not supported"))),
1139 }
1140 }
1141
1142 fn implied_type(
1145 &self,
1146 run: &Run,
1147 group: &GroupRef,
1148 destination: Option<NodeId>,
1149 element: Option<&str>,
1150 source_variable: Option<&str>,
1151 vars: &Vars,
1152 ) -> std::result::Result<(Type, Option<String>), String> {
1153 let types = self.types();
1154 if let (Some(dest), Some(element)) = (destination, element)
1155 && let Lookup::Found(found) = types.lookup(run.tree.ty(dest), element)
1156 && let [single] = found.element.types.as_slice()
1157 && single != "Resource"
1158 && single != "*"
1159 {
1160 return Ok((types.element_type(&found, None), Some(single.clone())));
1161 }
1162 if let Some(source) = source_variable.and_then(|s| vars.get(Mode::Input, s)) {
1163 if let Some(source_type) = run.tree.fhir_type(source)
1166 && let Some(produced) = self.default_target_type(group, &source_type)
1167 {
1168 return Ok(produced);
1169 }
1170 return Ok((run.tree.ty(source).clone(), run.tree.fhir_type(source)));
1171 }
1172 Err("cannot tell what type to create: name it, as in create('Type')".into())
1173 }
1174
1175 fn default_target_type(&self, group: &GroupRef, source_type: &str) -> Option<TypedTarget> {
1178 let key = (
1179 group.map.url.clone().unwrap_or_default(),
1180 source_type.to_owned(),
1181 );
1182 if let Some(found) = self.cache().default_target.get(&key) {
1183 return found.clone();
1184 }
1185 let found = self.find_default_target_type(group, source_type);
1186 self.cache().default_target.insert(key, found.clone());
1187 found
1188 }
1189
1190 fn find_default_target_type(&self, group: &GroupRef, source_type: &str) -> Option<TypedTarget> {
1191 let target_of = |map: &Arc<StructureMap>| {
1192 map.group.iter().find_map(|candidate| {
1193 if candidate.type_mode.as_deref() != Some("type-and-types") {
1194 return None;
1195 }
1196 let [source, target] = candidate.input.as_slice() else {
1197 return None;
1198 };
1199 if source.mode != "source"
1200 || target.mode != "target"
1201 || !self.stated_type_is(map, source.type_name.as_deref(), source_type)
1202 {
1203 return None;
1204 }
1205 let stated = target.type_name.as_deref()?;
1206 let ty = self
1207 .types()
1208 .structure(&self.type_name_url(map, stated, CORE));
1209 let name = ty.fhir_type().unwrap_or_else(|| stated.to_owned());
1210 Some((ty, Some(name)))
1211 })
1212 };
1213 if let Some(found) = target_of(&group.map) {
1214 return Some(found);
1215 }
1216 group.map.import.iter().find_map(|import| {
1217 self.maps
1218 .resolve(import)
1219 .iter()
1220 .filter(|imported| imported.url != group.map.url)
1221 .find_map(target_of)
1222 })
1223 }
1224
1225 async fn translate(
1228 &self,
1229 run: &mut Run,
1230 group: &GroupRef,
1231 rule: &Rule,
1232 parameters: &[Parameter],
1233 vars: &Vars,
1234 ) -> Result<Option<NodeId>> {
1235 let fail = |message: String| Error::in_rule(rule, message);
1236 let source = self
1237 .parameter_node(
1238 run,
1239 group,
1240 parameters
1241 .first()
1242 .ok_or_else(|| fail("translate needs a value".into()))?,
1243 vars,
1244 )
1245 .await?;
1246 let map_url = self.argument_text(run, group, parameters, 1, vars).await?;
1247 let output = self.argument_text(run, group, parameters, 2, vars).await?;
1248 let concept_map = match map_url.strip_prefix('#') {
1249 Some(id) => group
1250 .map
1251 .contained
1252 .iter()
1253 .find(|r| r["resourceType"] == "ConceptMap" && r["id"] == id)
1254 .cloned()
1255 .map(Arc::new)
1256 .ok_or_else(|| fail(format!("no contained ConceptMap `{id}`")))?,
1257 None => self
1258 .concept_maps
1259 .resolve(&map_url)
1260 .ok_or_else(|| fail(format!("ConceptMap `{map_url}` not found")))?,
1261 };
1262 let codes = self.codes_of(run, source);
1263 let mut translation = None;
1264 for (system, code) in &codes {
1265 if let Some(found) = translate(&concept_map, system.as_deref(), code)
1266 .map_err(|e| fail(format!("{e} in `{map_url}`")))?
1267 {
1268 translation = Some(found);
1269 break;
1270 }
1271 }
1272 let Some(translation) = translation else {
1273 let shown: Vec<&str> = codes.iter().map(|(_, c)| c.as_str()).collect();
1274 let rule_name = rule.name.as_deref().unwrap_or("?");
1275 run.warnings.push(format!(
1276 "rule `{rule_name}`: `{}` has no translation in `{map_url}`; left out",
1277 shown.join(", ")
1278 ));
1279 return Ok(None);
1280 };
1281
1282 let types = self.types();
1283 let mut coding = serde_json::Map::new();
1284 if let Some(system) = &translation.system {
1285 coding.insert("system".into(), system.clone().into());
1286 }
1287 if let Some(code) = &translation.code {
1288 coding.insert("code".into(), code.clone().into());
1289 }
1290 if let Some(display) = &translation.display {
1291 coding.insert("display".into(), display.clone().into());
1292 }
1293 let text = |value: &Option<String>| value.clone().map_or(Value::Null, Value::String);
1294 let (json, type_name) = match output.as_str() {
1295 "code" => (text(&translation.code), "code"),
1296 "system" => (text(&translation.system), "uri"),
1297 "display" => (text(&translation.display), "string"),
1298 "Coding" => (Value::Object(coding), "Coding"),
1299 "CodeableConcept" => (
1300 json!({ "coding": [Value::Object(coding)] }),
1301 "CodeableConcept",
1302 ),
1303 other => return Err(fail(format!("unknown translate output `{other}`"))),
1304 };
1305 let ty = types.structure(&type_url(type_name, CORE));
1306 Ok(Some(if json.is_object() {
1307 run.tree.load(&json, ty, &types)
1308 } else {
1309 run.tree.primitive(json, ty, Some(type_name.to_owned()))
1310 }))
1311 }
1312
1313 fn codes_of(&self, run: &Run, node: NodeId) -> Vec<(Option<String>, String)> {
1315 let text = |id: NodeId, name: &str| {
1316 run.tree
1317 .children(id, name)
1318 .first()
1319 .and_then(|&child| run.tree.text(child))
1320 };
1321 if run.tree.is_primitive(node) {
1322 return run
1323 .tree
1324 .text(node)
1325 .map(|code| (None, code))
1326 .into_iter()
1327 .collect();
1328 }
1329 let codings = run.tree.children(node, "coding");
1330 let codings = if codings.is_empty() {
1331 vec![node]
1332 } else {
1333 codings
1334 };
1335 codings
1336 .into_iter()
1337 .filter_map(|coding| Some((text(coding, "system"), text(coding, "code")?)))
1338 .collect()
1339 }
1340
1341 async fn parameter_node(
1345 &self,
1346 run: &mut Run,
1347 group: &GroupRef,
1348 parameter: &Parameter,
1349 vars: &Vars,
1350 ) -> Result<NodeId> {
1351 let types = self.types();
1352 let (value, type_name) = match ¶meter.value {
1353 ParameterValue::Id(name) => return self.variable(run, group, name, vars).await,
1354 ParameterValue::String(v) => (Value::String(v.clone()), "string"),
1355 ParameterValue::Boolean(v) => (Value::Bool(*v), "boolean"),
1356 ParameterValue::Integer(v) => (Value::from(*v), "integer"),
1357 ParameterValue::Decimal(v) => (Value::Number(v.clone()), "decimal"),
1358 ParameterValue::Date(v) => (Value::String(v.clone()), "date"),
1359 ParameterValue::Time(v) => (Value::String(v.clone()), "time"),
1360 ParameterValue::DateTime(v) => (Value::String(v.clone()), "dateTime"),
1361 };
1362 let ty = types.structure(&type_url(type_name, CORE));
1363 Ok(run.tree.primitive(value, ty, Some(type_name.to_owned())))
1364 }
1365
1366 async fn variable(
1368 &self,
1369 run: &mut Run,
1370 group: &GroupRef,
1371 name: &str,
1372 vars: &Vars,
1373 ) -> Result<NodeId> {
1374 if let Some(node) = vars.value(name) {
1375 return Ok(node);
1376 }
1377 if let Some((variable, path)) = name.split_once('.')
1379 && let Some(mut node) = vars.value(variable)
1380 {
1381 for segment in path.split('.') {
1382 node = *run
1383 .tree
1384 .children(node, segment)
1385 .first()
1386 .ok_or_else(|| Error::new(format!("`{name}` has no value")))?;
1387 }
1388 return Ok(node);
1389 }
1390 let values = self.constant(run, group, name).await?;
1391 match values.as_slice() {
1392 [single] => Ok(*single),
1393 [] => Err(Error::new(format!("unknown variable `{name}`"))),
1394 _ => Err(Error::new(format!("constant `{name}` has several values"))),
1395 }
1396 }
1397
1398 async fn parameter_text(
1399 &self,
1400 run: &mut Run,
1401 group: &GroupRef,
1402 parameter: &Parameter,
1403 vars: &Vars,
1404 ) -> Result<String> {
1405 if let ParameterValue::String(text) = ¶meter.value {
1406 return Ok(text.clone());
1407 }
1408 let node = self.parameter_node(run, group, parameter, vars).await?;
1409 Ok(run.tree.text(node).unwrap_or_default())
1410 }
1411
1412 async fn argument_text(
1413 &self,
1414 run: &mut Run,
1415 group: &GroupRef,
1416 parameters: &[Parameter],
1417 index: usize,
1418 vars: &Vars,
1419 ) -> Result<String> {
1420 let parameter = parameters
1421 .get(index)
1422 .ok_or_else(|| Error::new(format!("missing argument {}", index + 1)))?;
1423 self.parameter_text(run, group, parameter, vars).await
1424 }
1425
1426 fn string(&self, run: &mut Run, text: String, type_name: &str) -> NodeId {
1427 let ty = self.types().structure(&type_url(type_name, CORE));
1428 run.tree
1429 .primitive(Value::String(text), ty, Some(type_name.to_owned()))
1430 }
1431
1432 fn constant<'r>(
1436 &'r self,
1437 run: &'r mut Run,
1438 group: &'r GroupRef,
1439 name: &'r str,
1440 ) -> BoxFuture<'r, Result<Vec<NodeId>>> {
1441 Box::pin(async move {
1442 let key = (group.map.url.clone().unwrap_or_default(), name.to_owned());
1443 if let Some(values) = run.constants.get(&key) {
1444 return Ok(values.clone());
1445 }
1446 let Some(constant) = group.map.constants.iter().find(|c| c.name == name) else {
1447 return Ok(Vec::new());
1448 };
1449 let values = self
1450 .evaluate(run, group, &constant.value, None, &Vars::default())
1451 .await?;
1452 run.constants.insert(key, values.clone());
1453 Ok(values)
1454 })
1455 }
1456
1457 fn evaluate<'r>(
1458 &'r self,
1459 run: &'r mut Run,
1460 group: &'r GroupRef,
1461 expression: &'r str,
1462 focus: Option<NodeId>,
1463 vars: &'r Vars,
1464 ) -> BoxFuture<'r, Result<Vec<NodeId>>> {
1465 Box::pin(async move {
1466 let referenced = path_start_names(expression);
1469 let mut names: HashSet<String> = vars
1470 .names()
1471 .filter(|name| referenced.contains(*name))
1472 .map(str::to_owned)
1473 .collect();
1474 let mut variables = Vec::new();
1475 for name in &names {
1476 if let Some(node) = vars.value(name) {
1477 variables.push((name.clone(), self.item(run, node)));
1478 }
1479 }
1480 for constant in &group.map.constants {
1481 if names.contains(&constant.name) || expression == constant.value {
1482 continue;
1483 }
1484 if referenced.contains(&constant.name) {
1485 let values = self.constant(run, group, &constant.name).await?;
1486 if let [value] = values.as_slice() {
1487 variables.push((constant.name.clone(), self.item(run, *value)));
1488 names.insert(constant.name.clone());
1489 }
1490 }
1491 }
1492 let bound = bind_variables(expression, &names);
1493 let focus = focus.map(|node| self.item(run, node));
1494 let results = self
1495 .expressions
1496 .evaluate(&bound, focus.as_ref(), &variables)
1497 .await
1498 .map_err(|e| Error::new(format!("`{expression}`: {e}")))?;
1499 let types = self.types();
1500 Ok(results
1501 .into_iter()
1502 .map(|item| {
1503 let ty = item
1504 .type_name
1505 .as_deref()
1506 .map(|t| types.structure(&type_url(t, CORE)))
1507 .unwrap_or(Type::Unknown);
1508 if item.json.is_object() {
1509 run.tree.load(&item.json, ty, &types)
1510 } else {
1511 run.tree.primitive(item.json, ty, item.type_name)
1512 }
1513 })
1514 .collect())
1515 })
1516 }
1517
1518 async fn evaluate_boolean(
1519 &self,
1520 run: &mut Run,
1521 group: &GroupRef,
1522 expression: &str,
1523 focus: Option<NodeId>,
1524 vars: &Vars,
1525 ) -> Result<bool> {
1526 let values = self.evaluate(run, group, expression, focus, vars).await?;
1527 Ok(match values.as_slice() {
1528 [] => false,
1529 [single] => run.tree.value(*single) != Some(&Value::Bool(false)),
1530 _ => true,
1531 })
1532 }
1533
1534 fn item(&self, run: &Run, node: NodeId) -> Item {
1537 let type_name = match run.tree.ty(node) {
1538 backbone @ Type::Node { path, .. } if !path.is_empty() => Some(backbone.display()),
1539 _ => run.tree.fhir_type(node),
1540 };
1541 Item {
1542 json: run.tree.to_json(node),
1543 type_name,
1544 }
1545 }
1546}
1547
1548fn element_path(
1551 element: Option<&str>,
1552 extensions: &[fml_model::model::Extension],
1553 url: &str,
1554) -> Vec<String> {
1555 element
1556 .map(str::to_owned)
1557 .into_iter()
1558 .chain(
1559 extensions
1560 .iter()
1561 .filter(|e| e.url == url)
1562 .filter_map(|e| e.value_string.clone()),
1563 )
1564 .collect()
1565}
1566
1567fn parameter_name(parameter: &Parameter) -> String {
1569 match ¶meter.value {
1570 ParameterValue::Id(name) | ParameterValue::String(name) => name.clone(),
1571 other => format!("{other:?}"),
1572 }
1573}
1574
1575fn simple_copy(rule: &Rule) -> Option<(&str, &str)> {
1577 let ([source], [target]) = (rule.source.as_slice(), rule.target.as_slice()) else {
1578 return None;
1579 };
1580 (target.transform.as_deref() == Some("create") && target.parameter.is_empty()).then_some(())?;
1581 Some((source.variable.as_deref()?, target.variable.as_deref()?))
1582}