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(Box<Resource>),
14 JSON(serde_json::Value),
15}
16
17pub fn convert_input(input: Input) -> Result<minijinja::Value, OperationOutcomeError> {
28 match input {
29 Input::HL7V2(message) => {
30 let parsed_message = ParsedHL7V2Message::try_from(message.as_str())?.0;
31
32 Ok(Value::from_dyn_object(Arc::new(
33 jinja_extensions::conversions::hl7v2::JHL7V2::new(parsed_message),
34 )))
35 }
36 Input::FHIR(resource) => Ok(minijinja::Value::from_serialize(resource)),
37 Input::JSON(json) => Ok(minijinja::Value::from_serialize(json)),
38 }
39}
40
41#[derive(Clone, Copy, Debug)]
42pub enum OutputFormat {
43 FHIR,
44 JSON,
45 HL7V2,
46}
47
48pub enum Output {
49 FHIR(Box<Resource>),
50 JSON(serde_json::Value),
51 HL7V2(String),
52}
53
54fn derive_template_name(template_dir: &Path, path: &Path) -> Option<String> {
56 let relative_path = path.strip_prefix(template_dir).unwrap_or(path);
57 let Some(template_file_stem) = relative_path.file_stem().and_then(|s| s.to_str()) else {
58 eprintln!("Failed to get template name from path: {}", path.display());
59 return None;
60 };
61 let Some(parent) = relative_path.parent() else {
62 eprintln!(
63 "Failed to get parent directory for template: {}",
64 relative_path.display()
65 );
66 return None;
67 };
68
69 let template_name_path = parent.join(template_file_stem);
70
71 let Some(template_name) = template_name_path.to_str() else {
72 eprintln!(
73 "Failed to convert template name to string: {}",
74 template_name_path.display()
75 );
76 return None;
77 };
78
79 Some(template_name.to_string())
80}
81
82fn add_template(env: &mut Environment<'_>, template_dir: &Path, path: &Path) -> Option<()> {
83 let Ok(template_content) = std::fs::read_to_string(path) else {
84 eprintln!("Failed to read template file: {}", path.display());
85 return None;
86 };
87
88 let Some(template_name) = derive_template_name(template_dir, path) else {
89 eprintln!(
90 "Failed to derive template name for file: {}",
91 path.display()
92 );
93 return None;
94 };
95
96 println!(
97 "Adding template '{template_name}' from file: {}",
98 path.display()
99 );
100
101 if let Err(e) = env.add_template_owned(template_name.clone(), template_content) {
102 eprintln!("Failed to add template '{template_name}': {e}");
103 }
104
105 Some(())
106}
107
108pub fn create_environment<'a>(template_dir: Option<&str>) -> Environment<'a> {
109 let mut env = Environment::new();
110 env.add_filter(
111 "hl7v2_segments",
112 jinja_extensions::filters::hl7v2::hl7v2_segments,
113 );
114
115 if let Some(template_dir) = template_dir {
116 let template_dir = Path::new(template_dir);
117 walkdir::WalkDir::new(template_dir)
118 .into_iter()
119 .filter_map(std::result::Result::ok)
120 .filter(|e| {
121 e.file_type().is_file()
122 && e.path()
123 .extension()
124 .is_some_and(|ext| ext == "jinja" || ext == "j2")
125 })
126 .for_each(|entry| {
127 let path = entry.path();
128 add_template(&mut env, template_dir, path);
129 });
130 }
131
132 env
133}
134
135pub fn transform<S>(
153 template: &Template<'_, '_>,
154 ctx: S,
155 output: &OutputFormat,
156) -> Result<Output, OperationOutcomeError>
157where
158 S: serde::Serialize,
159{
160 let output_data = template
161 .render(ctx)
162 .map_err(|e| OperationOutcomeError::error(IssueType::invalid(), e.to_string()))?;
163
164 match output {
165 OutputFormat::FHIR => Ok(Output::FHIR(serde_json::from_str(&output_data).map_err(
166 |e| OperationOutcomeError::error(IssueType::invalid(), e.to_string()),
167 )?)),
168 OutputFormat::JSON => Ok(Output::JSON(
169 serde_json::from_str::<serde_json::Value>(&output_data)
170 .map_err(|e| OperationOutcomeError::error(IssueType::invalid(), e.to_string()))?,
171 )),
172 OutputFormat::HL7V2 => {
173 ParsedHL7V2Message::try_from(output_data.as_str())?;
175
176 Ok(Output::HL7V2(output_data))
177 }
178 }
179}