haste_fhir_converter/
lib.rs1use std::{path::Path, sync::Arc};
2
3use haste_fhir_model::r4::generated::{resources::Resource, terminology::IssueType};
4use haste_fhir_operation_error::OperationOutcomeError;
5use haste_hl7v2::parser::ParsedHL7V2Message;
6use minijinja::{Environment, Template, Value};
7
8mod jinja_extensions;
9mod liquid_extensions;
10
11pub enum Input {
12 HL7V2(String),
13 FHIR(Resource),
14 JSON(serde_json::Value),
15}
16
17pub fn convert_input(input: Input) -> Result<minijinja::Value, OperationOutcomeError> {
18 match input {
19 Input::HL7V2(message) => {
20 let parsed_message = ParsedHL7V2Message::try_from(message.as_str())?.0;
21
22 Ok(Value::from_dyn_object(Arc::new(
23 jinja_extensions::conversions::hl7v2::JHL7V2::new(parsed_message),
24 )))
25 }
26 Input::FHIR(resource) => Ok(minijinja::Value::from_serialize(resource)),
27 Input::JSON(json) => Ok(minijinja::Value::from_serialize(json)),
28 }
29}
30
31#[derive(Clone, Copy, Debug)]
32pub enum OutputFormat {
33 FHIR,
34 JSON,
35 HL7V2,
36}
37
38pub enum Output {
39 FHIR(Resource),
40 JSON(serde_json::Value),
41 HL7V2(String),
42}
43
44fn derive_template_name(template_dir: &Path, path: &Path) -> Option<String> {
46 let relative_path = path.strip_prefix(template_dir).unwrap_or(path);
47 let Some(template_file_stem) = relative_path.file_stem().and_then(|s| s.to_str()) else {
48 eprintln!("Failed to get template name from path: {:?}", path);
49 return None;
50 };
51 let Some(parent) = relative_path.parent() else {
52 eprintln!(
53 "Failed to get parent directory for template: {:?}",
54 relative_path
55 );
56 return None;
57 };
58
59 let template_name_path = parent.join(template_file_stem);
60
61 let Some(template_name) = template_name_path.to_str() else {
62 eprintln!(
63 "Failed to convert template name to string: {:?}",
64 template_name_path
65 );
66 return None;
67 };
68
69 Some(template_name.to_string())
70}
71
72fn add_template(env: &mut Environment<'_>, template_dir: &Path, path: &Path) -> Option<()> {
73 let Ok(template_content) = std::fs::read_to_string(path) else {
74 eprintln!("Failed to read template file: {:?}", path);
75 return None;
76 };
77
78 let Some(template_name) = derive_template_name(template_dir, path) else {
79 eprintln!("Failed to derive template name for file: {:?}", path);
80 return None;
81 };
82
83 println!("Adding template '{}' from file: {:?}", template_name, path);
84
85 if let Err(e) = env.add_template_owned(template_name.to_string(), template_content) {
86 eprintln!("Failed to add template '{}': {}", template_name, e);
87 }
88
89 Some(())
90}
91
92pub fn create_environment<'a>(template_dir: Option<&str>) -> Environment<'a> {
93 let mut env = Environment::new();
94 env.add_filter(
95 "hl7v2_segments",
96 jinja_extensions::filters::hl7v2::hl7v2_segments,
97 );
98
99 if let Some(template_dir) = template_dir {
100 let template_dir = Path::new(template_dir);
101 walkdir::WalkDir::new(template_dir)
102 .into_iter()
103 .filter_map(|e| e.ok())
104 .filter(|e| {
105 e.file_type().is_file()
106 && e.path()
107 .extension()
108 .map_or(false, |ext| ext == "jinja" || ext == "j2")
109 })
110 .for_each(|entry| {
111 let path = entry.path();
112 add_template(&mut env, template_dir, path);
113 });
114 }
115
116 env
117}
118
119pub fn transform<S>(
120 template: &Template<'_, '_>,
121 ctx: S,
122 output: &OutputFormat,
123) -> Result<Output, OperationOutcomeError>
124where
125 S: serde::Serialize,
126{
127 let output_data = template
128 .render(ctx)
129 .map_err(|e| OperationOutcomeError::error(IssueType::INVALID, e.to_string()))?;
130
131 match output {
132 OutputFormat::FHIR => Ok(Output::FHIR(
133 serde_json::from_str(&output_data)
134 .map_err(|e| OperationOutcomeError::error(IssueType::INVALID, e.to_string()))?,
135 )),
136 OutputFormat::JSON => Ok(Output::JSON(
137 serde_json::from_str::<serde_json::Value>(&output_data)
138 .map_err(|e| OperationOutcomeError::error(IssueType::INVALID, e.to_string()))?,
139 )),
140 OutputFormat::HL7V2 => {
141 ParsedHL7V2Message::try_from(output_data.as_str())?.0;
143
144 Ok(Output::HL7V2(output_data))
145 }
146 }
147}