1use haste_fhir_model::r4::generated::resources::ResourceType;
2use proc_macro2::{Ident, TokenStream};
3use quote::{ToTokens, format_ident, quote};
4use syn::{
5 Attribute, Data, DataStruct, DeriveInput, Expr, Field, Fields, Lit, Meta, PathArguments,
6 PathSegment, Type, 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 strip_type_wrappers(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 strip_type_wrappers(k, ignore_cases)
56 } else {
57 panic!("invalid");
58 }
59 }
60 _ => panic!("invalid"),
61 }
62 } else {
63 segment.clone()
64 }
65}
66
67fn field_inner_type(field: &Field) -> PathSegment {
69 match &field.ty {
70 Type::Path(path) => {
71 let type_ = path.path.segments.first().unwrap();
72 strip_type_wrappers(type_, vec!["Option".to_string(), "Vec".to_string()])
73 }
74 _ => panic!("Unsupported field type for serialization"),
75 }
76}
77
78fn get_optional_type(field: &Field) -> PathSegment {
80 match &field.ty {
81 Type::Path(path) => {
82 let type_ = path.path.segments.first().unwrap();
83 strip_type_wrappers(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 = field_inner_type(field).ident;
101
102 ResourceType::try_from(field_type.to_string()).is_ok()
103}
104
105fn build_return_value(fields: &Fields) -> proc_macro2::TokenStream {
106 let field_setters = fields.iter().map(|field| {
107 let optional = is_optional(field);
108 let field = field.ident.as_ref().unwrap();
109 let field_name = field.to_string();
110
111 if optional {
112 quote!{
113 #field: #field
114 }
115 } else {
116 quote!{
117 #field: #field.ok_or_else(||
118 OperationOutcomeError::error(
119 haste_fhir_model::r4::generated::terminology::IssueType::invalid(), format!("Field '{}' is required.", stringify!(#field_name))))?
120 }
121 }
122
123 });
124
125 quote! {
126 Ok(Self {
127 #(#field_setters),*
128 })
129 }
130}
131
132#[proc_macro_derive(ToParameters, attributes(parameter_rename, parameter_nested))]
133pub fn haste_to_parameters(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
134 let input = parse_macro_input!(input as DeriveInput);
135
136 match input.data {
137 Data::Struct(data) => derive_to_parameters(&input.ident, &data).into(),
138 _ => syn::Error::new_spanned(input.ident, "ToParameters can only be derived for structs")
139 .to_compile_error()
140 .into(),
141 }
142}
143
144fn derive_to_parameters(struct_name: &Ident, data: &DataStruct) -> TokenStream {
145 let parameters_name = format_ident!("parameters");
146 let var_name = format_ident!("s");
147
148 let to_parameters = match data
149 .fields
150 .iter()
151 .map(generate_parameter_code)
152 .collect::<syn::Result<Vec<_>>>()
153 {
154 Ok(tokens) => tokens,
155 Err(err) => return err.to_compile_error(),
156 };
157
158 quote! {
159 impl From<#struct_name> for Vec<ParametersParameter> {
160 fn from(#var_name: #struct_name) -> Self {
161 let mut #parameters_name = vec![];
162 #(#to_parameters)*
163 #parameters_name
164 }
165 }
166 }
167}
168
169fn generate_parameter_code(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
170 let field_name = field.ident.as_ref().ok_or_else(|| {
171 syn::Error::new_spanned(field, "ToParameters only supports named struct fields")
172 })?;
173
174 let tmp_name = format_ident!("tmp");
175 let var_name = format_ident!("s");
176
177 let mut body = generate_parameter_push(field, &tmp_name);
178
179 if determine_is_vector(field) {
180 body = quote! {
181 for #tmp_name in #tmp_name.into_iter() {
182 #body
183 }
184 };
185 }
186
187 let tokens = if is_optional(field) {
188 quote! {
189 if let Some(#tmp_name) = #var_name.#field_name {
190 #body
191 }
192 }
193 } else {
194 quote! {
195 let #tmp_name = #var_name.#field_name;
196 #body
197 }
198 };
199
200 Ok(tokens)
201}
202
203fn generate_parameter_push(field: &Field, value: &Ident) -> proc_macro2::TokenStream {
204 let value_type = field_inner_type(field);
205 let parameter_name = get_parameter_name(field);
206
207 if is_nested_parameter(&field.attrs) {
208 quote! {
209 parameters.push(ParametersParameter {
210 name: Box::new(FHIRString {
211 value: Some(#parameter_name.to_string()),
212 ..Default::default()
213 }),
214 part: Some(#value.into()),
215 ..Default::default()
216 });
217 }
218 } else if value_type.ident == format_ident!("Resource") {
219 quote! {
220 parameters.push(ParametersParameter {
221 name: Box::new(FHIRString {
222 value: Some(#parameter_name.to_string()),
223 ..Default::default()
224 }),
225 resource: Some(Box::new(#value)),
226 ..Default::default()
227 });
228 }
229 } else if value_type.ident == format_ident!("ParametersParameterValueTypeChoice") {
230 quote! {
231 parameters.push(ParametersParameter {
232 name: Box::new(FHIRString {
233 value: Some(#parameter_name.to_string()),
234 ..Default::default()
235 }),
236 value: Some(#value),
237 ..Default::default()
238 });
239 }
240 } else if is_resource_type(field) {
241 quote! {
242 parameters.push(ParametersParameter {
243 name: Box::new(FHIRString {
244 value: Some(#parameter_name.to_string()),
245 ..Default::default()
246 }),
247 resource: Some(Box::new(Resource::#value_type(#value))),
248 ..Default::default()
249 });
250 }
251 } else {
252 let primitive = value_type.ident.to_string().replacen("FHIR", "", 1);
253 let parameter_value_type = format_ident!("{}", primitive);
254
255 quote! {
256 parameters.push(ParametersParameter {
257 name: Box::new(FHIRString {
258 value: Some(#parameter_name.to_string()),
259 ..Default::default()
260 }),
261 value: Some(
262 haste_fhir_model::r4::generated::resources::ParametersParameterValueTypeChoice::#parameter_value_type(
263 Box::new(#value)
264 )
265 ),
266 ..Default::default()
267 });
268 }
269 }
270}
271
272#[proc_macro_derive(FromParameters, attributes(parameter_rename, parameter_nested))]
273pub fn haste_from_parameters(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
274 let input = parse_macro_input!(input as DeriveInput);
275
276 match input.data {
277 Data::Struct(data) => derive_from_parameters(&input.ident, &data).into(),
278 _ => syn::Error::new_spanned(
279 input.ident,
280 "FromParameters can only be derived for structs",
281 )
282 .to_compile_error()
283 .into(),
284 }
285}
286
287fn derive_from_parameters(struct_name: &Ident, data: &DataStruct) -> proc_macro2::TokenStream {
288 let parameters_name = format_ident!("parameters");
289 let current_parameter = format_ident!("param");
290
291 let declare_fields = match data
292 .fields
293 .iter()
294 .map(generate_field_declaration)
295 .collect::<syn::Result<Vec<_>>>()
296 {
297 Ok(fields) => fields,
298 Err(err) => return err.to_compile_error(),
299 };
300
301 let set_fields = match data
302 .fields
303 .iter()
304 .map(generate_parameter_match_arm)
305 .collect::<syn::Result<Vec<_>>>()
306 {
307 Ok(fields) => fields,
308 Err(err) => return err.to_compile_error(),
309 };
310
311 let return_value = build_return_value(&data.fields);
312
313 quote! {
314 impl TryFrom<Vec<ParametersParameter>> for #struct_name {
315 type Error = OperationOutcomeError;
316
317 fn try_from(#parameters_name: Vec<ParametersParameter>) -> Result<Self, Self::Error> {
318 #(#declare_fields)*
319
320 for #current_parameter in #parameters_name {
321 match #current_parameter.name.value.as_ref().map(|v| v.as_str()) {
322 #(#set_fields),*
323
324 Some(k) => {
325 return Err(OperationOutcomeError::error(
326 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
327 format!("Parameter '{}' is not allowed.", k),
328 ));
329 }
330
331 None => {
332 return Err(OperationOutcomeError::error(
333 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
334 "Parameter must have a name on it".to_string(),
335 ));
336 }
337 }
338 }
339
340 #return_value
341 }
342 }
343 }
344}
345
346fn generate_field_declaration(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
347 let field_name = field.ident.as_ref().ok_or_else(|| {
348 syn::Error::new_spanned(field, "FromParameters only supports named struct fields")
349 })?;
350
351 let field_type = get_optional_type(field).to_token_stream();
352
353 Ok(quote! {
354 let mut #field_name: Option<#field_type> = None;
355 })
356}
357
358fn generate_parameter_match_arm(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
359 let field_name = field.ident.as_ref().ok_or_else(|| {
360 syn::Error::new_spanned(field, "FromParameters only supports named struct fields")
361 })?;
362 let is_vector = determine_is_vector(field);
363 let expected_parameter_name = get_parameter_name(field);
364
365 let value_from_param = generate_value_extraction(field);
366
367 let setter = if is_vector {
368 quote! {
369 let tmp_value: Result<_, OperationOutcomeError> = #value_from_param;
370
371 if let Some(tmp_value) = tmp_value? {
372 if let Some(tmp_array) = #field_name.as_mut() {
373 tmp_array.push(tmp_value);
374 } else {
375 #field_name = Some(vec![tmp_value]);
376 }
377 }
378 }
379 } else {
380 quote! {
381 if #field_name.is_some() {
382 return Err(OperationOutcomeError::error(
383 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
384 format!("Parameter '{}' is not allowed to be repeated.", #expected_parameter_name)
385 ));
386 }
387
388 let tmp_value: Result<_, OperationOutcomeError> = #value_from_param;
389 #field_name = tmp_value?;
390 }
391 };
392
393 Ok(quote! {
394 Some(#expected_parameter_name) => {
395 #setter
396 }
397 })
398}
399
400fn generate_value_extraction(field: &Field) -> proc_macro2::TokenStream {
401 let value_type = field_inner_type(field);
402 let expected_parameter_name = get_parameter_name(field);
403 let current_parameter = format_ident!("param");
404
405 if is_nested_parameter(&field.attrs) {
406 quote! {
407 #value_type::try_from(
408 #current_parameter.part.unwrap_or_default()
409 )
410 .map(Some)
411 }
412 } else if value_type.ident == format_ident!("Resource") {
413 quote! {
414 Ok(#current_parameter.resource.map(|r| *r))
415 }
416 } else if value_type.ident == format_ident!("ParametersParameterValueTypeChoice") {
417 quote! {
418 Ok(#current_parameter.value)
419 }
420 } else if is_resource_type(field) {
421 quote! {
422 if let Some(Resource::#value_type(resource)) =
423 #current_parameter.resource.map(|r| *r)
424 {
425 Ok(Some(resource))
426 } else {
427 Err(OperationOutcomeError::error(
428 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
429 format!(
430 "Parameter '{}' does not contain correct value type.",
431 #expected_parameter_name
432 )
433 ))
434 }
435 }
436 } else {
437 let primitive = value_type.ident.to_string().replacen("FHIR", "", 1);
438 let parameter_value_type = format_ident!("{}", primitive);
439
440 quote! {
441 if let Some(
442 haste_fhir_model::r4::generated::resources::ParametersParameterValueTypeChoice::#parameter_value_type(value)
443 ) = #current_parameter.value {
444 Ok(Some(*value))
445 } else {
446 Err(OperationOutcomeError::error(
447 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
448 format!(
449 "Parameter '{}' does not contain correct value type.",
450 #expected_parameter_name
451 )
452 ))
453 }
454 }
455 }
456}