haste_fhir_ops_derive/
lib.rs

1use haste_fhir_model::r4::generated::resources::ResourceType;
2use proc_macro::TokenStream;
3use quote::{ToTokens, format_ident, quote};
4use syn::{
5    parse_macro_input, Attribute, Data, DeriveInput, Expr, Field, Fields, Lit, Meta, PathArguments, PathSegment, Type
6};
7
8fn get_attribute_value(attrs: &[Attribute], attribute: &str) -> Option<String> {
9    attrs.iter().find_map(|attr| match &attr.meta {
10        Meta::NameValue(name_value) => {
11            if name_value.path.is_ident(attribute) {
12                match &name_value.value {
13                    Expr::Lit(lit) => match &lit.lit {
14                        Lit::Str(lit) => Some(lit.value()),
15                        _ => panic!("Expected a string literal"),
16                    },
17                    _ => panic!("Expected a string literal"),
18                }
19            } else {
20                None
21            }
22        }
23        _ => None,
24    })
25}
26
27fn get_parameter_name(field: &Field) -> String {
28    if let Some(rename) = get_attribute_value(&field.attrs, "parameter_rename") { rename } else { field.ident.as_ref().unwrap().to_string() }
29}
30
31fn is_nested_parameter(attrs: &[Attribute]) -> bool {
32    attrs
33        .iter()
34        .any(|attr| attr.path().is_ident("parameter_nested"))
35}
36
37fn determine_is_vector(field: &Field) -> bool {
38    let inner_type = get_optional_type(field);
39
40    inner_type.ident == format_ident!("Vec")
41}
42
43fn _inner_type(segment: &PathSegment, ignore_cases: Vec<String>) -> PathSegment {
44    if ignore_cases.contains(&segment.ident.to_string()) {
45        match &segment.arguments {
46            PathArguments::AngleBracketed(args) => {
47                if let Some(syn::GenericArgument::Type(Type::Path(inner_path))) = args.args.first()
48                {
49                    let k = inner_path.path.segments.first().unwrap();
50                    _inner_type(k, ignore_cases)
51                } else {
52                    panic!("invalid");
53                }
54            }
55            _ => panic!("invalid"),
56        }
57    } else {
58        segment.clone()
59    }
60}
61
62/// Returns the inner type if it's between Options and Vecs etc..
63fn inner_type(field: &Field) -> PathSegment {
64    match &field.ty {
65        Type::Path(path) => {
66            let type_ = path.path.segments.first().unwrap();
67            _inner_type(type_, vec!["Option".to_string(), "Vec".to_string()])
68        }
69        _ => panic!("Unsupported field type for serialization"),
70    }
71}
72
73/// Returns the inner type if it's between Option
74fn get_optional_type(field: &Field) -> PathSegment {
75    match &field.ty {
76        Type::Path(path) => {
77            let type_ = path.path.segments.first().unwrap();
78            _inner_type(type_, vec!["Option".to_string()])
79        }
80        _ => panic!("Unsupported field type for serialization"),
81    }
82}
83
84fn is_optional(field: &Field) -> bool {
85    match &field.ty {
86        Type::Path(path) => {
87            let type_ = path.path.segments.first().unwrap();
88            type_.ident == format_ident!("Option")
89        }
90        _ => panic!("Unsupported field type for serialization"),
91    }
92}
93
94
95fn is_resource_type(field: &Field) -> bool {
96    let field_type = inner_type(field).ident;
97
98    let res = ResourceType::try_from(field_type.to_string());
99    if let Ok(_) = res {
100        return true;
101    } else {
102        return false;
103    }
104}
105
106fn build_return_value(fields: &Fields) -> proc_macro2::TokenStream {
107    
108    let field_setters = fields.iter().map(|field| {
109        let optional = is_optional(field);
110        let field = field.ident.as_ref().unwrap();
111        let field_name = field.to_string();
112
113        if optional {
114            quote!{ 
115                #field: #field
116            }
117        } else {
118            quote!{
119                #field: #field.ok_or_else(|| 
120                    OperationOutcomeError::error(
121                        haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Field '{}' is required.", stringify!(#field_name))))?
122             }
123        }
124        
125    });
126
127    quote! {
128        Ok(Self {
129            #(#field_setters),*
130        })
131    }
132}
133
134#[proc_macro_derive(ToParameters, attributes(parameter_rename, parameter_nested))]
135pub fn haste_to_parameters(input: TokenStream) -> TokenStream {
136    // Parse the input tokens into a syntax tree
137    let input = parse_macro_input!(input as DeriveInput);
138
139    match input.data {
140        Data::Struct(data) => {
141            let struct_name = input.ident;
142            let parameters_name = format_ident!("parameters");
143            let var_name = format_ident!("s");
144
145            let to_parameters = data.fields.iter().map(|field| {
146                let field_name = field.ident.as_ref().unwrap();
147                let value_type = inner_type(field);
148                let expected_parameter_name = get_parameter_name(field);
149                let is_optional = is_optional(field);
150                let tmp_name = format_ident!("tmp");
151
152                let mut as_param = if is_nested_parameter(&field.attrs) {
153                    quote!{ 
154                        #parameters_name.push(ParametersParameter { 
155                            name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
156                            part: Some(#tmp_name.into()),
157                            ..Default::default()
158                        });
159                    }
160                }else if value_type.ident == format_ident!("Resource") {
161                    quote!{
162                        #parameters_name.push(ParametersParameter {
163                            name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
164                            resource: Some(Box::new(#tmp_name)),
165                            ..Default::default()
166                        });
167                    }
168                }else  if value_type.ident == format_ident!("ParametersParameterValueTypeChoice") {
169                    quote! {
170                        #parameters_name.push(ParametersParameter {
171                            name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
172                            value: Some(#tmp_name),
173                            ..Default::default()
174                        });
175                    }
176                }
177                 else if is_resource_type(field) {
178                    quote!{
179                        #parameters_name.push(ParametersParameter {
180                                name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
181                                resource: Some(Box::new(Resource::#value_type(#tmp_name))),
182                                ..Default::default()
183                        });
184                    }
185
186                } else {
187                    // Need to remove the start FHIR on the primitives.
188                    let removed_fhir = value_type.ident.to_string().replacen("FHIR", "", 1);
189                    let parameter_value_type = format_ident!("{}", removed_fhir);
190
191                    quote! {
192                        #parameters_name.push(ParametersParameter {
193                            name: Box::new(FHIRString { value: Some(#expected_parameter_name.to_string()), ..Default::default() }),
194                            value: Some(haste_fhir_model::r4::generated::resources::ParametersParameterValueTypeChoice::#parameter_value_type(Box::new(#tmp_name))),
195                            ..Default::default()
196                        });
197                    }
198                };
199
200                if determine_is_vector(field) {
201                    as_param = quote! {
202                        for #tmp_name in #tmp_name.into_iter() {
203                            #as_param
204                        }
205                    };
206                }
207
208                if is_optional {
209                    quote! {
210                        if let Some(#tmp_name) = #var_name.#field_name {
211                           #as_param
212                        }
213                    }
214                }
215                else {
216                    quote! {
217                        let #tmp_name = #var_name.#field_name;
218                        #as_param
219                    }
220                }
221            });
222
223            let try_from_code = quote! {
224                impl From<#struct_name> for Vec<ParametersParameter> {
225                    fn from(#var_name: #struct_name) -> Self {
226                        let mut #parameters_name = vec![];
227                        #(#to_parameters)*
228                        #parameters_name
229                    }
230                }
231            };
232
233           // println!("{}", try_from_code.to_string());
234            try_from_code.into()
235
236        }
237        _ => panic!("From parameter deriviation is only supported for structs."),
238    }
239}
240
241
242#[proc_macro_derive(FromParameters, attributes(parameter_rename, parameter_nested))]
243pub fn haste_from_parameters(input: TokenStream) -> TokenStream {
244    // Parse the input tokens into a syntax tree
245    let input = parse_macro_input!(input as DeriveInput);
246
247    match input.data {
248        Data::Struct(data) => {
249            let struct_name = input.ident;
250            let parameters_name = format_ident!("parameters");
251            let current_parameter = format_ident!("param");
252
253            // Declare all the fields on the struct.
254            let declare_fields = data.fields.iter().map(|field| {
255                let field_name = field.ident.as_ref().unwrap();
256                let field_type_token = get_optional_type(field).to_token_stream();
257
258                quote! {
259                    let mut #field_name: Option<#field_type_token> = None;
260                }
261            });
262
263            let set_fields = data.fields.iter().map(|field| {
264                let field_name = field.ident.as_ref().unwrap();
265                let is_vector = determine_is_vector(field);
266                let value_type = inner_type(field);
267                let expected_parameter_name = get_parameter_name(field);
268
269                let get_value_from_param = if is_nested_parameter(&field.attrs) {
270                    quote!{ 
271                        #value_type::try_from(#current_parameter.part.unwrap_or_default()).map(|v| Some(v))
272                    }
273                }else if value_type.ident == format_ident!("Resource") {
274                    quote!{
275                        Ok(#current_parameter.resource.map(|r| *r))
276                    }
277                }else  if value_type.ident == format_ident!("ParametersParameterValueTypeChoice") {
278                    quote! {
279                        Ok(#current_parameter.value)
280                    }
281                }
282                 else if is_resource_type(field) {
283                    quote! {
284                        if let Some(Resource::#value_type(resource)) = #current_parameter.resource.map(|r| *r) {
285                            Ok(Some(resource))
286                        } else {
287                            return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter '{}' does not contain correct value type.", #expected_parameter_name)));
288                        }
289                    }
290                } else {
291                    // Need to remove the start FHIR on the primitives.
292                    let removed_fhir = value_type.ident.to_string().replacen("FHIR", "", 1);
293                    let parameter_value_type = format_ident!("{}", removed_fhir);
294
295                    quote! {
296                        if let Some(haste_fhir_model::r4::generated::resources::ParametersParameterValueTypeChoice::#parameter_value_type(value)) = #current_parameter.value {
297                            Ok(Some(*value))
298                        } else {
299                            return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter '{}' does not contain correct value type.", #expected_parameter_name)));
300                        }
301                    }
302                };
303
304                let setter = if is_vector {
305                    quote! {
306                        let tmp_value: Result<_, OperationOutcomeError> = #get_value_from_param;
307                        if let Some(tmp_value) = tmp_value? {
308                            if let Some(tmp_array) = #field_name.as_mut(){
309                                tmp_array.push(tmp_value);
310                            } else {
311                                #field_name = Some(vec![tmp_value]);
312                            }
313                        }
314                    }
315                } else {
316                    quote! {
317                        if #field_name.is_some(){
318                            return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter '{}' is not allowed to be repeated.", #expected_parameter_name)));
319                        }
320                        let tmp_value: Result<_, OperationOutcomeError> = #get_value_from_param;
321                        #field_name = tmp_value?;
322                    }
323                };
324
325                quote!{
326                    Some(#expected_parameter_name) =>  {
327                        #setter
328                    }
329                }
330            });
331
332            let return_value = build_return_value(&data.fields);
333
334            let try_from_code = quote! {
335                impl TryFrom<Vec<ParametersParameter>> for #struct_name {
336                    type Error = OperationOutcomeError;
337                    fn try_from(#parameters_name: Vec<ParametersParameter>) -> Result<Self, Self::Error> {
338                        #(#declare_fields)*
339
340                        for #current_parameter in #parameters_name {
341                            match #current_parameter.name.value.as_ref().map(|v| v.as_str()) {
342                                #(#set_fields),*
343                                Some(k) => {
344                                    return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter '{}' is not allowed.", k)));
345                                },
346                                None => {
347                                    return Err(OperationOutcomeError::error(haste_fhir_model::r4::generated::terminology::IssueType::Invalid(None), format!("Parameter must have a name on it")));
348                                }
349                            }
350                        }
351
352                        #return_value
353                    }
354                }
355            };
356            
357            try_from_code.into()
358        }
359        _ => panic!("From parameter deriviation is only supported for structs."),
360    }
361}