haste_health/commands/
hl7v2.rs1use crate::cli::state::CliState;
2use clap::Subcommand;
3use haste_fhir_client::FHIRClient;
4use haste_fhir_converter::Input;
5use haste_fhir_model::r4::generated::resources::Resource;
6use haste_fhir_model::r4::generated::terminology::{BundleType, IssueType};
7use haste_fhir_operation_error::OperationOutcomeError;
8use haste_hl7v2::mllp::MllpFormatter;
9use std::collections::HashMap;
10use std::io::Write;
11use std::net::TcpListener;
12use std::sync::Arc;
13use tokio::sync::Mutex;
14
15#[derive(Subcommand, Debug)]
17pub(crate) enum HL7v2Commands {
18 Receiver {
20 #[arg(short, long)]
22 address: String,
23 #[arg(short, long)]
25 port: u16,
26 #[arg(short, long)]
29 main: String,
30 #[arg(short, long)]
32 template_dir: String,
33 },
34 Sender {
36 #[arg(short, long)]
38 address: String,
39 #[arg(short, long)]
41 port: u16,
42 },
43}
44
45pub(crate) async fn run(
47 state: Arc<Mutex<CliState>>,
48 command: &HL7v2Commands,
49) -> Result<(), OperationOutcomeError> {
50 let fhir_client = crate::cli::client::fhir_client(state).await?;
51
52 match command {
53 HL7v2Commands::Receiver {
54 address,
55 port,
56 main,
57 template_dir,
58 } => {
59 let listener = TcpListener::bind(format!("{}:{}", address, port)).unwrap();
60
61 let environment = haste_fhir_converter::create_environment(Some(template_dir));
62
63 let template = environment
64 .get_template(&main)
65 .map_err(|e| OperationOutcomeError::error(IssueType::exception(), e.to_string()))?;
66
67 for stream in listener.incoming() {
68 let mut stream = match stream {
69 Ok(s) => s,
70 Err(e) => {
71 eprintln!("Failed to accept connection: {}", e);
72 continue;
73 }
74 };
75
76 loop {
77 let frame = match MllpFormatter::read_frame(&mut stream) {
78 Ok(f) => f,
79 Err(e) => {
80 eprintln!("Connection ended: {}", e);
81 break;
82 }
83 };
84
85 let start = std::time::Instant::now();
86
87 let hl7v2_bytes = match MllpFormatter::decode(frame.as_slice()) {
88 Ok(b) => b.to_vec(),
89 Err(e) => {
90 eprintln!("Failed to decode MLLP frame: {}", e);
91 let _ = stream.write_all(&MllpFormatter::nak());
92 continue;
93 }
94 };
95
96 let hl7v2_string = String::from_utf8_lossy(&hl7v2_bytes).to_string();
97
98 let hl7v2 = haste_fhir_converter::convert_input(Input::HL7V2(hl7v2_string))?;
99
100 let mut ctx = HashMap::new();
101 ctx.insert("hl7v2", hl7v2);
102
103 let haste_fhir_converter::Output::FHIR(resource) =
104 haste_fhir_converter::transform(
105 &template,
106 ctx,
107 &haste_fhir_converter::OutputFormat::FHIR,
108 )?
109 else {
110 eprintln!("Unexpected output format from template");
111 let _ = stream.write_all(&MllpFormatter::nak());
112 continue;
113 };
114
115 tracing::info!("total transformation: {:?}", start.elapsed());
116
117 match *resource {
118 Resource::Bundle(bundle) => match &bundle.type_ {
119 b if b == &BundleType::batch() => {
120 match fhir_client.batch((), bundle).await {
121 Ok(_) => {
122 if let Err(e) = stream.write_all(&MllpFormatter::ack()) {
123 eprintln!("Failed to send ACK: {}", e);
124 break;
125 }
126 }
127 Err(e) => {
128 eprintln!("Failed to send batch {}", e);
129 if let Err(e) = stream.write_all(&MllpFormatter::nak()) {
130 eprintln!("Failed to send NAK: {}", e);
131 break;
132 }
133 }
134 }
135 }
136 b if b == &BundleType::transaction() => {
137 match fhir_client.transaction((), bundle).await {
138 Ok(_) => {
139 if let Err(e) = stream.write_all(&MllpFormatter::ack()) {
140 eprintln!("Failed to send ACK: {}", e);
141 break;
142 }
143 }
144 Err(e) => {
145 eprintln!("Failed to send transaction {}", e);
146 if let Err(e) = stream.write_all(&MllpFormatter::nak()) {
147 eprintln!("Failed to send NAK: {}", e);
148 break;
149 }
150 }
151 }
152 }
153 _ => {
154 eprintln!("Unsupported Bundle type: {:?}", bundle.type_);
155 let _ = stream.write_all(&MllpFormatter::nak());
156 continue;
157 }
158 },
159 _ => {
160 let resource_type = resource.resource_type();
161 match fhir_client.create((), resource_type, *resource).await {
162 Ok(_) => {
163 if let Err(e) = stream.write_all(&MllpFormatter::ack()) {
164 eprintln!("Failed to send ACK: {}", e);
165 break;
166 }
167 }
168 Err(e) => {
169 eprintln!("Failed to send resource {}", e);
170 if let Err(e) = stream.write_all(&MllpFormatter::nak()) {
171 eprintln!("Failed to send NAK: {}", e);
172 break;
173 }
174 }
175 }
176 }
177 }
178 }
179 }
180
181 Ok(())
182 }
183 HL7v2Commands::Sender {
184 address: _,
185 port: _,
186 } => {
187 todo!("HL7v2 sender not implemented yet");
188 }
189 }
190}