Skip to main content

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