haste_health/commands/
codegen.rs1use clap::{Subcommand, ValueEnum};
2use haste_codegen::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}
34
35fn format_code(rust_code: String) -> String {
36 let mut format_command = std::process::Command::new("rustfmt")
37 .stdin(Stdio::piped())
38 .stdout(Stdio::piped())
39 .spawn()
40 .expect("Failed to spawn child process");
41
42 let mut stdin = format_command.stdin.take().expect("Failed to open stdin");
43 std::thread::spawn(move || {
44 stdin
45 .write_all(rust_code.as_bytes())
46 .expect("Failed to write to stdin");
47 });
48
49 let command_output = format_command
50 .wait_with_output()
51 .expect("Failed to read stdout");
52
53 let formatted_code = String::from_utf8_lossy(&command_output.stdout);
54
55 formatted_code.to_string()
56}
57
58pub async fn codegen(command: &CodeGen) -> Result<(), OperationOutcomeError> {
59 match command {
60 CodeGen::Operations { input, output } => {
61 let generated_operation_definitions =
62 type_gen::operation_definitions::generate_operation_definitions_from_files(input)
63 .map_err(|e| OperationOutcomeError::error(IssueType::Exception(None), e))?;
64
65 let formatted_code = format_code(generated_operation_definitions);
66
67 match output {
68 Some(output_path) => {
69 std::fs::write(output_path, formatted_code.to_string()).map_err(|e| {
70 OperationOutcomeError::error(IssueType::Exception(None), e.to_string())
71 })?;
72 println!("Generated FHIR types written to: {}", output_path);
73 }
74 None => {
75 println!("{}", formatted_code);
76 }
77 }
78
79 Ok(())
80 }
81 CodeGen::Types {
82 input,
83 output,
84 level,
85 } => {
86 let level = {
87 match level {
88 Some(GenerateLevel::Primitive) => Some("primitive-type"),
89 Some(GenerateLevel::Complex) => Some("complex-type"),
90 Some(GenerateLevel::Resource) => Some("resource"),
91 None => None,
92 }
93 };
94
95 let rust_code = type_gen::rust_types::generate(input, level).await?;
96 let output_path = Path::new(output);
97 let resource_path = output_path.join("resources.rs");
98 std::fs::write(resource_path, format_code(rust_code.resources.to_string())).map_err(
99 |e| OperationOutcomeError::error(IssueType::Exception(None), e.to_string()),
100 )?;
101
102 let type_path = output_path.join("types.rs");
103 std::fs::write(type_path, format_code(rust_code.types.to_string())).map_err(|e| {
104 OperationOutcomeError::error(IssueType::Exception(None), e.to_string())
105 })?;
106
107 let terminology_path = output_path.join("terminology.rs");
108 std::fs::write(
109 terminology_path,
110 format_code(rust_code.terminology.to_string()),
111 )
112 .map_err(|e| OperationOutcomeError::error(IssueType::Exception(None), e.to_string()))?;
113
114 let mod_path = output_path.join("mod.rs");
115 let module_code = quote! {
116 pub mod resources;
118 pub mod types;
119 pub mod terminology;
120 };
121 std::fs::write(mod_path, module_code.to_string()).map_err(|e| {
122 OperationOutcomeError::error(IssueType::Exception(None), e.to_string())
123 })?;
124
125 let mod_path = output_path.join("mod.rs");
126 let module_code = quote! {
127 pub mod resources;
129 pub mod types;
130 pub mod terminology;
131 };
132 std::fs::write(mod_path, format_code(module_code.to_string())).map_err(|e| {
133 OperationOutcomeError::error(IssueType::Exception(None), e.to_string())
134 })?;
135
136 println!("Generated FHIR types written to: {}", output_path.display());
137 Ok(())
138 }
139 }
140}