1use serde_json::{Map, Value, json};
18
19use crate::model::StructureMap;
20
21#[derive(Debug, Clone, PartialEq)]
23pub struct R4Map {
24 pub json: Value,
25 pub dropped: Vec<String>,
28}
29
30pub fn to_r4(map: &StructureMap) -> R4Map {
32 let mut json = serde_json::to_value(map).expect("a StructureMap serializes");
33 let mut dropped = Vec::new();
34 let object = json.as_object_mut().expect("a StructureMap is an object");
35 if let Some(Value::Array(constants)) = object.remove("const") {
36 for constant in constants {
37 dropped.push(format!(
38 "const `{}`",
39 constant["name"].as_str().unwrap_or("?")
40 ));
41 }
42 }
43 for contained in array_mut(object, "contained") {
44 if contained["resourceType"] == "ConceptMap" {
45 concept_map_to_r4(contained);
46 }
47 }
48 for group in array_mut(object, "group") {
49 let group = group.as_object_mut().expect("a group is an object");
50 group
51 .entry("typeMode")
52 .or_insert_with(|| Value::String("none".into()));
53 for rule in array_mut(group, "rule") {
54 rule_to_r4(rule, &mut dropped);
55 }
56 }
57 R4Map { json, dropped }
58}
59
60pub fn from_json(mut json: Value) -> Result<StructureMap, serde_json::Error> {
63 if let Some(object) = json.as_object_mut() {
64 for contained in array_mut(object, "contained") {
65 if contained["resourceType"] == "ConceptMap" {
66 concept_map_from_r4(contained);
67 }
68 }
69 for group in array_mut(object, "group") {
70 let group = group.as_object_mut().expect("a group is an object");
71 if group.get("typeMode").and_then(Value::as_str) == Some("none") {
72 group.remove("typeMode");
73 }
74 for rule in array_mut(group, "rule") {
75 rule_from_r4(rule);
76 }
77 }
78 }
79 serde_json::from_value(json)
80}
81
82fn array_mut<'a>(
83 object: &'a mut Map<String, Value>,
84 key: &str,
85) -> impl Iterator<Item = &'a mut Value> {
86 object
87 .get_mut(key)
88 .and_then(Value::as_array_mut)
89 .into_iter()
90 .flatten()
91}
92
93fn rule_to_r4(rule: &mut Value, dropped: &mut Vec<String>) {
94 let rule = rule.as_object_mut().expect("a rule is an object");
95 let name = rule
96 .get("name")
97 .and_then(Value::as_str)
98 .unwrap_or("?")
99 .to_owned();
100 for source in array_mut(rule, "source") {
101 let source = source.as_object_mut().expect("a source is an object");
102 if let Some(Value::String(default)) = source.remove("defaultValue") {
103 let (key, value) = literal_default(&default).unwrap_or_else(|| {
104 dropped.push(format!(
105 "default `{default}` of rule `{name}` is not a literal"
106 ));
107 ("defaultValueString", Value::String(default.clone()))
108 });
109 source.insert(key.into(), value);
110 }
111 }
112 for target in array_mut(rule, "target") {
113 let target = target.as_object_mut().expect("a target is an object");
114 if target.contains_key("context") {
115 target.insert("contextType".into(), "variable".into());
116 }
117 for parameter in array_mut(target, "parameter") {
118 parameter_to_r4(parameter);
119 }
120 }
121 for dependent in array_mut(rule, "dependent") {
122 let dependent = dependent.as_object_mut().expect("a dependent is an object");
123 if let Some(Value::Array(parameters)) = dependent.remove("parameter") {
124 let variables: Vec<Value> = parameters
125 .iter()
126 .filter_map(|p| {
127 p.as_object()?
128 .iter()
129 .find(|(k, _)| k.starts_with("value"))
130 .map(|(_, v)| match v {
131 Value::String(s) => Value::String(s.clone()),
132 other => Value::String(other.to_string()),
133 })
134 })
135 .collect();
136 dependent.insert("variable".into(), Value::Array(variables));
137 }
138 }
139 for nested in array_mut(rule, "rule") {
140 rule_to_r4(nested, dropped);
141 }
142}
143
144fn literal_default(expression: &str) -> Option<(&'static str, Value)> {
146 let text = expression.trim();
147 if text.len() >= 2 && text.starts_with('\'') && text.ends_with('\'') {
148 let inner = &text[1..text.len() - 1];
149 if !inner.replace("\\\\", "").replace("\\'", "").contains('\'') {
151 return Some(("defaultValueString", fml_syntax::ast::unescape(text).into()));
152 }
153 return None;
154 }
155 match text {
156 "true" => return Some(("defaultValueBoolean", true.into())),
157 "false" => return Some(("defaultValueBoolean", false.into())),
158 _ => {}
159 }
160 if let Ok(integer) = text.parse::<i64>() {
161 return Some(("defaultValueInteger", integer.into()));
162 }
163 if text.contains('.')
164 && let Ok(Value::Number(number)) = serde_json::from_str::<Value>(text)
165 {
166 return Some(("defaultValueDecimal", Value::Number(number)));
167 }
168 None
169}
170
171fn rule_from_r4(rule: &mut Value) {
172 let rule = rule.as_object_mut().expect("a rule is an object");
173 for source in array_mut(rule, "source") {
174 let source = source.as_object_mut().expect("a source is an object");
175 let key = source
176 .keys()
177 .find(|k| k.starts_with("defaultValue") && k.as_str() != "defaultValue")
178 .cloned();
179 if let Some(key) = key
180 && let Some(value) = source.remove(&key)
181 {
182 let expression = match value {
184 Value::String(text) if key == "defaultValueBoolean" => text,
185 Value::String(text) => fml_syntax::quote_fhirpath_string(&text),
186 Value::Bool(flag) => flag.to_string(),
187 Value::Number(number) => number.to_string(),
188 other => other.to_string(),
189 };
190 source.insert("defaultValue".into(), Value::String(expression));
191 }
192 }
193 for target in array_mut(rule, "target") {
194 let target = target.as_object_mut().expect("a target is an object");
195 target.remove("contextType");
196 }
197 for dependent in array_mut(rule, "dependent") {
198 let dependent = dependent.as_object_mut().expect("a dependent is an object");
199 if let Some(Value::Array(variables)) = dependent.remove("variable") {
200 let parameters: Vec<Value> = variables
201 .into_iter()
202 .map(|v| json!({ "valueId": v }))
203 .collect();
204 dependent.insert("parameter".into(), Value::Array(parameters));
205 }
206 }
207 for nested in array_mut(rule, "rule") {
208 rule_from_r4(nested);
209 }
210}
211
212fn parameter_to_r4(parameter: &mut Value) {
213 let Some(object) = parameter.as_object_mut() else {
214 return;
215 };
216 for key in ["valueDate", "valueTime", "valueDateTime"] {
217 if let Some(value) = object.remove(key) {
218 object.insert("valueString".into(), value);
219 }
220 }
221}
222
223const RELATIONSHIPS: &[(&str, &str)] = &[
224 ("related-to", "relatedto"),
225 ("equivalent", "equivalent"),
226 ("source-is-narrower-than-target", "wider"),
227 ("source-is-broader-than-target", "narrower"),
228 ("not-related-to", "disjoint"),
229];
230
231fn concept_map_to_r4(concept_map: &mut Value) {
232 for group in concept_map["group"].as_array_mut().into_iter().flatten() {
233 if group["unmapped"]["mode"] == "use-source-code" {
234 group["unmapped"]["mode"] = "provided".into();
235 }
236 for element in group["element"].as_array_mut().into_iter().flatten() {
237 for target in element["target"].as_array_mut().into_iter().flatten() {
238 let Some(object) = target.as_object_mut() else {
239 continue;
240 };
241 if let Some(Value::String(relationship)) = object.remove("relationship") {
242 let equivalence = RELATIONSHIPS
243 .iter()
244 .find(|(r5, _)| *r5 == relationship)
245 .map_or("relatedto", |(_, r4)| r4);
246 object.insert("equivalence".into(), equivalence.into());
247 }
248 }
249 }
250 }
251}
252
253fn concept_map_from_r4(concept_map: &mut Value) {
254 for group in concept_map["group"].as_array_mut().into_iter().flatten() {
255 if group["unmapped"]["mode"] == "provided" {
256 group["unmapped"]["mode"] = "use-source-code".into();
257 }
258 for element in group["element"].as_array_mut().into_iter().flatten() {
259 for target in element["target"].as_array_mut().into_iter().flatten() {
260 let Some(object) = target.as_object_mut() else {
261 continue;
262 };
263 if let Some(Value::String(equivalence)) = object.remove("equivalence") {
264 let relationship = match equivalence.as_str() {
265 "equal" => "equivalent",
266 "subsumes" => "source-is-broader-than-target",
267 "specializes" => "source-is-narrower-than-target",
268 "unmatched" => "not-related-to",
269 "inexact" => "related-to",
270 other => RELATIONSHIPS
271 .iter()
272 .find(|(_, r4)| *r4 == other)
273 .map_or("related-to", |(r5, _)| r5),
274 };
275 object.insert("relationship".into(), relationship.into());
276 }
277 }
278 }
279 }
280}