haste_health/commands/
codegen.rs1use clap::{Subcommand, ValueEnum};
2use haste_codegen::{testscript_gen, type_gen};
3use haste_fhir_model::r4::generated::terminology::IssueType;
4use haste_fhir_operation_error::OperationOutcomeError;
5use quote::quote;
6use std::{io::Write, path::Path, process::Stdio};
7
8#[derive(Clone, Debug, ValueEnum)]
10pub(crate) enum GenerateLevel {
11 Primitive,
12 Complex,
13 Resource,
14}
15
16#[derive(Subcommand, Debug)]
18pub(crate) enum CodeGen {
19 Types {
21 #[arg(short, long)]
23 input: Vec<String>,
24 #[arg(short, long)]
26 output: String,
27 #[arg(short, long)]
29 level: Option<GenerateLevel>,
30 },
31 Operations {
33 #[arg(short, long)]
35 input: Vec<String>,
36 #[arg(short, long)]
38 output: Option<String>,
39 },
40 TestScripts {
42 #[arg(short, long)]
44 input: Vec<String>,
45 #[arg(short, long)]
47 output: String,
48 },
49}
50
51fn format_code(rust_code: String) -> String {
52 let mut format_command = std::process::Command::new("rustfmt")
53 .stdin(Stdio::piped())
54 .stdout(Stdio::piped())
55 .spawn()
56 .expect("Failed to spawn child process");
57
58 let mut stdin = format_command.stdin.take().expect("Failed to open stdin");
59 std::thread::spawn(move || {
60 stdin
61 .write_all(rust_code.as_bytes())
62 .expect("Failed to write to stdin");
63 });
64
65 let command_output = format_command
66 .wait_with_output()
67 .expect("Failed to read stdout");
68
69 let formatted_code = String::from_utf8_lossy(&command_output.stdout);
70
71 formatted_code.to_string()
72}
73
74pub(crate) async fn run(command: &CodeGen) -> Result<(), OperationOutcomeError> {
76 match command {
77 CodeGen::Operations { input, output } => {
78 let generated_operation_definitions =
79 type_gen::operation_definitions::generate_operation_definitions_from_files(input)
80 .map_err(|e| OperationOutcomeError::error(IssueType::exception(), e))?;
81
82 let formatted_code = format_code(generated_operation_definitions);
83
84 match output {
85 Some(output_path) => {
86 tokio::fs::write(output_path, formatted_code.to_string())
87 .await
88 .map_err(|e| {
89 OperationOutcomeError::error(IssueType::exception(), e.to_string())
90 })?;
91 println!("Generated FHIR types written to: {}", output_path);
92 }
93 None => {
94 println!("{}", formatted_code);
95 }
96 }
97
98 Ok(())
99 }
100 CodeGen::Types {
101 input,
102 output,
103 level,
104 } => {
105 let level = {
106 match level {
107 Some(GenerateLevel::Primitive) => Some("primitive-type"),
108 Some(GenerateLevel::Complex) => Some("complex-type"),
109 Some(GenerateLevel::Resource) => Some("resource"),
110 None => None,
111 }
112 };
113
114 let rust_code = type_gen::rust_types::generate(input, level).await?;
115
116 let output_path = Path::new(output);
117 let resource_path = output_path.join("resources.rs");
118 tokio::fs::write(resource_path, format_code(rust_code.resources.to_string()))
119 .await
120 .map_err(|e| OperationOutcomeError::error(IssueType::exception(), e.to_string()))?;
121
122 let type_path = output_path.join("types.rs");
123 tokio::fs::write(type_path, format_code(rust_code.types.to_string()))
124 .await
125 .map_err(|e| OperationOutcomeError::error(IssueType::exception(), e.to_string()))?;
126
127 let terminology_path = output_path.join("terminology.rs");
128 tokio::fs::write(
129 terminology_path,
130 format_code(rust_code.terminology.to_string()),
131 )
132 .await
133 .map_err(|e| OperationOutcomeError::error(IssueType::exception(), e.to_string()))?;
134
135 let mod_path = output_path.join("mod.rs");
136 let module_code = quote! {
137 pub mod resources;
139 pub mod types;
140 pub mod terminology;
141 };
142 tokio::fs::write(mod_path, format_code(module_code.to_string()))
143 .await
144 .map_err(|e| OperationOutcomeError::error(IssueType::exception(), e.to_string()))?;
145
146 println!("Generated FHIR types written to: {}", output_path.display());
147 Ok(())
148 }
149 CodeGen::TestScripts { input, output } => {
150 let output_path = Path::new(output);
151 let testscripts = testscript_gen::generate_testscripts(input)
152 .map_err(|e| OperationOutcomeError::error(IssueType::exception(), e))?;
153
154 for testscript in &testscripts {
155 let id = testscript.id.clone().unwrap();
156 let id = id.replace("/", "_").replace(" ", "").replace(".", "_") + ".json";
157 let testscript_path = output_path.join(id);
158
159 println!("Writing TestScript to: {}", testscript_path.display());
160
161 tokio::fs::write(
162 testscript_path,
163 serde_json::to_string_pretty(testscript)
164 .expect("Failed to serialize TestScript to JSON"),
165 )
166 .await
167 .map_err(|e| OperationOutcomeError::error(IssueType::exception(), e.to_string()))?;
168 }
169
170 println!(
171 "Generated TestScripts written to: {}",
172 output_path.display()
173 );
174
175 Ok(())
176 }
177 }
178}