1#![allow(unused)]
2use std::{
3 collections::{HashMap, HashSet},
4 sync::LazyLock,
5};
6
7pub static RUST_KEYWORDS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
11 let mut m = HashSet::new();
12 m.insert("self");
13 m.insert("Self");
14 m.insert("super");
15 m.insert("type");
16 m.insert("use");
17 m.insert("identifier");
18 m.insert("abstract");
19 m.insert("for");
20 m.insert("if");
21 m.insert("else");
22 m.insert("match");
23 m.insert("while");
24 m.insert("loop");
25 m.insert("break");
26 m.insert("continue");
27 m.insert("ref");
28 m.insert("return");
29 m.insert("async");
30 m
31});
32
33pub static RUST_PRIMITIVES: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
34 let mut m = HashMap::new();
35 m.insert(
36 "http://hl7.org/fhirpath/System.String".to_string(),
37 "String".to_string(),
38 );
39 m.insert(
40 "http://hl7.org/fhirpath/System.Decimal".to_string(),
41 "f64".to_string(),
42 );
43 m.insert(
44 "http://hl7.org/fhirpath/System.Boolean".to_string(),
45 "bool".to_string(),
46 );
47 m.insert(
48 "http://hl7.org/fhirpath/System.Integer".to_string(),
49 "i64".to_string(),
50 );
51 m.insert(
52 "http://hl7.org/fhirpath/System.Time".to_string(),
53 "crate::r4::datetime::Time".to_string(),
54 );
55 m.insert(
56 "http://hl7.org/fhirpath/System.Date".to_string(),
57 "crate::r4::datetime::Date".to_string(),
58 );
59 m.insert(
60 "http://hl7.org/fhirpath/System.DateTime".to_string(),
61 "crate::r4::datetime::DateTime".to_string(),
62 );
63 m.insert(
64 "http://hl7.org/fhirpath/System.Instant".to_string(),
65 "crate::r4::datetime::Instant".to_string(),
66 );
67 m
68});
69
70pub static FHIR_PRIMITIVES: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
71 let mut m = HashMap::new();
72 m.insert("boolean".to_string(), "FHIRBoolean".to_string());
74
75 m.insert("decimal".to_string(), "FHIRDecimal".to_string());
77
78 m.insert("integer".to_string(), "FHIRInteger".to_string());
80 m.insert("positiveInt".to_string(), "FHIRPositiveInt".to_string());
82 m.insert("unsignedInt".to_string(), "FHIRUnsignedInt".to_string());
83
84 m.insert("base64Binary".to_string(), "FHIRBase64Binary".to_string());
86 m.insert("canonical".to_string(), "FHIRCanonical".to_string());
87 m.insert("code".to_string(), "FHIRCode".to_string());
88 m.insert("id".to_string(), "FHIRId".to_string());
89 m.insert("markdown".to_string(), "FHIRMarkdown".to_string());
90 m.insert("oid".to_string(), "FHIROid".to_string());
91 m.insert("string".to_string(), "FHIRString".to_string());
92 m.insert("uri".to_string(), "FHIRUri".to_string());
93 m.insert("url".to_string(), "FHIRUrl".to_string());
94 m.insert("uuid".to_string(), "FHIRUuid".to_string());
95 m.insert("xhtml".to_string(), "FHIRXhtml".to_string());
96
97 m.insert("instant".to_string(), "FHIRInstant".to_string());
99 m.insert("date".to_string(), "FHIRDate".to_string());
100 m.insert("dateTime".to_string(), "FHIRDateTime".to_string());
101 m.insert("time".to_string(), "FHIRTime".to_string());
102
103 m
104});
105
106pub static FHIR_PRIMITIVE_VALUE_TYPE: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
107 let mut m = HashMap::new();
108 m.insert("boolean".to_string(), "bool".to_string());
110
111 m.insert("decimal".to_string(), "f64".to_string());
113
114 m.insert("integer".to_string(), "i64".to_string());
116 m.insert("positiveInt".to_string(), "u64".to_string());
118 m.insert("unsignedInt".to_string(), "u64".to_string());
119
120 m.insert("base64Binary".to_string(), "String".to_string());
122 m.insert("canonical".to_string(), "String".to_string());
123 m.insert("code".to_string(), "String".to_string());
124 m.insert("date".to_string(), "String".to_string());
125 m.insert("dateTime".to_string(), "String".to_string());
126 m.insert("id".to_string(), "String".to_string());
127 m.insert("instant".to_string(), "String".to_string());
128 m.insert("markdown".to_string(), "String".to_string());
129 m.insert("oid".to_string(), "String".to_string());
130 m.insert("string".to_string(), "String".to_string());
131 m.insert("time".to_string(), "String".to_string());
132 m.insert("uri".to_string(), "String".to_string());
133 m.insert("url".to_string(), "String".to_string());
134 m.insert("uuid".to_string(), "String".to_string());
135 m.insert("xhtml".to_string(), "String".to_string());
136
137 m
138});
139
140pub mod conversion {
141 use std::collections::HashMap;
142
143 use super::{FHIR_PRIMITIVES, RUST_PRIMITIVES};
144 use haste_fhir_model::r4::generated::{terminology::BindingStrength, types::ElementDefinition};
145 use proc_macro2::TokenStream;
146 use quote::{format_ident, quote};
147
148 pub fn fhir_type_to_rust_type(
149 element: &ElementDefinition,
150 fhir_type: &str,
151 inlined_terminology: &HashMap<String, String>,
152 ) -> TokenStream {
153 let path = element.path.value.as_ref().map(|p| p.as_str());
154
155 match path {
156 Some("unsignedInt.value") | Some("positiveInt.value") => {
157 let k = format_ident!("{}", "u64");
158 quote! {
159 #k
160 }
161 }
162
163 _ => {
164 if let Some(rust_primitive) = RUST_PRIMITIVES.get(fhir_type) {
165 let path = path.unwrap();
167 if path == "instant.value" {
168 let k = RUST_PRIMITIVES
169 .get("http://hl7.org/fhirpath/System.Instant")
170 .unwrap()
171 .parse::<TokenStream>()
172 .unwrap();
173
174 quote! {
175 #k
176 }
177 } else {
178 let k = rust_primitive.parse::<TokenStream>().unwrap();
179 quote! {
180 #k
181 }
182 }
183 } else if let Some(primitive) = FHIR_PRIMITIVES.get(fhir_type) {
184 if let Some(BindingStrength::Required(_)) =
189 element.binding.as_ref().map(|b| b.strength.as_ref())
190 && let Some(canonical_string) = element
191 .binding
192 .as_ref()
193 .and_then(|b| b.valueSet.as_ref())
194 .and_then(|b| b.value.as_ref())
195 .map(|u| u.as_str())
196 && let Some(url) = canonical_string.split('|').next()
197 && let Some(inlined) = inlined_terminology.get(url)
198 {
199 let inline_type = format_ident!("{}", inlined);
200 quote! {
201 Box<terminology::#inline_type>
202 }
203 } else {
204 let k = format_ident!("{}", primitive.clone());
205 quote! {
206 Box<#k>
207 }
208 }
209 } else {
210 let k = format_ident!("{}", fhir_type.to_string());
211 quote! {
212 Box<#k>
213 }
214 }
215 }
216 }
217 }
218}
219
220pub mod extract {
221 use haste_fhir_model::r4::generated::resources::StructureDefinition;
222 use haste_fhir_model::r4::generated::types::ElementDefinition;
223 pub fn field_types<'a>(element: &ElementDefinition) -> Vec<&str> {
224 let codes = element
225 .type_
226 .as_ref()
227 .map(|types| {
228 types
229 .iter()
230 .filter_map(|t| t.code.value.as_ref().map(|v| v.as_str()))
231 .collect()
232 })
233 .unwrap_or_else(|| vec![]);
234 codes
235 }
236
237 pub fn field_name(path: &str) -> String {
238 let field_name: String = path
239 .split('.')
240 .last()
241 .unwrap_or("")
242 .chars()
243 .enumerate()
244 .map(|(i, c)| {
245 if i == 0 {
246 c.to_lowercase().next().unwrap_or(c)
247 } else {
248 c
249 }
250 })
251 .collect();
252 let removed_x = if field_name.ends_with("[x]") {
253 field_name.replace("[x]", "")
254 } else {
255 field_name.clone()
256 };
257
258 removed_x
259 }
260
261 pub fn is_abstract(sd: &StructureDefinition) -> bool {
262 sd.abstract_.value == Some(true)
263 }
264
265 pub fn path(element: &ElementDefinition) -> String {
266 element.path.value.clone().unwrap_or_else(|| "".to_string())
267 }
268 pub fn element_description(element: &ElementDefinition) -> String {
269 element
270 .definition
271 .as_ref()
272 .and_then(|d| d.value.as_ref())
273 .cloned()
274 .unwrap_or_else(|| "".to_string())
275 }
276
277 pub fn fhir_type(sd: &StructureDefinition, element: &ElementDefinition) -> String {
278 if crate::utilities::conditionals::is_root(sd, element) {
279 sd.type_
280 .value
281 .as_ref()
282 .expect("Root element must have a type")
283 .clone()
284 } else {
285 let default_types = vec![];
286 let fhir_types = element.type_.as_ref().unwrap_or(&default_types);
287 if fhir_types.len() == 1 {
288 fhir_types[0]
289 .code
290 .value
291 .as_ref()
292 .expect("Type must have a code")
293 .clone()
294 } else {
295 panic!("Element has multiple types, cannot determine FHIR type");
296 }
297 }
298 }
299
300 #[derive(Clone)]
301 pub enum Max {
302 Unlimited,
303 Fixed(usize),
304 }
305
306 pub fn cardinality(element: &ElementDefinition) -> (usize, Max) {
307 let min = element.min.as_ref().and_then(|m| m.value).map_or(0, |m| m) as usize;
308
309 let max = element
310 .max
311 .as_ref()
312 .and_then(|m| m.value.as_ref())
313 .map(|v| v.as_str())
314 .and_then(|s| {
315 if s == "*" {
316 Some(Max::Unlimited)
317 } else {
318 s.parse::<usize>().ok().and_then(|i| Some(Max::Fixed(i)))
319 }
320 });
321
322 (min, max.unwrap_or_else(|| Max::Fixed(1)))
323 }
324}
325
326pub mod generate {
327 use std::collections::HashMap;
328
329 use haste_fhir_model::r4::generated::{
330 resources::StructureDefinition, types::ElementDefinition,
331 };
332 use proc_macro2::TokenStream;
333 use quote::{format_ident, quote};
334
335 use crate::utilities::{FHIR_PRIMITIVES, conditionals, conversion, extract};
336
337 pub fn capitalize(s: &str) -> String {
339 let mut c = s.chars();
340 match c.next() {
341 None => String::new(),
342 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
343 }
344 }
345
346 pub fn struct_name(sd: &StructureDefinition, element: &ElementDefinition) -> String {
347 if conditionals::is_root(sd, element) {
348 let mut interface_name: String = capitalize(sd.id.as_ref().unwrap());
349 if conditionals::is_primitive_sd(sd) {
350 interface_name = "FHIR".to_owned() + &interface_name;
351 }
352 interface_name
353 } else {
354 element
355 .id
356 .as_ref()
357 .map(|p| p.split("."))
358 .map(|p| p.map(capitalize).collect::<Vec<String>>().join(""))
359 .unwrap()
360 .replace("[x]", "")
361 }
362 }
363
364 pub fn type_choice_name(sd: &StructureDefinition, element: &ElementDefinition) -> String {
365 let name = struct_name(sd, element);
366 name + "TypeChoice"
367 }
368
369 pub fn type_choice_variant_name(element: &ElementDefinition, fhir_type: &str) -> String {
370 let field_name = extract::field_name(&extract::path(element));
371 format!("{:0}{:1}", field_name, capitalize(fhir_type))
372 }
373
374 pub fn create_type_choice_variants(element: &ElementDefinition) -> Vec<String> {
375 extract::field_types(element)
376 .into_iter()
377 .map(|fhir_type| type_choice_variant_name(element, fhir_type))
378 .collect()
379 }
380 pub fn create_type_choice_primitive_variants(element: &ElementDefinition) -> Vec<String> {
381 extract::field_types(element)
382 .into_iter()
383 .filter(|fhir_type| FHIR_PRIMITIVES.contains_key(*fhir_type))
384 .map(|fhir_type| type_choice_variant_name(element, fhir_type))
385 .collect()
386 }
387
388 pub fn field_typename(
389 sd: &StructureDefinition,
390 element: &ElementDefinition,
391 inlined_terminology: &HashMap<String, String>,
392 ) -> TokenStream {
393 let field_value_type_name = if conditionals::is_typechoice(element) {
394 let k = format_ident!("{}", type_choice_name(sd, element));
395 quote! {
396 #k
397 }
398 } else if conditionals::is_nested_complex(element) {
399 let k = format_ident!("{}", struct_name(sd, element));
400 quote! {
401 #k
402 }
403 } else {
404 let fhir_type = element.type_.as_ref().unwrap()[0]
405 .code
406 .as_ref()
407 .value
408 .as_ref()
409 .unwrap();
410
411 conversion::fhir_type_to_rust_type(element, fhir_type, inlined_terminology)
412 };
413
414 field_value_type_name
415 }
416}
417
418pub mod conditionals {
419 use haste_fhir_model::r4::generated::{
420 resources::StructureDefinition, terminology::StructureDefinitionKind,
421 types::ElementDefinition,
422 };
423
424 use crate::utilities::{FHIR_PRIMITIVES, RUST_PRIMITIVES, extract};
425
426 pub fn is_root(sd: &StructureDefinition, element: &ElementDefinition) -> bool {
427 element.path.value == sd.id
428 }
429
430 pub fn is_resource_sd(sd: &StructureDefinition) -> bool {
431 if let StructureDefinitionKind::Resource(_) = sd.kind.as_ref() {
432 true
433 } else {
434 false
435 }
436 }
437
438 pub fn is_primitive(element: &ElementDefinition) -> bool {
439 let types = extract::field_types(element);
440 types.len() == 1 && FHIR_PRIMITIVES.contains_key(types[0])
441 }
442
443 pub fn is_nested_complex(element: &ElementDefinition) -> bool {
444 let types = extract::field_types(element);
445 types.len() > 1 || types[0] == "BackboneElement" || types[0] == "Element"
447 }
448
449 pub fn should_be_boxed(fhir_type: &str) -> bool {
451 !RUST_PRIMITIVES.contains_key(fhir_type)
452 }
453
454 pub fn is_primitive_sd(sd: &StructureDefinition) -> bool {
455 if let StructureDefinitionKind::PrimitiveType(_) = sd.kind.as_ref() {
456 true
457 } else {
458 false
459 }
460 }
461
462 pub fn is_typechoice(element: &ElementDefinition) -> bool {
463 extract::field_types(element).len() > 1
464 }
465}
466
467pub mod load {
468 use std::path::Path;
469
470 use haste_fhir_model::r4::generated::{
471 resources::{Resource, StructureDefinition},
472 terminology::StructureDefinitionKind,
473 };
474
475 use crate::utilities::extract;
476
477 pub fn load_from_file(file_path: &Path) -> Result<Resource, String> {
478 let data = std::fs::read_to_string(file_path)
479 .map_err(|e| format!("Failed to read file: {}", e))?;
480
481 let resource = haste_fhir_serialization_json::from_str::<Resource>(&data)
482 .map_err(|e| format!("Failed to parse JSON: {}", e))?;
483
484 Ok(resource)
485 }
486
487 pub fn get_structure_definitions<'a>(
488 resource: &'a Resource,
489 level: Option<&'static str>,
490 ) -> Result<Vec<&'a StructureDefinition>, String> {
491 match resource {
492 Resource::Bundle(bundle) => {
493 if let Some(entries) = bundle.entry.as_ref() {
494 let sds = entries
495 .iter()
496 .filter_map(|e| e.resource.as_ref())
497 .filter_map(|sd| match sd.as_ref() {
498 Resource::StructureDefinition(sd) => Some(sd),
499 _ => None,
500 });
501
502 let filtered_sds = sds.filter(move |sd| {
503 if let Some(level) = level {
504 match sd.kind.as_ref() {
505 StructureDefinitionKind::Resource(_)
506 | StructureDefinitionKind::Null(_) => level == "resource",
507 StructureDefinitionKind::ComplexType(_) => level == "complex-type",
508 StructureDefinitionKind::PrimitiveType(_) => {
509 level == "primitive-type"
510 }
511 _ => false,
512 }
513 } else {
514 true
515 }
516 });
517
518 Ok(filtered_sds.collect())
519 } else {
520 Ok(vec![])
521 }
522 }
523 Resource::StructureDefinition(sd) => {
524 let resources = std::iter::once(sd);
525 let filtered_resources = resources.filter(|sd| {
526 if let Some(level) = level {
527 match sd.kind.as_ref() {
528 StructureDefinitionKind::Resource(_)
529 | StructureDefinitionKind::Null(_) => level == "resource",
530 StructureDefinitionKind::ComplexType(_) => level == "complex-type",
531 StructureDefinitionKind::PrimitiveType(_) => level == "primitive-type",
532 _ => false,
533 }
534 } else {
535 true
536 }
537 });
538
539 Ok(filtered_resources.collect())
540 }
541 _ => Ok(vec![]),
542 }
543 }
544}