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 fhir_string() -> TokenStream {
204 quote! {
205 haste_fhir_model::r4::generated::types::FHIRString
206 }
207}
208
209fn generate_parameter_push(field: &Field, value: &Ident) -> proc_macro2::TokenStream {
210 let value_type = field_inner_type(field);
211 let parameter_name = get_parameter_name(field);
212
213 let fhir_string = fhir_string();
214
215 if is_nested_parameter(&field.attrs) {
216 quote! {
217 parameters.push(ParametersParameter {
218 name: Box::new(#fhir_string {
219 value: Some(#parameter_name.to_string()),
220 ..Default::default()
221 }),
222 part: Some(#value.into()),
223 ..Default::default()
224 });
225 }
226 } else if value_type.ident == format_ident!("Resource") {
227 quote! {
228 parameters.push(ParametersParameter {
229 name: Box::new(#fhir_string {
230 value: Some(#parameter_name.to_string()),
231 ..Default::default()
232 }),
233 resource: Some(Box::new(#value)),
234 ..Default::default()
235 });
236 }
237 } else if value_type.ident == format_ident!("ParametersParameterValueTypeChoice") {
238 quote! {
239 parameters.push(ParametersParameter {
240 name: Box::new(#fhir_string {
241 value: Some(#parameter_name.to_string()),
242 ..Default::default()
243 }),
244 value: Some(#value),
245 ..Default::default()
246 });
247 }
248 } else if is_resource_type(field) {
249 quote! {
250 parameters.push(ParametersParameter {
251 name: Box::new(#fhir_string {
252 value: Some(#parameter_name.to_string()),
253 ..Default::default()
254 }),
255 resource: Some(Box::new(Resource::#value_type(#value))),
256 ..Default::default()
257 });
258 }
259 } else {
260 let primitive = value_type.ident.to_string().replacen("FHIR", "", 1);
261 let parameter_value_type = format_ident!("{}", primitive);
262
263 quote! {
264 parameters.push(ParametersParameter {
265 name: Box::new(#fhir_string {
266 value: Some(#parameter_name.to_string()),
267 ..Default::default()
268 }),
269 value: Some(
270 haste_fhir_model::r4::generated::resources::ParametersParameterValueTypeChoice::#parameter_value_type(
271 Box::new(#value)
272 )
273 ),
274 ..Default::default()
275 });
276 }
277 }
278}
279
280#[proc_macro_derive(FromParameters, attributes(parameter_rename, parameter_nested))]
281pub fn haste_from_parameters(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
282 let input = parse_macro_input!(input as DeriveInput);
283
284 match input.data {
285 Data::Struct(data) => derive_from_parameters(&input.ident, &data).into(),
286 _ => syn::Error::new_spanned(
287 input.ident,
288 "FromParameters can only be derived for structs",
289 )
290 .to_compile_error()
291 .into(),
292 }
293}
294
295fn derive_from_parameters(struct_name: &Ident, data: &DataStruct) -> proc_macro2::TokenStream {
296 let parameters_name = format_ident!("parameters");
297 let current_parameter = format_ident!("param");
298
299 let declare_fields = match data
300 .fields
301 .iter()
302 .map(generate_field_declaration)
303 .collect::<syn::Result<Vec<_>>>()
304 {
305 Ok(fields) => fields,
306 Err(err) => return err.to_compile_error(),
307 };
308
309 let set_fields = match data
310 .fields
311 .iter()
312 .map(generate_parameter_match_arm)
313 .collect::<syn::Result<Vec<_>>>()
314 {
315 Ok(fields) => fields,
316 Err(err) => return err.to_compile_error(),
317 };
318
319 let return_value = build_return_value(&data.fields);
320
321 quote! {
322 impl TryFrom<Vec<ParametersParameter>> for #struct_name {
323 type Error = OperationOutcomeError;
324
325 fn try_from(#parameters_name: Vec<ParametersParameter>) -> Result<Self, Self::Error> {
326 #(#declare_fields)*
327
328 for #current_parameter in #parameters_name {
329 match #current_parameter.name.value.as_ref().map(|v| v.as_str()) {
330 #(#set_fields),*
331
332 Some(k) => {
333 return Err(OperationOutcomeError::error(
334 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
335 format!("Parameter '{}' is not allowed.", k),
336 ));
337 }
338
339 None => {
340 return Err(OperationOutcomeError::error(
341 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
342 "Parameter must have a name on it".to_string(),
343 ));
344 }
345 }
346 }
347
348 #return_value
349 }
350 }
351 }
352}
353
354fn generate_field_declaration(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
355 let field_name = field.ident.as_ref().ok_or_else(|| {
356 syn::Error::new_spanned(field, "FromParameters only supports named struct fields")
357 })?;
358
359 let field_type = get_optional_type(field).to_token_stream();
360
361 Ok(quote! {
362 let mut #field_name: Option<#field_type> = None;
363 })
364}
365
366fn generate_parameter_match_arm(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
367 let field_name = field.ident.as_ref().ok_or_else(|| {
368 syn::Error::new_spanned(field, "FromParameters only supports named struct fields")
369 })?;
370 let is_vector = determine_is_vector(field);
371 let expected_parameter_name = get_parameter_name(field);
372
373 let value_from_param = generate_value_extraction(field);
374
375 let setter = if is_vector {
376 quote! {
377 let tmp_value: Result<_, OperationOutcomeError> = #value_from_param;
378
379 if let Some(tmp_value) = tmp_value? {
380 if let Some(tmp_array) = #field_name.as_mut() {
381 tmp_array.push(tmp_value);
382 } else {
383 #field_name = Some(vec![tmp_value]);
384 }
385 }
386 }
387 } else {
388 quote! {
389 if #field_name.is_some() {
390 return Err(OperationOutcomeError::error(
391 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
392 format!("Parameter '{}' is not allowed to be repeated.", #expected_parameter_name)
393 ));
394 }
395
396 let tmp_value: Result<_, OperationOutcomeError> = #value_from_param;
397 #field_name = tmp_value?;
398 }
399 };
400
401 Ok(quote! {
402 Some(#expected_parameter_name) => {
403 #setter
404 }
405 })
406}
407
408fn generate_value_extraction(field: &Field) -> proc_macro2::TokenStream {
409 let value_type = field_inner_type(field);
410 let expected_parameter_name = get_parameter_name(field);
411 let current_parameter = format_ident!("param");
412
413 if is_nested_parameter(&field.attrs) {
414 quote! {
415 #value_type::try_from(
416 #current_parameter.part.unwrap_or_default()
417 )
418 .map(Some)
419 }
420 } else if value_type.ident == format_ident!("Resource") {
421 quote! {
422 Ok(#current_parameter.resource.map(|r| *r))
423 }
424 } else if value_type.ident == format_ident!("ParametersParameterValueTypeChoice") {
425 quote! {
426 Ok(#current_parameter.value)
427 }
428 } else if is_resource_type(field) {
429 quote! {
430 if let Some(Resource::#value_type(resource)) =
431 #current_parameter.resource.map(|r| *r)
432 {
433 Ok(Some(resource))
434 } else {
435 Err(OperationOutcomeError::error(
436 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
437 format!(
438 "Parameter '{}' does not contain correct value type.",
439 #expected_parameter_name
440 )
441 ))
442 }
443 }
444 } else {
445 let primitive = value_type.ident.to_string().replacen("FHIR", "", 1);
446 let parameter_value_type = format_ident!("{}", primitive);
447
448 quote! {
449 if let Some(
450 haste_fhir_model::r4::generated::resources::ParametersParameterValueTypeChoice::#parameter_value_type(value)
451 ) = #current_parameter.value {
452 Ok(Some(*value))
453 } else {
454 Err(OperationOutcomeError::error(
455 haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
456 format!(
457 "Parameter '{}' does not contain correct value type.",
458 #expected_parameter_name
459 )
460 ))
461 }
462 }
463 }
464}