Skip to main content

haste_health/commands/
codegen.rs

1use 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        /// 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    TestScripts {
34        #[arg(short, long)]
35        input: Vec<String>,
36        /// Output Rust file path
37        #[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                /// DO NOT EDIT THIS FILE. It is auto-generated by the FHIR Rust code generator.
124               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                /// DO NOT EDIT THIS FILE. It is auto-generated by the FHIR Rust code generator.
135               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}