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 #[derive(Clone)]
278 pub enum Max {
279 Unlimited,
280 Fixed(usize),
281 }
282
283 pub fn cardinality(element: &ElementDefinition) -> (usize, Max) {
284 let min = element.min.as_ref().and_then(|m| m.value).map_or(0, |m| m) as usize;
285
286 let max = element
287 .max
288 .as_ref()
289 .and_then(|m| m.value.as_ref())
290 .map(|v| v.as_str())
291 .and_then(|s| {
292 if s == "*" {
293 Some(Max::Unlimited)
294 } else {
295 s.parse::<usize>().ok().and_then(|i| Some(Max::Fixed(i)))
296 }
297 });
298
299 (min, max.unwrap_or_else(|| Max::Fixed(1)))
300 }
301}
302
303pub mod generate {
304 use std::collections::HashMap;
305
306 use haste_fhir_model::r4::generated::{
307 resources::StructureDefinition, types::ElementDefinition,
308 };
309 use proc_macro2::TokenStream;
310 use quote::{format_ident, quote};
311
312 use crate::utilities::{FHIR_PRIMITIVES, conditionals, conversion, extract};
313
314 pub fn capitalize(s: &str) -> String {
316 let mut c = s.chars();
317 match c.next() {
318 None => String::new(),
319 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
320 }
321 }
322
323 pub fn struct_name(sd: &StructureDefinition, element: &ElementDefinition) -> String {
324 if conditionals::is_root(sd, element) {
325 let mut interface_name: String = capitalize(sd.id.as_ref().unwrap());
326 if conditionals::is_primitive_sd(sd) {
327 interface_name = "FHIR".to_owned() + &interface_name;
328 }
329 interface_name
330 } else {
331 element
332 .id
333 .as_ref()
334 .map(|p| p.split("."))
335 .map(|p| p.map(capitalize).collect::<Vec<String>>().join(""))
336 .unwrap()
337 .replace("[x]", "")
338 }
339 }
340
341 pub fn type_choice_name(sd: &StructureDefinition, element: &ElementDefinition) -> String {
342 let name = struct_name(sd, element);
343 name + "TypeChoice"
344 }
345
346 pub fn type_choice_variant_name(element: &ElementDefinition, fhir_type: &str) -> String {
347 let field_name = extract::field_name(&extract::path(element));
348 format!("{:0}{:1}", field_name, capitalize(fhir_type))
349 }
350
351 pub fn create_type_choice_variants(element: &ElementDefinition) -> Vec<String> {
352 extract::field_types(element)
353 .into_iter()
354 .map(|fhir_type| type_choice_variant_name(element, fhir_type))
355 .collect()
356 }
357 pub fn create_type_choice_primitive_variants(element: &ElementDefinition) -> Vec<String> {
358 extract::field_types(element)
359 .into_iter()
360 .filter(|fhir_type| FHIR_PRIMITIVES.contains_key(*fhir_type))
361 .map(|fhir_type| type_choice_variant_name(element, fhir_type))
362 .collect()
363 }
364
365 pub fn field_typename(
366 sd: &StructureDefinition,
367 element: &ElementDefinition,
368 inlined_terminology: &HashMap<String, String>,
369 ) -> TokenStream {
370 let field_value_type_name = if conditionals::is_typechoice(element) {
371 let k = format_ident!("{}", type_choice_name(sd, element));
372 quote! {
373 #k
374 }
375 } else if conditionals::is_nested_complex(element) {
376 let k = format_ident!("{}", struct_name(sd, element));
377 quote! {
378 #k
379 }
380 } else {
381 let fhir_type = element.type_.as_ref().unwrap()[0]
382 .code
383 .as_ref()
384 .value
385 .as_ref()
386 .unwrap();
387
388 conversion::fhir_type_to_rust_type(element, fhir_type, inlined_terminology)
389 };
390
391 field_value_type_name
392 }
393}
394
395pub mod conditionals {
396 use haste_fhir_model::r4::generated::{
397 resources::StructureDefinition, terminology::StructureDefinitionKind,
398 types::ElementDefinition,
399 };
400
401 use crate::utilities::{FHIR_PRIMITIVES, RUST_PRIMITIVES, extract};
402
403 pub fn is_root(sd: &StructureDefinition, element: &ElementDefinition) -> bool {
404 element.path.value == sd.id
405 }
406
407 pub fn is_resource_sd(sd: &StructureDefinition) -> bool {
408 if let StructureDefinitionKind::Resource(_) = sd.kind.as_ref() {
409 true
410 } else {
411 false
412 }
413 }
414
415 pub fn is_primitive(element: &ElementDefinition) -> bool {
416 let types = extract::field_types(element);
417 types.len() == 1 && FHIR_PRIMITIVES.contains_key(types[0])
418 }
419
420 pub fn is_nested_complex(element: &ElementDefinition) -> bool {
421 let types = extract::field_types(element);
422 types.len() > 1 || types[0] == "BackboneElement" || types[0] == "Element"
424 }
425
426 pub fn should_be_boxed(fhir_type: &str) -> bool {
428 !RUST_PRIMITIVES.contains_key(fhir_type)
429 }
430
431 pub fn is_primitive_sd(sd: &StructureDefinition) -> bool {
432 if let StructureDefinitionKind::PrimitiveType(_) = sd.kind.as_ref() {
433 true
434 } else {
435 false
436 }
437 }
438
439 pub fn is_typechoice(element: &ElementDefinition) -> bool {
440 extract::field_types(element).len() > 1
441 }
442}
443
444pub mod load {
445 use std::path::Path;
446
447 use haste_fhir_model::r4::generated::{
448 resources::{Resource, StructureDefinition},
449 terminology::StructureDefinitionKind,
450 };
451
452 use crate::utilities::extract;
453
454 pub fn load_from_file(file_path: &Path) -> Result<Resource, String> {
455 let data = std::fs::read_to_string(file_path)
456 .map_err(|e| format!("Failed to read file: {}", e))?;
457
458 let resource = haste_fhir_serialization_json::from_str::<Resource>(&data)
459 .map_err(|e| format!("Failed to parse JSON: {}", e))?;
460
461 Ok(resource)
462 }
463
464 pub fn get_structure_definitions<'a>(
465 resource: &'a Resource,
466 level: Option<&'static str>,
467 ) -> Result<Vec<&'a StructureDefinition>, String> {
468 match resource {
469 Resource::Bundle(bundle) => {
470 if let Some(entries) = bundle.entry.as_ref() {
471 let sds = entries
472 .iter()
473 .filter_map(|e| e.resource.as_ref())
474 .filter_map(|sd| match sd.as_ref() {
475 Resource::StructureDefinition(sd) => Some(sd),
476 _ => None,
477 });
478
479 let filtered_sds = sds.filter(move |sd| {
480 if let Some(level) = level {
481 match sd.kind.as_ref() {
482 StructureDefinitionKind::Resource(_)
483 | StructureDefinitionKind::Null(_) => level == "resource",
484 StructureDefinitionKind::ComplexType(_) => level == "complex-type",
485 StructureDefinitionKind::PrimitiveType(_) => {
486 level == "primitive-type"
487 }
488 _ => false,
489 }
490 } else {
491 true
492 }
493 });
494
495 Ok(filtered_sds.collect())
496 } else {
497 Ok(vec![])
498 }
499 }
500 Resource::StructureDefinition(sd) => {
501 let resources = std::iter::once(sd);
502 let filtered_resources = resources.filter(|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(_) => level == "primitive-type",
509 _ => false,
510 }
511 } else {
512 true
513 }
514 });
515
516 Ok(filtered_resources.collect())
517 }
518 _ => Ok(vec![]),
519 }
520 }
521}