haste_health/commands/
codegen.rs

1use 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        /// Output Rust file path
21        #[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        /// Output Rust file path
30        #[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                /// DO NOT EDIT THIS FILE. It is auto-generated by the FHIR Rust code generator.
117               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                /// DO NOT EDIT THIS FILE. It is auto-generated by the FHIR Rust code generator.
128               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}