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].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 #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 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(¶meters);
298 let generate_output = generate_output(¶meters);
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 });
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}