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