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