Skip to main content

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].type_.as_ref()
88        && (std::mem::discriminant(&**parameter_type)
89            == std::mem::discriminant(&AllTypes::Any(None))
90            || ResourceType::try_from(
91                Into::<Option<String>>::into(&**parameter_type).unwrap_or_default(),
92            )
93            .is_ok())
94    {
95        true
96    } else {
97        false
98    }
99}
100
101fn generate_parameter_type(
102    name: &str,
103    parameters: &Vec<&OperationDefinitionParameter>,
104    direction: &Direction,
105    is_base: bool,
106) -> Vec<TokenStream> {
107    let mut types = vec![];
108    let mut fields = vec![];
109
110    for p in parameters.iter() {
111        let is_array = p.max.value != Some("1".to_string());
112        let required = p.min.value.unwrap_or(0) > 0;
113        let initial_field_name = p.name.value.as_ref().expect("Parameter must have a name");
114        let formatted_field_name = initial_field_name.replace("-", "_");
115
116        let field_ident = if RUST_KEYWORDS.contains(&formatted_field_name.as_str()) {
117            format_ident!("{}_", formatted_field_name)
118        } else {
119            format_ident!("{}", formatted_field_name)
120        };
121
122        let attribute_rename = if RUST_KEYWORDS.contains(&formatted_field_name.as_str())
123            || formatted_field_name != *initial_field_name
124        {
125            quote! {  #[parameter_rename=#initial_field_name] }
126        } else {
127            quote! {}
128        };
129
130        if let Some(type_) = p.type_.as_ref() {
131            let type_ = if std::mem::discriminant(&**type_)
132                == std::mem::discriminant(&AllTypes::Any(None))
133            {
134                AllTypes::Resource(None)
135            } else {
136                *type_.clone()
137            };
138            let field = create_field_value(
139                Into::<Option<String>>::into(&type_)
140                    .unwrap_or_default()
141                    .as_str(),
142                is_array,
143                required,
144            );
145
146            fields.push(quote! {
147                #attribute_rename
148                pub #field_ident: #field
149            })
150        } else {
151            let name = name.to_string()
152                + formatted_field_name
153                    .split("_")
154                    .map(|s| capitalize(s))
155                    .collect::<String>()
156                    .as_str();
157            let nested_types = generate_parameter_type(
158                &name,
159                &p.part
160                    .as_ref()
161                    .map(|v| v.iter().collect())
162                    .unwrap_or(vec![]),
163                direction,
164                false,
165            );
166            types.extend(nested_types);
167
168            let type_ = create_field_value(&name, is_array, required);
169            fields.push(quote! {
170                #attribute_rename
171                #[parameter_nested]
172                pub #field_ident: #type_
173            })
174        }
175    }
176
177    let struct_name = format_ident!("{}", name);
178
179    let base_parameter_type = if is_base && is_resource_return(parameters) {
180        let required = parameters.get(0).and_then(|p| p.min.value).unwrap_or(0) > 0;
181        let type_ = parameters
182            .get(0)
183            .and_then(|p| {
184                p.type_
185                    .as_ref()
186                    .and_then(|v| Into::<Option<String>>::into(&**v))
187            })
188            .unwrap_or_default();
189
190        let return_type = if type_ == "Any" {
191            "Resource"
192        } else {
193            type_.as_str()
194        };
195        let return_type_ident = format_ident!("{}", return_type);
196
197        let return_v = if required {
198            quote! {
199                value.return_
200            }
201        } else {
202            quote! {
203               value.return_.unwrap_or_default()
204            }
205        };
206
207        let returned_value = if return_type == "Resource" {
208            quote! {#return_v}
209        } else {
210            quote! { Resource::#return_type_ident(#return_v) }
211        };
212
213        quote! {
214            #[derive(Debug, FromParameters)]
215            pub struct #struct_name {
216                #(#fields),*
217            }
218
219            impl From<#struct_name> for Resource {
220                fn from(value: #struct_name) -> Self {
221                    // Special handling for single "return" parameter of type Any or a Resource type
222                    #returned_value
223                }
224            }
225        }
226    } else {
227        quote! {
228            #[derive(Debug, FromParameters, ToParameters)]
229            pub struct #struct_name {
230                #(#fields),*
231            }
232
233            impl From<#struct_name> for Resource {
234                fn from(value: #struct_name) -> Self {
235                    let parameters: Vec<ParametersParameter> = value.into();
236                    Resource::Parameters(Parameters {
237                        parameter: Some(parameters),
238                        ..Default::default()
239                    })
240                }
241            }
242        }
243    };
244
245    types.push(base_parameter_type);
246
247    types
248}
249
250fn generate_output(parameters: &Cow<Vec<OperationDefinitionParameter>>) -> Vec<TokenStream> {
251    let input_parameters = parameters
252        .iter()
253        .filter(|p| match p.use_.as_ref() {
254            OperationParameterUse::Out(_) => true,
255            _ => false,
256        })
257        .collect::<Vec<_>>();
258
259    generate_parameter_type("Output", &input_parameters, &Direction::Output, true)
260}
261
262fn generate_input(parameters: &Cow<Vec<OperationDefinitionParameter>>) -> Vec<TokenStream> {
263    let input_parameters = parameters
264        .iter()
265        .filter(|p| match p.use_.as_ref() {
266            OperationParameterUse::In(_) => true,
267            _ => false,
268        })
269        .collect::<Vec<_>>();
270
271    generate_parameter_type("Input", &input_parameters, &Direction::Input, true)
272}
273
274enum Direction {
275    Input,
276    Output,
277}
278
279fn generate_operation_definition(file_path: &Path) -> Result<TokenStream, String> {
280    let resource = load::load_from_file(file_path)?;
281    let op_defs = get_operation_definitions(&resource)?;
282    // Generate code for each operation definition
283    let mut generated = quote! {};
284    for op_def in op_defs {
285        let name = format_ident!("{}", get_name(op_def));
286        let op_code = op_def
287            .code
288            .value
289            .as_ref()
290            .expect("Operation must have a code.");
291        let parameters = op_def
292            .parameter
293            .as_ref()
294            .map(Cow::Borrowed)
295            .unwrap_or(Cow::Owned(vec![]));
296
297        let generate_input = generate_input(&parameters);
298        let generate_output = generate_output(&parameters);
299
300        generated.extend(quote! {
301            pub mod #name {
302                use super::*;
303                pub const CODE: &str = #op_code;
304                #(#generate_input)*
305                #(#generate_output)*
306            }
307            // Code generation for each operation definition
308        });
309    }
310
311    Ok(generated)
312}
313
314pub fn generate_operation_definitions_from_files(
315    file_paths: &Vec<String>,
316) -> Result<String, String> {
317    let mut generated_code = quote! {
318        #![allow(non_snake_case)]
319        use haste_fhir_ops::derive::{FromParameters, ToParameters};
320        use haste_fhir_model::r4::generated::types::*;
321        use haste_fhir_model::r4::generated::resources::*;
322        use haste_fhir_operation_error::*;
323    };
324
325    for dir_path in file_paths {
326        let walker = WalkDir::new(dir_path).into_iter();
327        for entry in walker
328            .filter_map(|e| e.ok())
329            .filter(|e| e.metadata().unwrap().is_file())
330        {
331            let generated_types = generate_operation_definition(entry.path())?;
332
333            generated_code = quote! {
334                #generated_code
335                #generated_types
336            }
337        }
338    }
339
340    Ok(generated_code.to_string())
341}