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.insert("where");
31 m.insert("in");
32 m.insert("final");
33 m.insert("as");
34 m.insert("do");
35 m.insert("box");
36 m.insert("pub");
37 m.insert("false");
38 m.insert("true");
39 m.insert("mod");
40 m.insert("gen");
41 m.insert("crate");
42 m.insert("fn");
43 m.insert("let");
44 m.insert("const");
45 m.insert("static");
46 m.insert("struct");
47 m.insert("enum");
48 m.insert("trait");
49 m.insert("impl");
50 m.insert("unsafe");
51 m.insert("extern");
52 m.insert("move");
53 m.insert("mut");
54 m.insert("dyn");
55 m.insert("await");
56 m.insert("try");
57 m.insert("yield");
58 m.insert("macro");
59 m.insert("union");
60 m
61});
62
63pub static RUST_PRIMITIVES: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
64 let mut m = HashMap::new();
65 m.insert(
66 "http://hl7.org/fhirpath/System.String".to_string(),
67 "String".to_string(),
68 );
69 m.insert(
70 "http://hl7.org/fhirpath/System.Decimal".to_string(),
71 "f64".to_string(),
72 );
73 m.insert(
74 "http://hl7.org/fhirpath/System.Boolean".to_string(),
75 "bool".to_string(),
76 );
77 m.insert(
78 "http://hl7.org/fhirpath/System.Integer".to_string(),
79 "i64".to_string(),
80 );
81 m.insert(
82 "http://hl7.org/fhirpath/System.Time".to_string(),
83 "crate::r4::datetime::Time".to_string(),
84 );
85 m.insert(
86 "http://hl7.org/fhirpath/System.Date".to_string(),
87 "crate::r4::datetime::Date".to_string(),
88 );
89 m.insert(
90 "http://hl7.org/fhirpath/System.DateTime".to_string(),
91 "crate::r4::datetime::DateTime".to_string(),
92 );
93 m.insert(
94 "http://hl7.org/fhirpath/System.Instant".to_string(),
95 "crate::r4::datetime::Instant".to_string(),
96 );
97 m
98});
99
100pub static FHIR_PRIMITIVES: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
101 let mut m = HashMap::new();
102 m.insert("boolean".to_string(), "FHIRBoolean".to_string());
104
105 m.insert("decimal".to_string(), "FHIRDecimal".to_string());
107
108 m.insert("integer".to_string(), "FHIRInteger".to_string());
110 m.insert("positiveInt".to_string(), "FHIRPositiveInt".to_string());
112 m.insert("unsignedInt".to_string(), "FHIRUnsignedInt".to_string());
113
114 m.insert("base64Binary".to_string(), "FHIRBase64Binary".to_string());
116 m.insert("canonical".to_string(), "FHIRCanonical".to_string());
117 m.insert("code".to_string(), "FHIRCode".to_string());
118 m.insert("id".to_string(), "FHIRId".to_string());
119 m.insert("markdown".to_string(), "FHIRMarkdown".to_string());
120 m.insert("oid".to_string(), "FHIROid".to_string());
121 m.insert("string".to_string(), "FHIRString".to_string());
122 m.insert("uri".to_string(), "FHIRUri".to_string());
123 m.insert("url".to_string(), "FHIRUrl".to_string());
124 m.insert("uuid".to_string(), "FHIRUuid".to_string());
125 m.insert("xhtml".to_string(), "FHIRXhtml".to_string());
126
127 m.insert("instant".to_string(), "FHIRInstant".to_string());
129 m.insert("date".to_string(), "FHIRDate".to_string());
130 m.insert("dateTime".to_string(), "FHIRDateTime".to_string());
131 m.insert("time".to_string(), "FHIRTime".to_string());
132
133 m
134});
135
136pub static FHIR_PRIMITIVE_VALUE_TYPE: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
137 let mut m = HashMap::new();
138 m.insert("boolean".to_string(), "bool".to_string());
140
141 m.insert("decimal".to_string(), "f64".to_string());
143
144 m.insert("integer".to_string(), "i64".to_string());
146 m.insert("positiveInt".to_string(), "u64".to_string());
148 m.insert("unsignedInt".to_string(), "u64".to_string());
149
150 m.insert("base64Binary".to_string(), "String".to_string());
152 m.insert("canonical".to_string(), "String".to_string());
153 m.insert("code".to_string(), "String".to_string());
154 m.insert("date".to_string(), "String".to_string());
155 m.insert("dateTime".to_string(), "String".to_string());
156 m.insert("id".to_string(), "String".to_string());
157 m.insert("instant".to_string(), "String".to_string());
158 m.insert("markdown".to_string(), "String".to_string());
159 m.insert("oid".to_string(), "String".to_string());
160 m.insert("string".to_string(), "String".to_string());
161 m.insert("time".to_string(), "String".to_string());
162 m.insert("uri".to_string(), "String".to_string());
163 m.insert("url".to_string(), "String".to_string());
164 m.insert("uuid".to_string(), "String".to_string());
165 m.insert("xhtml".to_string(), "String".to_string());
166
167 m
168});
169
170pub mod conversion {
171 use std::collections::HashMap;
172
173 use super::{FHIR_PRIMITIVES, RUST_PRIMITIVES};
174 use haste_fhir_model::r4::generated::{terminology::BindingStrength, types::ElementDefinition};
175 use proc_macro2::TokenStream;
176 use quote::{format_ident, quote};
177
178 pub fn fhir_type_to_rust_type<S: std::hash::BuildHasher>(
197 element: &ElementDefinition,
198 fhir_type: &str,
199 inlined_terminology: &HashMap<String, String, S>,
200 ) -> (TokenStream, bool) {
201 let path = element.path.value.as_deref();
202
203 match path {
204 Some("unsignedInt.value" | "positiveInt.value") => {
205 let k = format_ident!("{}", "u64");
206 (
207 quote! {
208 #k
209 },
210 false,
211 )
212 }
213
214 _ => {
215 if let Some(rust_primitive) = RUST_PRIMITIVES.get(fhir_type) {
216 if matches!(path, Some("instant.value")) {
217 let k = RUST_PRIMITIVES
218 .get("http://hl7.org/fhirpath/System.Instant")
219 .unwrap()
220 .parse::<TokenStream>()
221 .unwrap();
222
223 (
224 quote! {
225 #k
226 },
227 false,
228 )
229 } else {
230 let k = rust_primitive.parse::<TokenStream>().unwrap();
231 (
232 quote! {
233 #k
234 },
235 false,
236 )
237 }
238 } else if let Some(primitive) = FHIR_PRIMITIVES.get(fhir_type) {
239 if Some(&BindingStrength::required())
244 == element.binding.as_ref().map(|b| &b.strength)
245 && let Some(canonical_string) = element
246 .binding
247 .as_ref()
248 .and_then(|b| b.valueSet.as_ref())
249 .and_then(|b| b.value.as_ref())
250 .map(std::string::String::as_str)
251 && let Some(url) = canonical_string.split('|').next()
252 && let Some(inlined) = inlined_terminology.get(url)
253 {
254 let inline_type = format_ident!("{}", inlined);
255 (
256 quote! {
257 terminology::BoundCode<terminology::#inline_type>
258 },
259 false,
260 )
261 } else {
262 let k = format_ident!("{}", primitive.clone());
263 (
264 quote! {
265 #k
266 },
267 true,
268 )
269 }
270 } else {
271 let k = format_ident!("{}", fhir_type.to_string());
272 (
273 quote! {
274 #k
275 },
276 true,
277 )
278 }
279 }
280 }
281 }
282}
283
284pub mod extract {
285 use haste_fhir_model::r4::generated::resources::StructureDefinition;
286 use haste_fhir_model::r4::generated::types::ElementDefinition;
287 pub fn field_types(element: &ElementDefinition) -> Vec<&str> {
288 element.type_.as_ref().map_or_else(Vec::new, |types| {
289 types
290 .iter()
291 .filter_map(|t| t.code.value.as_deref())
292 .collect()
293 })
294 }
295
296 #[must_use]
297 pub fn field_name(path: &str) -> String {
298 let field_name: String = path
299 .split('.')
300 .next_back()
301 .unwrap_or("")
302 .chars()
303 .enumerate()
304 .map(|(i, c)| {
305 if i == 0 {
306 c.to_lowercase().next().unwrap_or(c)
307 } else {
308 c
309 }
310 })
311 .collect();
312 if field_name.ends_with("[x]") {
313 field_name.replace("[x]", "")
314 } else {
315 field_name.clone()
316 }
317 }
318
319 pub fn is_abstract(sd: &StructureDefinition) -> bool {
320 sd.abstract_.value == Some(true)
321 }
322
323 pub fn path(element: &ElementDefinition) -> String {
324 element.path.value.clone().unwrap_or_default()
325 }
326 pub fn element_description(element: &ElementDefinition) -> String {
327 element
328 .definition
329 .as_ref()
330 .and_then(|d| d.value.as_ref())
331 .cloned()
332 .unwrap_or_else(|| {
333 element
334 .path
335 .value
336 .clone()
337 .unwrap_or_else(|| "no description".to_string())
338 })
339 }
340
341 pub fn fhir_type(sd: &StructureDefinition, element: &ElementDefinition) -> String {
367 if crate::utilities::conditionals::is_root(sd, element) {
368 sd.type_
369 .value
370 .as_ref()
371 .expect("Root element must have a type")
372 .clone()
373 } else {
374 let default_types = vec![];
375 let fhir_types = element.type_.as_ref().unwrap_or(&default_types);
376 if fhir_types.len() == 1 {
377 fhir_types[0]
378 .code
379 .value
380 .as_ref()
381 .expect("Type must have a code")
382 .clone()
383 } else {
384 panic!("Element has multiple types, cannot determine FHIR type");
385 }
386 }
387 }
388
389 #[derive(Clone, Copy)]
390 pub enum Max {
391 Unlimited,
392 Fixed(u64),
393 }
394
395 pub fn cardinality(element: &ElementDefinition) -> (u64, Max) {
396 let min = element.min.as_ref().and_then(|m| m.value).map_or(0, |m| m);
397
398 let max = element
399 .max
400 .as_ref()
401 .and_then(|m| m.value.as_ref())
402 .map(std::string::String::as_str)
403 .and_then(|s| {
404 if s == "*" {
405 Some(Max::Unlimited)
406 } else {
407 s.parse::<u64>().ok().map(Max::Fixed)
408 }
409 });
410
411 (min, max.unwrap_or(Max::Fixed(1)))
412 }
413}
414
415pub mod generate {
416 use std::collections::HashMap;
417
418 use haste_fhir_model::r4::generated::{
419 resources::StructureDefinition, types::ElementDefinition,
420 };
421 use proc_macro2::TokenStream;
422 use quote::{format_ident, quote};
423
424 use crate::utilities::{FHIR_PRIMITIVES, conditionals, conversion, extract};
425
426 #[must_use]
428 pub fn capitalize(s: &str) -> String {
429 let mut c = s.chars();
430 match c.next() {
431 None => String::new(),
432 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
433 }
434 }
435
436 pub fn struct_name(sd: &StructureDefinition, element: &ElementDefinition) -> String {
463 if conditionals::is_root(sd, element) {
464 let mut interface_name: String = capitalize(sd.id.as_ref().unwrap());
465 if conditionals::is_primitive_sd(sd) {
466 interface_name = "FHIR".to_owned() + &interface_name;
467 }
468 interface_name
469 } else {
470 element
471 .id
472 .as_ref()
473 .map(|p| p.split('.'))
474 .map(|p| p.map(capitalize).collect::<String>())
475 .unwrap()
476 .replace("[x]", "")
477 }
478 }
479
480 pub fn type_choice_name(sd: &StructureDefinition, element: &ElementDefinition) -> String {
481 let name = struct_name(sd, element);
482 name + "TypeChoice"
483 }
484
485 pub fn type_choice_variant_name(element: &ElementDefinition, fhir_type: &str) -> String {
486 let field_name = extract::field_name(&extract::path(element));
487 format!("{:0}{:1}", field_name, capitalize(fhir_type))
488 }
489
490 pub fn create_type_choice_variants(element: &ElementDefinition) -> Vec<String> {
491 extract::field_types(element)
492 .into_iter()
493 .map(|fhir_type| type_choice_variant_name(element, fhir_type))
494 .collect()
495 }
496 pub fn create_type_choice_primitive_variants(element: &ElementDefinition) -> Vec<String> {
497 extract::field_types(element)
498 .into_iter()
499 .filter(|fhir_type| FHIR_PRIMITIVES.contains_key(*fhir_type))
500 .map(|fhir_type| type_choice_variant_name(element, fhir_type))
501 .collect()
502 }
503
504 pub fn field_typename<S: ::std::hash::BuildHasher>(
536 sd: &StructureDefinition,
537 element: &ElementDefinition,
538 inlined_terminology: &HashMap<String, String, S>,
539 ) -> (TokenStream, bool) {
540 if conditionals::is_typechoice(element) {
541 let k = format_ident!("{}", type_choice_name(sd, element));
542 (
543 quote! {
544 #k
545 },
546 false,
547 )
548 } else if conditionals::is_nested_complex(element) {
549 let k = format_ident!("{}", struct_name(sd, element));
550 (
551 quote! {
552 #k
553 },
554 false,
555 )
556 } else {
557 let fhir_type = element.type_.as_ref().unwrap()[0]
558 .code
559 .as_ref()
560 .value
561 .as_ref()
562 .unwrap();
563
564 conversion::fhir_type_to_rust_type(element, fhir_type, inlined_terminology)
565 }
566 }
567}
568
569pub mod conditionals {
570 use haste_fhir_model::r4::generated::{
571 resources::StructureDefinition, terminology::StructureDefinitionKind,
572 types::ElementDefinition,
573 };
574
575 use crate::utilities::{FHIR_PRIMITIVES, RUST_PRIMITIVES, extract};
576
577 pub fn is_root(sd: &StructureDefinition, element: &ElementDefinition) -> bool {
578 element.path.value == sd.id
579 }
580
581 pub fn is_resource_sd(sd: &StructureDefinition) -> bool {
582 sd.kind == StructureDefinitionKind::resource()
583 }
584
585 pub fn is_primitive_type(fhir_type: &str) -> bool {
586 FHIR_PRIMITIVES.contains_key(fhir_type)
587 }
588
589 pub fn is_primitive_element(element: &ElementDefinition) -> bool {
590 let types = extract::field_types(element);
591 types.len() == 1 && is_primitive_type(types[0])
592 }
593
594 pub fn is_nested_complex(element: &ElementDefinition) -> bool {
595 let types = extract::field_types(element);
596 types.len() > 1 || types[0] == "BackboneElement" || types[0] == "Element"
598 }
599
600 pub fn should_be_boxed(fhir_type: &str) -> bool {
602 !RUST_PRIMITIVES.contains_key(fhir_type)
603 }
604
605 pub fn is_primitive_sd(sd: &StructureDefinition) -> bool {
606 sd.kind == StructureDefinitionKind::primitive_type()
607 }
608
609 pub fn is_typechoice(element: &ElementDefinition) -> bool {
610 extract::field_types(element).len() > 1
611 }
612}
613
614pub mod load {
615 use std::path::Path;
616
617 use haste_fhir_model::r4::generated::{
618 resources::{Resource, StructureDefinition},
619 terminology::StructureDefinitionKind,
620 };
621
622 use crate::utilities::extract;
623
624 pub fn load_from_file(file_path: &Path) -> Result<Resource, String> {
643 let data =
644 std::fs::read_to_string(file_path).map_err(|e| format!("Failed to read file: {e}"))?;
645
646 let resource = serde_json::from_str::<Resource>(&data)
647 .map_err(|e| format!("Failed to parse JSON: {e}"))?;
648
649 Ok(resource)
650 }
651
652 pub fn get_structure_definitions<'a>(
692 resource: &'a Resource,
693 level: Option<&'static str>,
694 ) -> Result<Vec<&'a StructureDefinition>, String> {
695 match resource {
696 Resource::Bundle(bundle) => {
697 if let Some(entries) = bundle.entry.as_ref() {
698 let sds = entries
699 .iter()
700 .filter_map(|e| e.resource.as_ref())
701 .filter_map(|sd| match sd.as_ref() {
702 Resource::StructureDefinition(sd) => Some(sd),
703 _ => None,
704 });
705
706 let filtered_sds = sds.filter(move |sd| {
707 if let Some(level) = level {
708 match &sd.kind {
709 kind if kind == &StructureDefinitionKind::resource()
710 || kind == &StructureDefinitionKind::null() =>
711 {
712 level == "resource"
713 }
714 kind if kind == &StructureDefinitionKind::complex_type() => {
715 level == "complex-type"
716 }
717 kind if kind == &StructureDefinitionKind::primitive_type() => {
718 level == "primitive-type"
719 }
720 _ => false,
721 }
722 } else {
723 true
724 }
725 });
726
727 Ok(filtered_sds.collect())
728 } else {
729 Ok(vec![])
730 }
731 }
732 Resource::StructureDefinition(sd) => {
733 let resources = std::iter::once(sd);
734 let filtered_resources = resources.filter(|sd| {
735 if let Some(level) = level {
736 match &sd.kind {
737 kind if kind == &StructureDefinitionKind::resource()
738 || kind == &StructureDefinitionKind::null() =>
739 {
740 level == "resource"
741 }
742 kind if kind == &StructureDefinitionKind::complex_type() => {
743 level == "complex-type"
744 }
745 kind if kind == &StructureDefinitionKind::primitive_type() => {
746 level == "primitive-type"
747 }
748 _ => false,
749 }
750 } else {
751 true
752 }
753 });
754
755 Ok(filtered_resources.collect())
756 }
757 _ => Ok(vec![]),
758 }
759 }
760}