haste_codegen/type_gen/
operation_definitions.rs

1use std::{borrow::Cow, path::Path};
2
3use crate::utilities::{FHIR_PRIMITIVES, RUST_KEYWORDS, generate::capitalize, load};
4use haste_fhir_model::r4::generated::{
5    resources::{OperationDefinition, OperationDefinitionParameter, Resource, ResourceType},
6    terminology::{AllTypes, OperationParameterUse},
7};
8use proc_macro2::TokenStream;
9use quote::{format_ident, quote};
10use walkdir::WalkDir;
11
12fn get_operation_definitions<'a>(
13    resource: &'a Resource,
14) -> Result<Vec<&'a OperationDefinition>, String> {
15    match resource {
16        Resource::Bundle(bundle) => {
17            if let Some(entries) = bundle.entry.as_ref() {
18                let op_defs = entries
19                    .iter()
20                    .filter_map(|e| e.resource.as_ref())
21                    .filter_map(|sd| match sd.as_ref() {
22                        Resource::OperationDefinition(op_def) => Some(op_def),
23                        _ => None,
24                    });
25                Ok(op_defs.collect())
26            } else {
27                Ok(vec![])
28            }
29        }
30        Resource::OperationDefinition(op_def) => {
31            let op_def = op_def;
32
33            Ok(vec![op_def])
34        }
35        _ => Err("Resource is not a Bundle or OperationDefinition".to_string()),
36    }
37}
38
39fn get_name(op_def: &OperationDefinition) -> String {
40    let id = op_def
41        .id
42        .clone()
43        .expect("Operation definition must have an id.");
44    let interface_name = id
45        .split("-")
46        .into_iter()
47        .map(|s| capitalize(s))
48        .collect::<Vec<String>>()
49        .join("");
50    interface_name
51}
52
53fn create_field_value(type_: &str, is_array: bool, required: bool) -> TokenStream {
54    let base_type = if let Some(primitive) = FHIR_PRIMITIVES.get(type_) {
55        primitive.as_str()
56    }
57    // For element move to ParametersParameterValueTypeChoice
58    // This sets it as parameter.parameter.value where it would be pulled from.
59    else if type_ == "Element" {
60        "ParametersParameterValueTypeChoice"
61    } else {
62        type_
63    };
64
65    let type_ = format_ident!("{}", base_type);
66
67    let type_ = if is_array {
68        quote! {Vec<#type_>}
69    } else {
70        quote! {#type_}
71    };
72
73    let type_ = if required {
74        quote! { #type_ }
75    } else {
76        quote! {Option<#type_>}
77    };
78
79    type_
80}
81
82/// If param is return and type is a resource, you can return resource directly from field.
83fn is_resource_return(parameters: &Vec<&OperationDefinitionParameter>) -> bool {
84    // Need special handling for single "return" parameter of type Any or a Resource type
85    if parameters.len() == 1
86        && parameters[0].name.value.as_deref() == Some("return")
87        && let Some(parameter_type) = parameters[0]
88            .type_
89            .as_ref()
90        
91        && (std::mem::discriminant(&**parameter_type) == std::mem::discriminant(&AllTypes::Any(None)) 
92            || ResourceType::try_from(Into::<Option<String>>::into(&**parameter_type).unwrap_or_default()).is_ok())
93    {
94        true
95    } else {
96        false
97    }
98}
99
100fn generate_parameter_type(
101    name: &str,
102    parameters: &Vec<&OperationDefinitionParameter>,
103    direction: &Direction,
104    is_base: bool,
105) -> Vec<TokenStream> {
106    let mut types = vec![];
107    let mut fields = vec![];
108
109    for p in parameters.iter() {
110        let is_array = p.max.value != Some("1".to_string());
111        let required = p.min.value.unwrap_or(0) > 0;
112        let initial_field_name = p.name.value.as_ref().expect("Parameter must have a name");
113        let formatted_field_name = initial_field_name.replace("-", "_");
114
115        let field_ident = if RUST_KEYWORDS.contains(&formatted_field_name.as_str()) {
116            format_ident!("{}_", formatted_field_name)
117        } else {
118            format_ident!("{}", formatted_field_name)
119        };
120
121        let attribute_rename = if RUST_KEYWORDS.contains(&formatted_field_name.as_str())
122            || formatted_field_name != *initial_field_name
123        {
124            quote! {  #[parameter_rename=#initial_field_name] }
125        } else {
126            quote! {}
127        };
128
129        if let Some(type_) = p.type_.as_ref() {
130            let type_ = if std::mem::discriminant(&**type_) == std::mem::discriminant(&AllTypes::Any(None)) { AllTypes::Resource(None) } else { *type_.clone() };
131            let field = create_field_value(Into::<Option<String>>::into(&type_).unwrap_or_default().as_str(), is_array, required);
132
133            fields.push(quote! {
134                #attribute_rename
135                pub #field_ident: #field
136            })
137        } else {
138            let name = name.to_string()
139                + formatted_field_name
140                    .split("_")
141                    .map(|s| capitalize(s))
142                    .collect::<String>()
143                    .as_str();
144            let nested_types = generate_parameter_type(
145                &name,
146                &p.part
147                    .as_ref()
148                    .map(|v| v.iter().collect())
149                    .unwrap_or(vec![]),
150                direction,
151                false,
152            );
153            types.extend(nested_types);
154
155            let type_ = create_field_value(&name, is_array, required);
156            fields.push(quote! {
157                #attribute_rename
158                #[parameter_nested]
159                pub #field_ident: #type_
160            })
161        }
162    }
163
164    let struct_name = format_ident!("{}", name);
165
166    let base_parameter_type = if is_base && is_resource_return(parameters) {
167        let required = parameters.get(0).and_then(|p| p.min.value).unwrap_or(0) > 0;
168        let type_ = parameters
169            .get(0)
170            .and_then(|p| {
171                p.type_
172                    .as_ref()
173                    .and_then(|v| Into::<Option<String>>::into(&**v))
174            })
175            .unwrap_or_default();
176
177        let return_type = if type_ == "Any" { "Resource" } else { type_.as_str() };
178        let return_type_ident = format_ident!("{}", return_type);
179
180        let return_v = if required {
181            quote! {
182                value.return_
183            }
184        } else {
185            quote! {
186               value.return_.unwrap_or_default()
187            }
188        };
189
190        let returned_value = if return_type == "Resource" {
191            quote! {#return_v}
192        } else {
193            quote! { Resource::#return_type_ident(#return_v) }
194        };
195
196        quote! {
197            #[derive(Debug, FromParameters)]
198            pub struct #struct_name {
199                #(#fields),*
200            }
201
202            impl From<#struct_name> for Resource {
203                fn from(value: #struct_name) -> Self {
204                    // Special handling for single "return" parameter of type Any or a Resource type
205                    #returned_value
206                }
207            }
208        }
209    } else {
210        quote! {
211            #[derive(Debug, FromParameters, ToParameters)]
212            pub struct #struct_name {
213                #(#fields),*
214            }
215
216            impl From<#struct_name> for Resource {
217                fn from(value: #struct_name) -> Self {
218                    let parameters: Vec<ParametersParameter> = value.into();
219                    Resource::Parameters(Parameters {
220                        parameter: Some(parameters),
221                        ..Default::default()
222                    })
223                }
224            }
225        }
226    };
227
228    types.push(base_parameter_type);
229
230    types
231}
232
233fn generate_output(parameters: &Cow<Vec<OperationDefinitionParameter>>) -> Vec<TokenStream> {
234    let input_parameters = parameters
235        .iter()
236        .filter(|p| match p.use_.as_ref() {
237            OperationParameterUse::Out(_) => true,
238            _ => false,
239        })
240        .collect::<Vec<_>>();
241
242    generate_parameter_type("Output", &input_parameters, &Direction::Output, true)
243}
244
245fn generate_input(parameters: &Cow<Vec<OperationDefinitionParameter>>) -> Vec<TokenStream> {
246    let input_parameters = parameters
247        .iter()
248        .filter(|p| match p.use_.as_ref() {
249            OperationParameterUse::In(_) => true,
250            _ => false,
251        })
252        .collect::<Vec<_>>();
253
254    generate_parameter_type("Input", &input_parameters, &Direction::Input, true)
255}
256
257enum Direction {
258    Input,
259    Output,
260}
261
262fn generate_operation_definition(file_path: &Path) -> Result<TokenStream, String> {
263    let resource = load::load_from_file(file_path)?;
264    let op_defs = get_operation_definitions(&resource)?;
265    // Generate code for each operation definition
266    let mut generated = quote! {};
267    for op_def in op_defs {
268        let name = format_ident!("{}", get_name(op_def));
269        let op_code = op_def
270            .code
271            .value
272            .as_ref()
273            .expect("Operation must have a code.");
274        let parameters = op_def
275            .parameter
276            .as_ref()
277            .map(Cow::Borrowed)
278            .unwrap_or(Cow::Owned(vec![]));
279
280        let generate_input = generate_input(&parameters);
281        let generate_output = generate_output(&parameters);
282
283        generated.extend(quote! {
284            pub mod #name {
285                use super::*;
286                pub const CODE: &str = #op_code;
287                #(#generate_input)*
288                #(#generate_output)*
289            }
290            // Code generation for each operation definition
291        });
292    }
293
294    Ok(generated)
295}
296
297pub fn generate_operation_definitions_from_files(
298    file_paths: &Vec<String>,
299) -> Result<String, String> {
300    let mut generated_code = quote! {
301        #![allow(non_snake_case)]
302        use haste_fhir_ops::derive::{FromParameters, ToParameters};
303        use haste_fhir_model::r4::generated::types::*;
304        use haste_fhir_model::r4::generated::resources::*;
305        use haste_fhir_operation_error::*;
306    };
307
308    for dir_path in file_paths {
309        let walker = WalkDir::new(dir_path).into_iter();
310        for entry in walker
311            .filter_map(|e| e.ok())
312            .filter(|e| e.metadata().unwrap().is_file())
313        {
314            let generated_types = generate_operation_definition(entry.path())?;
315
316            generated_code = quote! {
317                #generated_code
318                #generated_types
319            }
320        }
321    }
322
323    Ok(generated_code.to_string())
324}