haste_codegen/type_gen/
operation_definitions.rs1use 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 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
82fn is_resource_return(parameters: &Vec<&OperationDefinitionParameter>) -> bool {
84 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 #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 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(¶meters);
281 let generate_output = generate_output(¶meters);
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 });
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}