#![expect(
clippy::arithmetic_side_effects,
clippy::allow_attributes,
clippy::allow_attributes_without_reason,
clippy::cast_precision_loss,
clippy::exit,
clippy::indexing_slicing,
clippy::unchecked_time_subtraction,
clippy::uninlined_format_args,
clippy::unnecessary_debug_formatting,
clippy::unwrap_used,
reason = "pre-existing CLI reporting and table-rendering debt is tracked in policy/clippy-debt.toml"
)]
#![cfg_attr(
test,
expect(
clippy::expect_used,
reason = "pre-existing CLI config tests use static fixture expects; cleanup is tracked in policy/clippy-debt.toml"
)
)]
use clap::{Parser, Subcommand};
use hl7v2::synthetic::generate::{Template, generate};
use hl7v2::{
AckCode as GenAckCode, Event, Message, StreamParser, ack, load_profile, normalize, parse,
parse_mllp, to_json, validate, wrap_mllp, write, write_mllp,
};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process;
mod config;
mod monitor;
mod serve;
#[cfg(test)]
mod tests;
#[derive(Parser)]
#[command(
name = "hl7v2",
about = "HL7 v2 parser, validator, and generator",
version
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
Parse {
input: PathBuf,
#[arg(long)]
json: bool,
#[arg(long)]
canonical_delims: bool,
#[arg(long)]
envelope: bool,
#[arg(long)]
mllp: bool,
#[arg(long)]
streaming: bool,
#[arg(long)]
summary: bool,
},
Norm {
input: PathBuf,
#[arg(long)]
canonical_delims: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
mllp_in: bool,
#[arg(long)]
mllp_out: bool,
#[arg(long)]
summary: bool,
},
Val {
input: PathBuf,
#[arg(long)]
profile: PathBuf,
#[arg(long)]
mllp: bool,
#[arg(long)]
detailed: bool,
#[arg(long, value_enum, default_value = "text")]
report: ReportFormat,
#[arg(long)]
summary: bool,
},
Stats {
input: PathBuf,
#[arg(long)]
mllp: bool,
#[arg(long)]
distributions: bool,
#[arg(long, value_enum, default_value = "text")]
format: ReportFormat,
},
Ack {
input: PathBuf,
#[arg(long)]
mode: AckMode,
#[arg(long)]
code: AckCode,
#[arg(long)]
mllp_in: bool,
#[arg(long)]
mllp_out: bool,
#[arg(long)]
summary: bool,
},
Gen {
#[arg(long)]
profile: PathBuf,
#[arg(long)]
seed: u64,
#[arg(long)]
count: usize,
#[arg(long)]
out: PathBuf,
#[arg(long)]
stats: bool,
},
Serve {
#[arg(long, value_enum, default_value = "http")]
mode: ServerMode,
#[arg(short, long, default_value = "8080")]
port: u16,
#[arg(long, default_value = "0.0.0.0")]
host: String,
#[arg(long, default_value = "10485760")]
max_body_size: usize,
},
Interactive,
}
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq)]
enum ServerMode {
Http,
Grpc,
}
#[derive(clap::ValueEnum, Clone, Debug, PartialEq)]
enum AckMode {
Original,
Enhanced,
}
#[derive(clap::ValueEnum, Clone, Debug)]
#[value(rename_all = "UPPERCASE")]
enum AckCode {
AA,
AE,
AR,
CA,
CE,
CR,
}
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Default)]
enum ReportFormat {
#[default]
Text,
Json,
Yaml,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive(tracing::Level::INFO.into()),
)
.init();
let cli = Cli::parse();
let result = match &cli.command {
Commands::Parse {
input,
json,
canonical_delims,
envelope,
mllp,
streaming,
summary,
} => parse_command(
input,
*json,
*canonical_delims,
*envelope,
*mllp,
*streaming,
*summary,
),
Commands::Norm {
input,
canonical_delims,
output,
mllp_in,
mllp_out,
summary,
} => norm_command(
input,
*canonical_delims,
output,
*mllp_in,
*mllp_out,
*summary,
),
Commands::Val {
input,
profile,
mllp,
detailed,
report,
summary,
} => val_command(input, profile, *mllp, *detailed, report, *summary),
Commands::Stats {
input,
mllp,
distributions,
format,
} => stats_command(input, *mllp, *distributions, format),
Commands::Ack {
input,
mode,
code,
mllp_in,
mllp_out,
summary,
} => ack_command(input, mode, code, *mllp_in, *mllp_out, *summary),
Commands::Gen {
profile,
seed,
count,
out,
stats,
} => gen_command(profile, *seed, *count, out, *stats),
Commands::Serve {
mode,
port,
host,
max_body_size,
} => serve::run_server(mode, *port, host, *max_body_size).await,
Commands::Interactive => interactive_mode(),
};
if let Err(e) = result {
eprintln!("Error: {}", e);
process::exit(1);
}
}
fn display_performance_stats(monitor: &monitor::PerformanceMonitor) {
println!();
println!("Performance Statistics:");
println!(" Total execution time: {:?}", monitor.elapsed());
let metrics = monitor.get_metrics();
if !metrics.is_empty() {
println!(" Detailed metrics:");
for (name, duration) in metrics {
println!(" {}: {:?}", name, duration);
}
}
let system_info = monitor::get_system_info();
println!(" System information:");
if let Some(cpu_usage) = system_info.cpu.cpu_usage_percent {
println!(" CPU usage: {:.2}%", cpu_usage);
}
println!(" Total memory: {} bytes", system_info.total_memory);
println!(" Used memory: {} bytes", system_info.used_memory);
if let Some(rss) = system_info.memory.resident_set_size {
println!(" Process memory (RSS): {} bytes", rss);
}
if let Some(vms) = system_info.memory.virtual_memory_size {
println!(" Process memory (VMS): {} bytes", vms);
}
}
fn parse_command(
input: &PathBuf,
json: bool,
canonical_delims: bool,
envelope: bool,
mllp: bool,
streaming: bool,
summary: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let mut monitor = monitor::PerformanceMonitor::new();
if streaming {
let file = fs::File::open(input)?;
let reader = std::io::BufReader::new(file);
let mut parser = StreamParser::new(reader);
let mut message_count = 0;
let mut event_count = 0;
while let Ok(Some(event)) = parser.next_event() {
event_count += 1;
if matches!(event, Event::StartMessage { .. }) {
message_count += 1;
}
if json {
let event_json = match &event {
Event::StartMessage { delims } => serde_json::json!({
"event": "start_message",
"delims": {
"field": delims.field.to_string(),
"comp": delims.comp.to_string(),
"rep": delims.rep.to_string(),
"esc": delims.esc.to_string(),
"sub": delims.sub.to_string(),
}
}),
Event::Segment { id } => serde_json::json!({
"event": "segment",
"id": String::from_utf8_lossy(id)
}),
Event::Field { num, raw } => serde_json::json!({
"event": "field",
"num": num,
"raw": String::from_utf8_lossy(raw)
}),
Event::EndMessage => serde_json::json!({
"event": "end_message"
}),
};
println!("{}", serde_json::to_string(&event_json)?);
} else {
match event {
Event::StartMessage { delims } => println!(
"--- Message {} Start (delims: {:?}) ---",
message_count, delims
),
Event::Segment { id } => println!("Segment: {}", String::from_utf8_lossy(&id)),
Event::Field { num, raw } => {
println!(" Field {}: {}", num, String::from_utf8_lossy(&raw));
}
Event::EndMessage => println!("--- Message End ---"),
}
}
}
if summary {
println!("\nStreaming Parse Summary:");
println!(" Input file: {:?}", input);
println!(" Messages: {}", message_count);
println!(" Total events: {}", event_count);
display_performance_stats(&monitor);
}
return Ok(());
}
let contents = fs::read(input)?;
let file_size = contents.len();
let read_time = monitor.elapsed();
monitor.record_metric("File read", read_time);
let message = if mllp {
parse_mllp(&contents)?
} else {
parse(&contents)?
};
let parse_time = monitor.elapsed() - read_time;
monitor.record_metric("Message parsing", parse_time);
let segment_count = message.segments.len();
if canonical_delims {
let original_bytes = write(&message);
let output_bytes = normalize(&original_bytes, true)?;
if envelope {
let mllp_bytes = wrap_mllp(&output_bytes);
std::io::stdout().write_all(&mllp_bytes)?;
} else {
std::io::stdout().write_all(&output_bytes)?;
}
} else if envelope {
let output_bytes = write(&message);
let mllp_bytes = wrap_mllp(&output_bytes);
std::io::stdout().write_all(&mllp_bytes)?;
} else {
let json_value = to_json(&message);
let json_conversion_time = monitor.elapsed() - read_time - parse_time;
monitor.record_metric("JSON conversion", json_conversion_time);
if json {
println!("{}", serde_json::to_string_pretty(&json_value)?);
} else {
println!("{}", serde_json::to_string(&json_value)?);
}
}
let output_time = monitor.elapsed() - read_time - parse_time;
monitor.record_metric("Output", output_time);
if summary {
println!();
println!("Parse Summary:");
println!(" Input file: {:?}", input);
println!(" File size: {} bytes", file_size);
println!(" Segments: {}", segment_count);
println!(" Streaming mode: {}", streaming);
println!(" Canonical delimiters: {}", canonical_delims);
println!(" MLLP envelope: {}", envelope);
println!(
" Delimiters: |^~\\& (field={} comp={} rep={} esc={} sub={})",
message.delims.field,
message.delims.comp,
message.delims.rep,
message.delims.esc,
message.delims.sub
);
display_performance_stats(&monitor);
}
Ok(())
}
fn norm_command(
input: &PathBuf,
canonical_delims: bool,
output: &Option<PathBuf>,
mllp_in: bool,
mllp_out: bool,
summary: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let mut monitor = monitor::PerformanceMonitor::new();
let contents = fs::read(input)?;
let input_file_size = contents.len();
let read_time = monitor.elapsed();
monitor.record_metric("File read", read_time);
let message = if mllp_in {
parse_mllp(&contents)?
} else {
parse(&contents)?
};
let parse_time = monitor.elapsed() - read_time;
monitor.record_metric("Message parsing", parse_time);
let segment_count = message.segments.len();
let original_bytes = write(&message);
let normalized_bytes = if canonical_delims {
normalize(&original_bytes, true)?
} else {
original_bytes
};
let normalize_time = monitor.elapsed() - read_time - parse_time;
monitor.record_metric("Message normalization", normalize_time);
let output_bytes = if mllp_out {
wrap_mllp(&normalized_bytes)
} else {
normalized_bytes
};
let mllp_time = monitor.elapsed() - read_time - parse_time - normalize_time;
monitor.record_metric("MLLP processing", mllp_time);
if let Some(output_path) = output {
fs::write(output_path, &output_bytes)?;
if summary {
let write_time =
monitor.elapsed() - read_time - parse_time - normalize_time - mllp_time;
monitor.record_metric("File write", write_time);
println!();
println!("Normalize Summary:");
println!(" Input file: {:?}", input);
println!(" Output file: {:?}", output_path);
println!(" Input size: {} bytes", input_file_size);
println!(" Output size: {} bytes", output_bytes.len());
println!(" Segments: {}", segment_count);
println!(" Canonical delimiters: {}", canonical_delims);
println!(" MLLP output: {}", mllp_out);
display_performance_stats(&monitor);
}
} else {
std::io::stdout().write_all(&output_bytes)?;
if summary {
let write_time =
monitor.elapsed() - read_time - parse_time - normalize_time - mllp_time;
monitor.record_metric("Output write", write_time);
println!();
println!("Normalize Summary:");
println!(" Input file: {:?}", input);
println!(" Output: stdout");
println!(" Input size: {} bytes", input_file_size);
println!(" Output size: {} bytes", output_bytes.len());
println!(" Segments: {}", segment_count);
println!(" Canonical delimiters: {}", canonical_delims);
println!(" MLLP output: {}", mllp_out);
display_performance_stats(&monitor);
}
}
Ok(())
}
fn val_command(
input: &PathBuf,
profile: &PathBuf,
mllp: bool,
detailed: bool,
report: &ReportFormat,
summary: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let mut monitor = monitor::PerformanceMonitor::new();
let contents = fs::read(input)?;
let file_size = contents.len();
let read_time = monitor.elapsed();
monitor.record_metric("File read", read_time);
let message = if mllp {
parse_mllp(&contents)?
} else {
parse(&contents)?
};
let parse_time = monitor.elapsed() - read_time;
monitor.record_metric("Message parsing", parse_time);
let profile_yaml = fs::read_to_string(profile)?;
let read_profile_time = monitor.elapsed() - read_time - parse_time;
monitor.record_metric("Profile read", read_profile_time);
let loaded_profile = load_profile(&profile_yaml)?;
let load_profile_time = monitor.elapsed() - read_time - parse_time - read_profile_time;
monitor.record_metric("Profile loading", load_profile_time);
let results = validate(&message, &loaded_profile);
let validation_time =
monitor.elapsed() - read_time - parse_time - read_profile_time - load_profile_time;
monitor.record_metric("Message validation", validation_time);
let validation_report = ValidationReport {
input_file: input.to_string_lossy().to_string(),
profile_file: profile.to_string_lossy().to_string(),
file_size,
segment_count: message.segments.len(),
is_valid: results.is_empty(),
issue_count: results.len(),
issues: results.iter().map(|r| format!("{:?}", r)).collect(),
};
match report {
ReportFormat::Json => {
let json_output = serde_json::to_string_pretty(&validation_report)?;
println!("{}", json_output);
}
ReportFormat::Yaml => {
let yaml_output = serde_yaml::to_string(&validation_report)?;
println!("{}", yaml_output);
}
ReportFormat::Text => {
if results.is_empty() {
println!("Validation passed: No issues found");
} else if detailed {
println!("Validation issues found:");
for result in &results {
println!(" - {:?}", result);
}
} else {
println!("Validation failed: {} issues found", results.len());
}
}
}
if summary && *report == ReportFormat::Text {
println!();
println!("Validation Summary:");
println!(" Input file: {:?}", input);
println!(" Profile file: {:?}", profile);
println!(" File size: {} bytes", file_size);
println!(" Segments: {}", message.segments.len());
println!(" Issues found: {}", results.len());
display_performance_stats(&monitor);
}
if !results.is_empty() {
std::process::exit(1);
}
Ok(())
}
#[derive(serde::Serialize)]
struct ValidationReport {
input_file: String,
profile_file: String,
file_size: usize,
segment_count: usize,
is_valid: bool,
issue_count: usize,
issues: Vec<String>,
}
#[derive(serde::Serialize)]
struct StatsReport {
input_file: String,
file_size: usize,
segment_count: usize,
segments: Vec<SegmentStats>,
field_distributions: Option<Vec<FieldDistribution>>,
}
#[derive(serde::Serialize)]
struct SegmentStats {
segment_id: String,
count: usize,
}
#[derive(serde::Serialize)]
struct FieldDistribution {
path: String,
unique_values: usize,
sample_values: Vec<String>,
}
fn collect_stats(message: &Message, distributions: bool) -> StatsReport {
let mut segment_counts: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for segment in &message.segments {
*segment_counts
.entry(segment.id_str().to_string())
.or_insert(0) += 1;
}
let segments: Vec<SegmentStats> = segment_counts
.into_iter()
.map(|(id, count)| SegmentStats {
segment_id: id,
count,
})
.collect();
let field_distributions = if distributions {
let mut dists: Vec<FieldDistribution> = Vec::new();
for segment in &message.segments {
let segment_id = segment.id_str();
for (field_idx, field) in segment.fields.iter().enumerate().take(5) {
if field_idx == 0 {
continue; }
let path = format!("{}.{}", segment_id, field_idx);
let value = field.first_text().unwrap_or("").to_string();
if let Some(existing) = dists.iter_mut().find(|d| d.path == path) {
if !existing.sample_values.contains(&value) && existing.sample_values.len() < 10
{
existing.sample_values.push(value);
}
existing.unique_values = existing.sample_values.len();
} else {
dists.push(FieldDistribution {
path,
unique_values: 1,
sample_values: vec![value],
});
}
}
}
Some(dists)
} else {
None
};
StatsReport {
input_file: String::new(), file_size: 0, segment_count: message.segments.len(),
segments,
field_distributions,
}
}
fn format_stats_report(
report: &StatsReport,
format: &ReportFormat,
) -> Result<String, Box<dyn std::error::Error>> {
match format {
ReportFormat::Json => Ok(serde_json::to_string_pretty(report)?),
ReportFormat::Yaml => Ok(serde_yaml::to_string(report)?),
ReportFormat::Text => {
let mut output = String::new();
output.push_str("Message Statistics:\n");
output.push_str(&format!(" Input file: {}\n", report.input_file));
output.push_str(&format!(" File size: {} bytes\n", report.file_size));
output.push_str(&format!(" Total segments: {}\n", report.segment_count));
output.push('\n');
output.push_str("Segment breakdown:\n");
for seg in &report.segments {
output.push_str(&format!(
" {}: {} occurrence(s)\n",
seg.segment_id, seg.count
));
}
if let Some(dists) = &report.field_distributions {
output.push('\n');
output.push_str("Field value distributions:\n");
for dist in dists {
output.push_str(&format!(" {}:\n", dist.path));
output.push_str(&format!(" Unique values: {}\n", dist.unique_values));
if !dist.sample_values.is_empty() {
output.push_str(&format!(
" Sample values: {:?}\n",
dist.sample_values.iter().take(5).collect::<Vec<_>>()
));
}
}
}
Ok(output)
}
}
}
fn stats_command(
input: &PathBuf,
mllp: bool,
distributions: bool,
format: &ReportFormat,
) -> Result<(), Box<dyn std::error::Error>> {
let mut monitor = monitor::PerformanceMonitor::new();
let contents = fs::read(input)?;
let file_size = contents.len();
let read_time = monitor.elapsed();
monitor.record_metric("File read", read_time);
let message = if mllp {
parse_mllp(&contents)?
} else {
parse(&contents)?
};
let parse_time = monitor.elapsed() - read_time;
monitor.record_metric("Message parsing", parse_time);
let mut stats_report = collect_stats(&message, distributions);
stats_report.input_file = input.to_string_lossy().to_string();
stats_report.file_size = file_size;
let report_output = format_stats_report(&stats_report, format)?;
println!("{}", report_output);
let output_time = monitor.elapsed() - read_time - parse_time;
monitor.record_metric("Output", output_time);
Ok(())
}
fn ack_command(
input: &PathBuf,
mode: &AckMode,
code: &AckCode,
mllp_in: bool,
mllp_out: bool,
summary: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let mut monitor = monitor::PerformanceMonitor::new();
let contents = fs::read(input)?;
let input_file_size = contents.len();
let read_time = monitor.elapsed();
monitor.record_metric("File read", read_time);
let message = if mllp_in {
parse_mllp(&contents)?
} else {
parse(&contents)?
};
let parse_time = monitor.elapsed() - read_time;
monitor.record_metric("Message parsing", parse_time);
let ack_code = match code {
AckCode::AA => GenAckCode::AA,
AckCode::AE => GenAckCode::AE,
AckCode::AR => GenAckCode::AR,
AckCode::CA => GenAckCode::CA,
AckCode::CE => GenAckCode::CE,
AckCode::CR => GenAckCode::CR,
};
let ack_message = ack(&message, ack_code)?;
let ack_generation_time = monitor.elapsed() - read_time - parse_time;
monitor.record_metric("ACK generation", ack_generation_time);
let ack_bytes = if mllp_out {
write_mllp(&ack_message)
} else {
write(&ack_message)
};
let mllp_processing_time = monitor.elapsed() - read_time - parse_time - ack_generation_time;
monitor.record_metric("MLLP processing", mllp_processing_time);
std::io::stdout().write_all(&ack_bytes)?;
if summary {
let write_time =
monitor.elapsed() - read_time - parse_time - ack_generation_time - mllp_processing_time;
monitor.record_metric("Output write", write_time);
println!();
println!("ACK Generation Summary:");
println!(" Input file: {:?}", input);
println!(" Mode: {:?}", mode);
println!(" Code: {:?}", code);
println!(" Input size: {} bytes", input_file_size);
println!(" Output size: {} bytes", ack_bytes.len());
println!(" Segments in original: {}", message.segments.len());
println!(" Segments in ACK: {}", ack_message.segments.len());
println!(" MLLP input: {}", mllp_in);
println!(" MLLP output: {}", mllp_out);
display_performance_stats(&monitor);
}
Ok(())
}
fn interactive_mode() -> Result<(), Box<dyn std::error::Error>> {
println!("HL7 v2 Toolkit - Interactive Mode");
println!("Type 'help' for available commands or 'exit' to quit.");
println!();
loop {
print!("hl7v2> ");
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let input = input.trim();
match input {
"exit" | "quit" => {
println!("Goodbye!");
break;
}
"help" => {
println!("Available commands:");
println!(" parse <file> [options] - Parse an HL7 message");
println!(" norm <file> [options] - Normalize an HL7 message");
println!(" val <file> <profile> - Validate an HL7 message");
println!(" ack <file> [options] - Generate an ACK for an HL7 message");
println!(" gen <profile> [options] - Generate synthetic messages");
println!(" help - Show this help message");
println!(" exit|quit - Exit interactive mode");
println!();
}
_ => {
if input.starts_with("parse ") {
handle_parse_command(input)?;
} else if input.starts_with("norm ") {
handle_norm_command(input)?;
} else if input.starts_with("val ") {
handle_val_command(input)?;
} else if input.starts_with("ack ") {
handle_ack_command(input)?;
} else if input.starts_with("gen ") {
handle_gen_command(input)?;
} else if !input.is_empty() {
println!("Unknown command. Type 'help' for available commands.");
}
}
}
}
Ok(())
}
fn handle_parse_command(input: &str) -> Result<(), Box<dyn std::error::Error>> {
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() < 2 {
println!(
"Usage: parse <file> [--json] [--canonical-delims] [--envelope] [--mllp] [--streaming] [--summary]"
);
return Ok(());
}
let file_path = PathBuf::from(parts[1]);
let mut json = false;
let mut canonical_delims = false;
let mut envelope = false;
let mut mllp = false;
let mut streaming = false;
let mut summary = false;
for part in &parts[2..] {
match *part {
"--json" => json = true,
"--canonical-delims" => canonical_delims = true,
"--envelope" => envelope = true,
"--mllp" => mllp = true,
"--streaming" => streaming = true,
"--summary" => summary = true,
_ => println!("Unknown option: {}", part),
}
}
parse_command(
&file_path,
json,
canonical_delims,
envelope,
mllp,
streaming,
summary,
)
}
fn handle_norm_command(input: &str) -> Result<(), Box<dyn std::error::Error>> {
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() < 2 {
println!("Usage: norm <file> [--canonical-delims] [--mllp-in] [--mllp-out] [--summary]");
return Ok(());
}
let file_path = PathBuf::from(parts[1]);
let mut canonical_delims = false;
let mut mllp_in = false;
let mut mllp_out = false;
let mut summary = false;
for part in &parts[2..] {
match *part {
"--canonical-delims" => canonical_delims = true,
"--mllp-in" => mllp_in = true,
"--mllp-out" => mllp_out = true,
"--summary" => summary = true,
_ => println!("Unknown option: {}", part),
}
}
norm_command(
&file_path,
canonical_delims,
&None,
mllp_in,
mllp_out,
summary,
)
}
fn handle_val_command(input: &str) -> Result<(), Box<dyn std::error::Error>> {
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() < 3 {
println!(
"Usage: val <file> <profile> [--mllp] [--detailed] [--report <text|json|yaml>] [--summary]"
);
return Ok(());
}
let file_path = PathBuf::from(parts[1]);
let profile_path = PathBuf::from(parts[2]);
let mut mllp = false;
let mut detailed = false;
let mut summary = false;
let mut report = ReportFormat::Text;
let mut i = 3;
while i < parts.len() {
match parts[i] {
"--mllp" => {
mllp = true;
i += 1;
}
"--detailed" => {
detailed = true;
i += 1;
}
"--summary" => {
summary = true;
i += 1;
}
"--report" => {
if i + 1 < parts.len() {
report = match parts[i + 1] {
"json" => ReportFormat::Json,
"yaml" => ReportFormat::Yaml,
_ => ReportFormat::Text,
};
i += 2;
} else {
println!("Missing report format value");
return Ok(());
}
}
_ => {
println!("Unknown option: {}", parts[i]);
i += 1;
}
}
}
val_command(&file_path, &profile_path, mllp, detailed, &report, summary)
}
fn handle_ack_command(input: &str) -> Result<(), Box<dyn std::error::Error>> {
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() < 2 {
println!(
"Usage: ack <file> [--mode <original|enhanced>] [--code <AA|AE|AR|CA|CE|CR>] [--mllp-in] [--mllp-out] [--summary]"
);
return Ok(());
}
let file_path = PathBuf::from(parts[1]);
let mut mode = AckMode::Original;
let mut code = AckCode::AA;
let mut mllp_in = false;
let mut mllp_out = false;
let mut summary = false;
let mut i = 2;
while i < parts.len() {
match parts[i] {
"--mode" => {
if i + 1 < parts.len() {
mode = match parts[i + 1] {
"original" => AckMode::Original,
"enhanced" => AckMode::Enhanced,
_ => {
println!("Invalid mode: {}", parts[i + 1]);
return Ok(());
}
};
i += 2;
} else {
println!("Missing mode value");
return Ok(());
}
}
"--code" => {
if i + 1 < parts.len() {
code = match parts[i + 1] {
"AA" => AckCode::AA,
"AE" => AckCode::AE,
"AR" => AckCode::AR,
"CA" => AckCode::CA,
"CE" => AckCode::CE,
"CR" => AckCode::CR,
_ => {
println!("Invalid code: {}", parts[i + 1]);
return Ok(());
}
};
i += 2;
} else {
println!("Missing code value");
return Ok(());
}
}
"--mllp-in" => {
mllp_in = true;
i += 1;
}
"--mllp-out" => {
mllp_out = true;
i += 1;
}
"--summary" => {
summary = true;
i += 1;
}
_ => {
println!("Unknown option: {}", parts[i]);
return Ok(());
}
}
}
ack_command(&file_path, &mode, &code, mllp_in, mllp_out, summary)
}
fn handle_gen_command(input: &str) -> Result<(), Box<dyn std::error::Error>> {
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() < 2 {
println!(
"Usage: gen <profile> [--seed <number>] [--count <number>] [--out <directory>] [--stats]"
);
return Ok(());
}
let profile_path = PathBuf::from(parts[1]);
let mut seed = 42;
let mut count = 1;
let mut out = PathBuf::from("output");
let mut stats = false;
let mut i = 2;
while i < parts.len() {
match parts[i] {
"--seed" => {
if i + 1 < parts.len() {
seed = parts[i + 1].parse().unwrap_or(42);
i += 2;
} else {
println!("Missing seed value");
return Ok(());
}
}
"--count" => {
if i + 1 < parts.len() {
count = parts[i + 1].parse().unwrap_or(1);
i += 2;
} else {
println!("Missing count value");
return Ok(());
}
}
"--out" => {
if i + 1 < parts.len() {
out = PathBuf::from(parts[i + 1]);
i += 2;
} else {
println!("Missing output directory");
return Ok(());
}
}
"--stats" => {
stats = true;
i += 1;
}
_ => {
println!("Unknown option: {}", parts[i]);
return Ok(());
}
}
}
gen_command(&profile_path, seed, count, &out, stats)
}
fn gen_command(
profile: &PathBuf,
seed: u64,
count: usize,
out: &PathBuf,
stats: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let mut monitor = monitor::PerformanceMonitor::new();
let template_yaml = fs::read_to_string(profile)?;
let read_template_time = monitor.elapsed();
monitor.record_metric("Template read", read_template_time);
let template: Template = serde_yaml::from_str(&template_yaml)?;
let parse_template_time = monitor.elapsed() - read_template_time;
monitor.record_metric("Template parsing", parse_template_time);
let messages = generate(&template, seed, count)?;
let generation_time = monitor.elapsed() - read_template_time - parse_template_time;
monitor.record_metric("Message generation", generation_time);
fs::create_dir_all(out)?;
let create_dir_time =
monitor.elapsed() - read_template_time - parse_template_time - generation_time;
monitor.record_metric("Directory creation", create_dir_time);
let mut written_files = 0;
for (i, message) in messages.iter().enumerate() {
let filename = out.join(format!("message_{:03}.hl7", i + 1));
let message_bytes = write(message);
fs::write(&filename, &message_bytes)?;
if stats {
println!("Generated message written to: {:?}", filename);
}
written_files += 1;
}
let write_time = monitor.elapsed()
- read_template_time
- parse_template_time
- generation_time
- create_dir_time;
monitor.record_metric("File writing", write_time);
if stats {
println!("Successfully generated {} messages", messages.len());
}
if stats {
println!();
println!("Generation Statistics:");
println!(" Template file: {:?}", profile);
println!(" Seed: {}", seed);
println!(" Count: {}", count);
println!(" Output directory: {:?}", out);
println!(" Messages generated: {}", messages.len());
println!(" Files written: {}", written_files);
display_performance_stats(&monitor);
}
Ok(())
}