paperclip_macros/
actix.rs

1//! Convenience macros for the [actix-web](https://github.com/paperclip-rs/paperclip/tree/master/plugins/actix-web)
2//! OpenAPI plugin (exposed by paperclip with `actix` feature).
3
4use heck::*;
5use http::StatusCode;
6use lazy_static::lazy_static;
7use proc_macro::TokenStream;
8use quote::{quote, ToTokens};
9use strum_macros::EnumString;
10use syn::{
11    parse_macro_input,
12    punctuated::{Pair, Punctuated},
13    spanned::Spanned,
14    Attribute, Data, DataEnum, DeriveInput, Field, Fields, FieldsNamed, FieldsUnnamed, FnArg,
15    Generics, Ident, ItemFn, Lit, Meta, MetaList, MetaNameValue, NestedMeta, Path, PathArguments,
16    ReturnType, Token, TraitBound, Type, TypeTraitObject,
17};
18
19use proc_macro2::TokenStream as TokenStream2;
20use std::collections::HashMap;
21
22const SCHEMA_MACRO_ATTR: &str = "openapi";
23
24lazy_static! {
25    static ref EMPTY_SCHEMA_HELP: String = format!(
26        "you can mark the struct with #[{}(empty)] to ignore this warning.",
27        SCHEMA_MACRO_ATTR
28    );
29}
30
31/// Actual parser and emitter for `api_v2_operation` macro.
32pub fn emit_v2_operation(attrs: TokenStream, input: TokenStream) -> TokenStream {
33    let default_span = proc_macro2::Span::call_site();
34    let mut item_ast: ItemFn = match syn::parse(input) {
35        Ok(s) => s,
36        Err(e) => {
37            emit_error!(e.span().unwrap(), "operation must be a function.");
38            return quote!().into();
39        }
40    };
41
42    // Unit struct
43    let s_name = format!("paperclip_{}", item_ast.sig.ident);
44    let unit_struct = Ident::new(&s_name, default_span);
45    let generics = &item_ast.sig.generics;
46    let mut generics_call = quote!();
47    let mut struct_definition = quote!(
48        #[allow(non_camel_case_types, missing_docs)]
49        struct #unit_struct;
50    );
51    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
52    if !generics.params.is_empty() {
53        let turbofish = ty_generics.as_turbofish();
54        let generics_params = extract_generics_params(&item_ast);
55        generics_call = quote!(#turbofish { p: std::marker::PhantomData });
56        struct_definition = quote!(struct #unit_struct #ty_generics { p: std::marker::PhantomData<(#generics_params)> } )
57    }
58
59    // Get rid of async prefix. In the end, we'll have them all as `impl Future` thingies.
60    if item_ast.sig.asyncness.is_some() {
61        item_ast.sig.asyncness = None;
62    }
63
64    let mut wrapper = quote!(paperclip::actix::ResponseWrapper<actix_web::HttpResponse, #unit_struct #ty_generics>);
65    let mut is_impl_trait = false;
66    let mut is_responder = false;
67    match &mut item_ast.sig.output {
68        rt @ ReturnType::Default => {
69            // Not particularly useful, but let's deal with it anyway
70            *rt = ReturnType::Type(
71                Token![->](default_span),
72                Box::new(syn::parse2(wrapper.clone()).expect("parsing empty type")),
73            );
74        }
75        ReturnType::Type(_, ty) => {
76            let t = quote!(#ty).to_string();
77            if let Type::ImplTrait(_) = &**ty {
78                is_impl_trait = true;
79            }
80
81            if t == "impl Responder" {
82                // `impl Responder` is a special case because we have to add another wrapper.
83                // FIXME: Better way to deal with this?
84                is_responder = true;
85                *ty = Box::new(
86                    syn::parse2(quote!(
87                        impl std::future::Future<Output=paperclip::actix::ResponderWrapper<#ty>>
88                    ))
89                    .expect("parsing impl trait"),
90                );
91            } else if !is_impl_trait {
92                // Any handler that's not returning an impl trait should return an `impl Future`
93                *ty = Box::new(
94                    syn::parse2(quote!(impl std::future::Future<Output=#ty>))
95                        .expect("parsing impl trait"),
96                );
97            }
98
99            if let Type::ImplTrait(imp) = &**ty {
100                let obj = TypeTraitObject {
101                    dyn_token: Some(Token![dyn](default_span)),
102                    bounds: imp.bounds.clone(),
103                };
104                *ty = Box::new(
105                    syn::parse2(quote!(#ty + paperclip::v2::schema::Apiv2Operation))
106                        .expect("parsing impl trait"),
107                );
108
109                if !is_responder {
110                    // NOTE: We're only using the box "type" to generate the operation data, we're not boxing
111                    // the handlers at runtime.
112                    wrapper = quote!(paperclip::actix::ResponseWrapper<Box<#obj + std::marker::Unpin>, #unit_struct #ty_generics>);
113                }
114            }
115        }
116    }
117
118    let block = item_ast.block;
119    // We need a function because devs should be able to use "return" keyword along the way.
120    let wrapped_fn_call = if is_responder {
121        quote!(paperclip::util::ready(paperclip::actix::ResponderWrapper((move || #block)())))
122    } else if is_impl_trait {
123        quote!((move || #block)())
124    } else {
125        quote!((move || async move #block)())
126    };
127
128    item_ast.block = Box::new(
129        syn::parse2(quote!(
130            {
131                let f = #wrapped_fn_call;
132                paperclip::actix::ResponseWrapper {
133                    responder: f,
134                    operations: #unit_struct #generics_call,
135                }
136            }
137        ))
138        .expect("parsing wrapped block"),
139    );
140
141    // Initialize operation parameters from macro attributes
142    let (mut op_params, mut op_values) = parse_operation_attrs(attrs);
143
144    if op_params.iter().any(|i| *i == "skip") {
145        return quote!(
146            #[allow(non_camel_case_types, missing_docs)]
147            #struct_definition
148
149            #item_ast
150
151            impl #impl_generics paperclip::v2::schema::Apiv2Operation for #unit_struct #ty_generics #where_clause {
152                fn operation() -> paperclip::v2::models::DefaultOperationRaw {
153                    Default::default()
154                }
155
156                #[allow(unused_mut)]
157                fn security_definitions() -> std::collections::BTreeMap<String, paperclip::v2::models::SecurityScheme> {
158                    Default::default()
159                }
160
161                fn definitions() -> std::collections::BTreeMap<String, paperclip::v2::models::DefaultSchemaRaw> {
162                    Default::default()
163                }
164
165                fn is_visible() -> bool {
166                    false
167                }
168            }
169        ).into();
170    }
171
172    // Optionally extract summary and description from doc comments
173    if !op_params.iter().any(|i| *i == "summary") {
174        let (summary, description) = extract_fn_documentation(&item_ast);
175        if let Some(summary) = summary {
176            op_params.push(Ident::new("summary", item_ast.span()));
177            op_values.push(summary)
178        }
179        if let Some(description) = description {
180            op_params.push(Ident::new("description", item_ast.span()));
181            op_values.push(description)
182        }
183    }
184
185    if op_params.iter().any(|i| *i == "deprecated") || extract_deprecated(&item_ast.attrs) {
186        op_params.push(Ident::new("deprecated", item_ast.span()));
187        op_values.push(quote!(true))
188    }
189
190    let modifiers = extract_fn_arguments_types(&item_ast);
191
192    let operation_modifier = if is_responder {
193        quote! { paperclip::actix::ResponderWrapper::<actix_web::HttpResponse> }
194    } else {
195        quote! { <<#wrapper as std::future::Future>::Output> }
196    };
197
198    quote!(
199        #struct_definition
200
201        #item_ast
202
203        impl #impl_generics paperclip::v2::schema::Apiv2Operation for #unit_struct #ty_generics #where_clause {
204            fn operation() -> paperclip::v2::models::DefaultOperationRaw {
205                use paperclip::actix::OperationModifier;
206                let mut op = paperclip::v2::models::DefaultOperationRaw {
207                    #(
208                        #op_params: #op_values,
209                    )*
210                    .. Default::default()
211                };
212                #(
213                    <#modifiers>::update_parameter(&mut op);
214                    <#modifiers>::update_security(&mut op);
215                )*
216                #operation_modifier::update_response(&mut op);
217                op
218            }
219
220            #[allow(unused_mut)]
221            fn security_definitions() -> std::collections::BTreeMap<String, paperclip::v2::models::SecurityScheme> {
222                use paperclip::actix::OperationModifier;
223                let mut map = Default::default();
224                #(
225                    <#modifiers>::update_security_definitions(&mut map);
226                )*
227                map
228            }
229
230            fn definitions() -> std::collections::BTreeMap<String, paperclip::v2::models::DefaultSchemaRaw> {
231                use paperclip::actix::OperationModifier;
232                let mut map = std::collections::BTreeMap::new();
233                #(
234                    <#modifiers>::update_definitions(&mut map);
235                )*
236                #operation_modifier::update_definitions(&mut map);
237                map
238            }
239        }
240    )
241        .into()
242}
243
244/// Extract punctuated generic parameters from fn definition
245fn extract_generics_params(item_ast: &ItemFn) -> Punctuated<Ident, syn::token::Comma> {
246    item_ast
247        .sig
248        .generics
249        .params
250        .pairs()
251        .filter_map(|pair| match pair {
252            Pair::Punctuated(syn::GenericParam::Type(gen), punct) => {
253                Some(Pair::new(gen.ident.clone(), Some(*punct)))
254            }
255            Pair::End(syn::GenericParam::Type(gen)) => Some(Pair::new(gen.ident.clone(), None)),
256            _ => None,
257        })
258        .collect()
259}
260
261/// Extract function arguments
262fn extract_fn_arguments_types(item_ast: &ItemFn) -> Vec<Type> {
263    item_ast
264        .sig
265        .inputs
266        .iter()
267        .filter_map(|inp| match inp {
268            FnArg::Receiver(_) => None,
269            FnArg::Typed(ref t) => Some(*t.ty.clone()),
270        })
271        .collect()
272}
273
274/// Parse macro attrs, matching to Operation fields
275/// Returning operation attribute identifier and value initialization arrays
276/// Note: Array likes initialized from string "val1, val2, val3", where "val1"
277/// would parse into destination item
278fn parse_operation_attrs(attrs: TokenStream) -> (Vec<Ident>, Vec<proc_macro2::TokenStream>) {
279    let attrs = crate::parse_input_attrs(attrs);
280    let mut params = Vec::new();
281    let mut values = Vec::new();
282    for attr in attrs.0 {
283        match &attr {
284            NestedMeta::Meta(Meta::Path(attr_path)) => {
285                if let Some(attr_) = attr_path.get_ident() {
286                    if *attr_ == "skip" || *attr_ == "deprecated" {
287                        params.push(attr_.clone());
288                    } else {
289                        emit_error!(attr_.span(), "Not supported bare attribute {:?}", attr_)
290                    }
291                }
292            }
293            NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) => {
294                if let Some(ident) = path.get_ident() {
295                    match ident.to_string().as_str() {
296                        "summary" | "description" | "operation_id" => {
297                            if let Lit::Str(val) = lit {
298                                params.push(ident.clone());
299                                values.push(quote!(Some(# val.to_string())));
300                            } else {
301                                emit_error!(lit.span(), "Expected string literal: {:?}", lit)
302                            }
303                        }
304                        "consumes" | "produces" => {
305                            if let Lit::Str(mimes) = lit {
306                                let mut mime_types = Vec::new();
307                                for val in mimes.value().split(',') {
308                                    let val = val.trim();
309                                    if let Err(err) = val.parse::<mime::Mime>() {
310                                        emit_error!(
311                                            lit.span(),
312                                            "Value {} does not parse as mime type: {}",
313                                            val,
314                                            err
315                                        );
316                                    } else {
317                                        mime_types.push(quote!(paperclip::v2::models::MediaRange( # val.parse().unwrap())));
318                                    }
319                                }
320                                if !mime_types.is_empty() {
321                                    params.push(ident.clone());
322                                    values.push(quote!({
323                                    let mut tmp = std::collections::BTreeSet::new();
324                                    # (
325                                    tmp.insert(# mime_types);
326                                    ) *
327                                    Some(tmp)
328                                    }));
329                                }
330                            } else {
331                                emit_error!(
332                                    lit.span(),
333                                    "Expected comma separated values in string literal: {:?}",
334                                    lit
335                                )
336                            }
337                        }
338                        x => emit_error!(ident.span(), "Unknown attribute {}", x),
339                    }
340                } else {
341                    emit_error!(
342                        path.span(),
343                        "Expected single identifier, got path {:?}",
344                        path
345                    )
346                }
347            }
348            NestedMeta::Meta(Meta::List(MetaList { path, nested, .. })) => {
349                if let Some(ident) = path.get_ident() {
350                    match ident.to_string().as_str() {
351                        "tags" => {
352                            let mut tags = Vec::new();
353                            for meta in nested.pairs().map(|pair| pair.into_value()) {
354                                if let NestedMeta::Meta(Meta::Path(Path { segments, .. })) = meta {
355                                    tags.push(segments[0].ident.to_string());
356                                } else if let NestedMeta::Lit(Lit::Str(lit)) = meta {
357                                    tags.push(lit.value());
358                                } else {
359                                    emit_error!(
360                                        meta.span(),
361                                        "Expected comma separated list of tags idents: {:?}",
362                                        meta
363                                    )
364                                }
365                            }
366                            if !tags.is_empty() {
367                                params.push(ident.clone());
368                                values.push(quote!(vec![ #( #tags.to_string() ),* ]));
369                            }
370                        }
371                        x => emit_error!(ident.span(), "Unknown list ident {}", x),
372                    }
373                }
374            }
375            _ => {
376                emit_error!(attr.span(), "Not supported attribute type {:?}", attr)
377            }
378        }
379    }
380    (params, values)
381}
382
383/// Extracts summary from top line doc comment and description from the rest
384fn extract_fn_documentation(
385    item_ast: &ItemFn,
386) -> (
387    Option<proc_macro2::TokenStream>,
388    Option<proc_macro2::TokenStream>,
389) {
390    let docs = extract_documentation(&item_ast.attrs);
391    let lines = docs.lines();
392    let mut before_empty = true;
393    let (summary, description): (Vec<_>, Vec<_>) = lines.partition(|line| {
394        if line.trim().is_empty() {
395            before_empty = false
396        };
397        before_empty
398    });
399    let none_if_empty = |text: &str| {
400        if text.is_empty() {
401            None
402        } else {
403            Some(quote!(Some(#text.to_string())))
404        }
405    };
406    let summary = none_if_empty(summary.into_iter().collect::<String>().trim());
407    let description = none_if_empty(description.join("\n").trim());
408    (summary, description)
409}
410
411/// Actual parser and emitter for `api_v2_errors` macro.
412pub fn emit_v2_errors(attrs: TokenStream, input: TokenStream) -> TokenStream {
413    let item_ast = match crate::expect_struct_or_enum(input) {
414        Ok(i) => i,
415        Err(ts) => return ts,
416    };
417
418    let name = &item_ast.ident;
419    let attrs = crate::parse_input_attrs(attrs);
420    let generics = item_ast.generics.clone();
421    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
422
423    let mut default_schema: Option<syn::Ident> = None;
424    // Convert macro attributes to tuples in form of (u16, &str, &Option<syn::Ident>)
425    let error_codes = attrs
426        .0
427        .iter()
428        // Pair code attrs with description attrs; save attr itself to properly span error messages at later stage
429        .fold(Vec::new(), |mut list: Vec<(Option<u16>, Option<String>, Option<syn::Ident>, _)>, attr| {
430            let span = attr.span().unwrap();
431            match attr {
432                // Read named attribute.
433                NestedMeta::Meta(Meta::NameValue(name_value)) => {
434                    let attr_name = name_value.path.get_ident().map(|ident| ident.to_string());
435                    let attr_value = &name_value.lit;
436                    match (attr_name.as_deref(), attr_value) {
437                        // "code" attribute adds new element to list
438                        (Some("code"), Lit::Int(attr_value)) => {
439                            let status_code = attr_value.base10_parse::<u16>()
440                                .map_err(|_| emit_error!(span, "Invalid u16 in code argument")).ok();
441                            list.push((status_code, None, None, attr));
442                        }
443                        // "description" attribute updates last element in list
444                        (Some("description"), Lit::Str(attr_value)) =>
445                            if let Some(last_value) = list.last_mut() {
446                                if last_value.1.is_some() {
447                                    emit_warning!(span, "This attribute overwrites previous description");
448                                }
449                                last_value.1 = Some(attr_value.value());
450                            } else {
451                                emit_error!(span, "Attribute 'description' can be only placed after prior 'code' argument");
452                            },
453                        // "schema" attribute updates last element in list
454                        (Some("schema"), Lit::Str(attr_value)) =>
455                            if let Some(last_value) = list.last_mut() {
456                                if last_value.2.is_some() {
457                                    emit_warning!(span, "This attribute overwrites previous schema");
458                                }
459                                match attr_value.parse() {
460                                    Ok(value) => last_value.2 = Some(value),
461                                    Err(error) => emit_error!(span, "Error parsing schema: {}", error),
462                                }
463                            } else {
464                                emit_error!(span, "Attribute 'schema' can be only placed after prior 'code' argument");
465                            },
466                        (Some("default_schema"), Lit::Str(attr_value)) =>
467                            match attr_value.parse() {
468                                Ok(value) => default_schema = Some(value),
469                                Err(error) => emit_error!(span, "Error parsing default_schema: {}", error),
470                            },
471                        _ => emit_error!(span, "Invalid macro attribute. Should be plain u16, 'code = u16', 'description = str', 'schema = str' or 'default_schema = str'")
472                    }
473                }
474                // Read plain status code as attribute.
475                NestedMeta::Lit(Lit::Int(attr_value)) => {
476                    let status_code = attr_value.base10_parse::<u16>()
477                        .map_err(|_| emit_error!(span, "Invalid u16 in code argument")).ok();
478                    list.push((status_code, None, None, attr));
479                }
480                _ => emit_error!(span, "This macro supports only named attributes - 'code' (u16), 'description' (str), 'schema' (str) or 'default_schema' (str)")
481            }
482
483            list
484        })
485        .iter()
486        // Map code-message pairs into bits of code, filter empty codes out
487        .filter_map(|quad| {
488            let (code, description, schema) = match quad {
489                (Some(code), Some(description), schema, _) => {
490                    (code, description.to_owned(), schema.to_owned())
491                }
492                (Some(code), None, schema, attr) => {
493                    let span = attr.span().unwrap();
494                    let description = StatusCode::from_u16(*code)
495                        .map_err(|_| {
496                            emit_warning!(span, format!("Invalid status code {}", code));
497                            String::new()
498                        })
499                        .map(|s| s.canonical_reason()
500                            .map(str::to_string)
501                            .unwrap_or_else(|| {
502                                emit_warning!(span, format!("Status code {} doesn't have a canonical name", code));
503                                String::new()
504                            })
505                        )
506                        .unwrap_or_else(|_| String::new());
507                    (code, description, schema.to_owned())
508                }
509                (None, _, _, _) => return None,
510            };
511            Some((*code, description, schema))
512        })
513        .collect::<Vec<(u16, String, Option<syn::Ident>)>>();
514
515    let error_definitions = error_codes.iter().fold(
516        if default_schema.is_none() {
517            TokenStream2::new()
518        } else {
519            quote! {
520                #default_schema::update_definitions(map);
521            }
522        },
523        |mut stream, (_, _, schema)| {
524            if let Some(schema) = schema {
525                let tokens = quote! {
526                    #schema::update_definitions(map);
527                };
528                stream.extend(tokens);
529            }
530            stream
531        },
532    );
533
534    let update_definitions = quote! {
535        fn update_definitions(map: &mut std::collections::BTreeMap<String, paperclip::v2::models::DefaultSchemaRaw>) {
536            use paperclip::actix::OperationModifier;
537            #error_definitions
538        }
539    };
540
541    // for compatibility with previous error trait
542    let error_map = error_codes.iter().fold(
543        proc_macro2::TokenStream::new(),
544        |mut stream, (code, description, _)| {
545            let token = quote! {
546                (#code, #description),
547            };
548            stream.extend(token);
549            stream
550        },
551    );
552
553    let update_error_helper = quote! {
554        fn update_error_definitions(code: &u16, description: &str, schema: &Option<&str>, op: &mut paperclip::v2::models::DefaultOperationRaw) {
555            if let Some(schema) = &schema {
556                op.responses.insert(code.to_string(), paperclip::v2::models::Either::Right(paperclip::v2::models::Response {
557                    description: Some(description.to_string()),
558                    schema: Some(paperclip::v2::models::DefaultSchemaRaw {
559                        name: Some(schema.to_string()),
560                        reference: Some(format!("#/definitions/{}", schema)),
561                        .. Default::default()
562                    }),
563                    ..Default::default()
564                }));
565            } else {
566                op.responses.insert(code.to_string(), paperclip::v2::models::Either::Right(paperclip::v2::models::DefaultResponseRaw {
567                    description: Some(description.to_string()),
568                    ..Default::default()
569                }));
570            }
571        }
572    };
573    let default_schema = default_schema.map(|i| i.to_string());
574    let update_errors = error_codes.iter().fold(
575        update_error_helper,
576        |mut stream, (code, description, schema)| {
577            let tokens = if let Some(schema) = schema {
578                let schema = schema.to_string();
579                quote! {
580                    update_error_definitions(&#code, #description, &Some(#schema), op);
581                }
582            } else if let Some(scheme) = &default_schema {
583                quote! {
584                    update_error_definitions(&#code, #description, &Some(#scheme), op);
585                }
586            } else {
587                quote! {
588                    update_error_definitions(&#code, #description, &None, op);
589                }
590            };
591            stream.extend(tokens);
592            stream
593        },
594    );
595
596    let gen = quote! {
597        #item_ast
598
599        impl #impl_generics paperclip::v2::schema::Apiv2Errors for #name #ty_generics #where_clause {
600            const ERROR_MAP: &'static [(u16, &'static str)] = &[
601                #error_map
602            ];
603            fn update_error_definitions(op: &mut paperclip::v2::models::DefaultOperationRaw) {
604                #update_errors
605            }
606            #update_definitions
607        }
608    };
609
610    gen.into()
611}
612
613/// Actual parser and emitter for `emit_v2_errors_overlay` macro.
614pub fn emit_v2_errors_overlay(attrs: TokenStream, input: TokenStream) -> TokenStream {
615    let item_ast = match crate::expect_struct_or_enum(input) {
616        Ok(i) => i,
617        Err(ts) => return ts,
618    };
619
620    let name = &item_ast.ident;
621    let inner = match &item_ast.data {
622        Data::Struct(s) => if s.fields.len() == 1 {
623            match &s.fields {
624                Fields::Unnamed(s) => s.unnamed.first().map(|s| match &s.ty {
625                    Type::Path(s) => s.path.segments.first().map(|f| &f.ident),
626                    _ => None,
627                }),
628                _ => None,
629            }
630        } else {
631            None
632        }
633        .flatten()
634        .unwrap_or_else(|| {
635            abort!(
636                s.fields.span(),
637                "This macro supports only unnamed structs with 1 element"
638            )
639        }),
640        _ => {
641            abort!(item_ast.span(), "This macro supports only unnamed structs");
642        }
643    };
644
645    let attrs = crate::parse_input_attrs(attrs);
646    let generics = item_ast.generics.clone();
647    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
648
649    // Convert macro attributes to vector of u16
650    let error_codes = attrs
651        .0
652        .iter()
653        // Pair code attrs with description attrs; save attr itself to properly span error messages at later stage
654        .fold(Vec::new(), |mut list: Vec<u16>, attr| {
655            let span = attr.span().unwrap();
656            match attr {
657                // Read plain status code as attribute.
658                NestedMeta::Lit(Lit::Int(attr_value)) => {
659                    let status_code = attr_value
660                        .base10_parse::<u16>()
661                        .map_err(|_| emit_error!(span, "Invalid u16 in code argument"))
662                        .unwrap();
663                    list.push(status_code);
664                }
665                _ => emit_error!(
666                    span,
667                    "This macro supports only named attributes - 'code' (u16)"
668                ),
669            }
670
671            list
672        });
673    let filter_error_codes = error_codes
674        .iter()
675        .fold(TokenStream2::new(), |mut stream, code| {
676            let status_code = &code.to_string();
677            let tokens = quote! {
678                op.responses.remove(#status_code);
679            };
680            stream.extend(tokens);
681            stream
682        });
683
684    let gen = quote! {
685        #item_ast
686
687        impl std::fmt::Display for #name {
688            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
689                std::fmt::Display::fmt(&self.0, f)
690            }
691        }
692
693        impl actix_web::error::ResponseError for #name {
694            fn status_code(&self) -> actix_web::http::StatusCode {
695                self.0.status_code()
696            }
697            fn error_response(&self) -> actix_web::HttpResponse {
698                self.0.error_response()
699            }
700        }
701
702        impl #impl_generics paperclip::v2::schema::Apiv2Errors for #name #ty_generics #where_clause {
703            const ERROR_MAP: &'static [(u16, &'static str)] = &[];
704            fn update_definitions(map: &mut std::collections::BTreeMap<String, paperclip::v2::models::DefaultSchemaRaw>) {
705                #inner::update_definitions(map);
706            }
707            fn update_error_definitions(op: &mut paperclip::v2::models::DefaultOperationRaw) {
708                #inner::update_error_definitions(op);
709                #filter_error_codes
710            }
711        }
712    };
713
714    gen.into()
715}
716
717fn extract_rename(attrs: &[Attribute]) -> Option<String> {
718    let attrs = extract_openapi_attrs(attrs);
719    for attr in attrs.flat_map(|attr| attr.into_iter()) {
720        if let NestedMeta::Meta(Meta::NameValue(nv)) = attr {
721            if nv.path.is_ident("rename") {
722                if let Lit::Str(s) = nv.lit {
723                    return Some(s.value());
724                } else {
725                    emit_error!(
726                        nv.lit.span().unwrap(),
727                        format!(
728                            "`#[{}(rename = \"...\")]` expects a string argument",
729                            SCHEMA_MACRO_ATTR
730                        ),
731                    );
732                }
733            }
734        }
735    }
736
737    None
738}
739
740fn extract_example(attrs: &[Attribute]) -> Option<String> {
741    let attrs = extract_openapi_attrs(attrs);
742    for attr in attrs.flat_map(|attr| attr.into_iter()) {
743        if let NestedMeta::Meta(Meta::NameValue(nv)) = attr {
744            if nv.path.is_ident("example") {
745                if let Lit::Str(s) = nv.lit {
746                    return Some(s.value());
747                } else {
748                    emit_error!(
749                        nv.lit.span().unwrap(),
750                        format!(
751                            "`#[{}(example = \"...\")]` expects a string argument",
752                            SCHEMA_MACRO_ATTR
753                        ),
754                    );
755                }
756            }
757        }
758    }
759
760    None
761}
762
763fn field_extract_f32(nv: MetaNameValue) -> Option<proc_macro2::TokenStream> {
764    let value: Result<proc_macro2::TokenStream, String> = match &nv.lit {
765        Lit::Str(s) => match s.value().parse::<f32>() {
766            Ok(s) => Ok(quote! { #s }),
767            Err(error) => Err(error.to_string()),
768        },
769        Lit::Float(f) => Ok(quote! { #f }),
770        Lit::Int(i) => {
771            let f: f32 = i
772                .base10_parse()
773                .unwrap_or_else(|e| abort!(i.span(), "{}", e));
774            Ok(quote! { #f })
775        }
776        _ => {
777            emit_error!(
778                nv.lit.span().unwrap(),
779                "Expected a string, float or int argument"
780            );
781            return None;
782        }
783    };
784    match value {
785        Ok(value) => Some(value),
786        Err(error) => {
787            emit_error!(nv.lit.span().unwrap(), error);
788            None
789        }
790    }
791}
792
793fn extract_openapi_f32(attrs: &[Attribute], ident: &str) -> Option<proc_macro2::TokenStream> {
794    let attrs = extract_openapi_attrs(attrs);
795    for attr in attrs.flat_map(|attr| attr.into_iter()) {
796        if let NestedMeta::Meta(Meta::NameValue(nv)) = attr {
797            if nv.path.is_ident(ident) {
798                return field_extract_f32(nv);
799            }
800        }
801    }
802
803    None
804}
805
806/// Actual parser and emitter for `api_v2_schema` macro.
807pub fn emit_v2_definition(input: TokenStream) -> TokenStream {
808    let item_ast = match crate::expect_struct_or_enum(input) {
809        Ok(i) => i,
810        Err(ts) => return ts,
811    };
812
813    if let Some(empty) = check_empty_schema(&item_ast) {
814        return empty;
815    }
816
817    let docs = extract_documentation(&item_ast.attrs);
818    let docs = docs.trim();
819
820    let example = if let Some(example) = extract_example(&item_ast.attrs) {
821        // allow to parse escaped json string or single str value
822        quote!(
823            paperclip::v2::serde_json::from_str::<paperclip::v2::serde_json::Value>(#example).ok().or_else(|| Some(#example.into()))
824        )
825    } else {
826        quote!(None)
827    };
828
829    let props = SerdeProps::from_item_attrs(&item_ast.attrs);
830
831    let name = &item_ast.ident;
832
833    // Add `Apiv2Schema` bound for impl if the type is generic.
834    let mut generics = item_ast.generics.clone();
835    let bound = syn::parse2::<TraitBound>(quote!(paperclip::v2::schema::Apiv2Schema))
836        .expect("expected to parse trait bound");
837    generics.type_params_mut().for_each(|param| {
838        param.bounds.push(bound.clone().into());
839    });
840
841    let opt_impl = add_optional_impl(name, &generics);
842    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
843
844    // FIXME: Use attr path segments to find flattening, skipping, etc.
845    let mut props_gen = quote! {};
846
847    match &item_ast.data {
848        Data::Struct(ref s) => {
849            props_gen.extend(quote!(
850                schema.data_type = Some(DataType::Object);
851            ));
852            match &s.fields {
853                Fields::Named(ref f) => {
854                    handle_field_struct(f, &item_ast.attrs, &props, &mut props_gen)
855                }
856                Fields::Unnamed(ref f) => {
857                    handle_unnamed_field_struct(f, &item_ast.attrs, &mut props_gen)
858                }
859                Fields::Unit => {
860                    emit_warning!(
861                        s.struct_token.span().unwrap(),
862                        "unit structs do not have any fields and hence will have empty schema.";
863                        help = "{}", &*EMPTY_SCHEMA_HELP;
864                    );
865                }
866            }
867        }
868        Data::Enum(ref e) => handle_enum(e, &props, &mut props_gen),
869        Data::Union(ref u) => emit_error!(
870            u.union_token.span().unwrap(),
871            "unions are unsupported for deriving schema"
872        ),
873    };
874
875    let base_name = extract_rename(&item_ast.attrs).unwrap_or_else(|| name.to_string());
876    let type_params: Vec<&Ident> = generics.type_params().map(|p| &p.ident).collect();
877    let schema_name = if type_params.is_empty() {
878        quote! { #base_name }
879    } else {
880        let type_names = quote! {
881            [#(#type_params::name()),*]
882                .iter()
883                .filter_map(|n| n.to_owned())
884                .collect::<Vec<String>>()
885                .join(", ")
886        };
887        quote! { format!("{}<{}>", #base_name, #type_names) }
888    };
889    let props_gen_empty = props_gen.is_empty();
890
891    #[cfg(not(feature = "path-in-definition"))]
892    let default_schema_raw_def = quote! {
893        let mut schema = DefaultSchemaRaw {
894            name: Some(#schema_name.into()),
895            example: #example,
896            ..Default::default()
897        };
898    };
899
900    #[cfg(feature = "path-in-definition")]
901    let default_schema_raw_def = quote! {
902        let mut schema = DefaultSchemaRaw {
903            name: Some(Self::__paperclip_schema_name()), // Add name for later use.
904            example: #example,
905            .. Default::default()
906        };
907    };
908
909    #[cfg(not(feature = "path-in-definition"))]
910    let paperclip_schema_name_def = quote!();
911
912    #[cfg(feature = "path-in-definition")]
913    let paperclip_schema_name_def = quote! {
914        fn __paperclip_schema_name() -> String {
915            // The module path itself, e.g cratename::module
916            let full_module_path = std::module_path!().to_string();
917            // We're not interested in the crate name, nor do we want :: as a seperator
918            let trimmed_module_path = full_module_path.split("::")
919                .enumerate()
920                .filter(|(index, _)| *index != 0) // Skip the first element, i.e the crate name
921                .map(|(_, component)| component)
922                .collect::<Vec<_>>()
923                .join("_");
924            format!("{}_{}", trimmed_module_path, #schema_name)
925        }
926    };
927
928    #[cfg(not(feature = "path-in-definition"))]
929    let const_name_def = quote! {
930        fn name() -> Option<String> {
931            Some(#schema_name.to_string())
932        }
933    };
934
935    #[cfg(feature = "path-in-definition")]
936    let const_name_def = quote!();
937
938    #[cfg(not(feature = "path-in-definition"))]
939    let props_gen_empty_name_def = quote! {
940        schema.name = Some(#schema_name.into());
941    };
942
943    #[cfg(feature = "path-in-definition")]
944    let props_gen_empty_name_def = quote! {
945        schema.name = Some(Self::__paperclip_schema_name());
946    };
947
948    let gen = quote! {
949        impl #impl_generics #name #ty_generics #where_clause {
950            #paperclip_schema_name_def
951        }
952
953        impl #impl_generics paperclip::v2::schema::Apiv2Schema for #name #ty_generics #where_clause {
954            #const_name_def
955            fn description() -> &'static str {
956                #docs
957            }
958
959            fn raw_schema() -> paperclip::v2::models::DefaultSchemaRaw {
960                use paperclip::v2::models::{DataType, DataTypeFormat, DefaultSchemaRaw};
961                use paperclip::v2::schema::TypedData;
962
963                #default_schema_raw_def
964
965                #props_gen
966                // props_gen may override the schema for unnamed structs with 1 element
967                // as it replaces the struct type with inner type.
968                // make sure we set the name properly if props_gen is not empty
969                if !#props_gen_empty {
970                    #props_gen_empty_name_def
971                }
972                schema
973            }
974        }
975
976        #opt_impl
977    };
978
979    gen.into()
980}
981
982/// Actual parser and emitter for `Apiv2Security` derive macro.
983pub fn emit_v2_security(input: TokenStream) -> TokenStream {
984    let item_ast = match crate::expect_struct_or_enum(input) {
985        Ok(i) => i,
986        Err(ts) => return ts,
987    };
988
989    if let Some(empty) = check_empty_schema(&item_ast) {
990        return empty;
991    }
992
993    let name = &item_ast.ident;
994    // Add `Apiv2Schema` bound for impl if the type is generic.
995    let mut generics = item_ast.generics.clone();
996    let bound = syn::parse2::<TraitBound>(quote!(paperclip::v2::schema::Apiv2Schema))
997        .expect("expected to parse trait bound");
998    generics.type_params_mut().for_each(|param| {
999        param.bounds.push(bound.clone().into());
1000    });
1001
1002    let opt_impl = add_optional_impl(name, &generics);
1003    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1004
1005    let mut security_attrs = HashMap::new();
1006    let mut scopes = Vec::new();
1007
1008    let valid_attrs = vec![
1009        "alias",
1010        "description",
1011        "name",
1012        "in",
1013        "flow",
1014        "auth_url",
1015        "token_url",
1016        "parent",
1017    ];
1018    let invalid_attr_msg = format!("Invalid macro attribute. Should be bare security type [\"apiKey\", \"oauth2\"] or named attribute {:?}", valid_attrs);
1019
1020    // Read security params from openapi attr.
1021    for nested in extract_openapi_attrs(&item_ast.attrs) {
1022        for nested_attr in nested {
1023            let span = nested_attr.span().unwrap();
1024            match &nested_attr {
1025                // Read bare attribute.
1026                NestedMeta::Meta(Meta::Path(attr_path)) => {
1027                    if let Some(type_) = attr_path.get_ident() {
1028                        if security_attrs
1029                            .insert("type".to_string(), type_.to_string())
1030                            .is_some()
1031                        {
1032                            emit_warning!(span, "Auth type defined multiple times.");
1033                        }
1034                    }
1035                }
1036                // Read named attribute.
1037                NestedMeta::Meta(Meta::NameValue(name_value)) => {
1038                    let attr_name = name_value.path.get_ident().map(|id| id.to_string());
1039                    let attr_value = &name_value.lit;
1040
1041                    if let Some(attr_name) = attr_name {
1042                        if valid_attrs.contains(&attr_name.as_str()) {
1043                            if let Lit::Str(attr_value) = attr_value {
1044                                if security_attrs
1045                                    .insert(attr_name.clone(), attr_value.value())
1046                                    .is_some()
1047                                {
1048                                    emit_warning!(
1049                                        span,
1050                                        "Attribute {} defined multiple times.",
1051                                        attr_name
1052                                    );
1053                                }
1054                            } else {
1055                                emit_warning!(
1056                                    span,
1057                                    "Invalid value for named attribute: {}",
1058                                    attr_name
1059                                );
1060                            }
1061                        } else {
1062                            emit_warning!(span, invalid_attr_msg);
1063                        }
1064                    } else {
1065                        emit_error!(span, invalid_attr_msg);
1066                    }
1067                }
1068                // Read scopes attribute
1069                NestedMeta::Meta(Meta::List(list_attr)) => {
1070                    match list_attr
1071                        .path
1072                        .get_ident()
1073                        .map(|id| id.to_string())
1074                        .as_deref()
1075                    {
1076                        Some("scopes") => {
1077                            for nested in &list_attr.nested {
1078                                match nested {
1079                                    NestedMeta::Lit(Lit::Str(value)) => {
1080                                        scopes.push(value.value().to_string())
1081                                    }
1082                                    _ => emit_error!(
1083                                        nested.span().unwrap(),
1084                                        "Invalid list attribute value"
1085                                    ),
1086                                }
1087                            }
1088                        }
1089                        Some(path) => emit_error!(span, "Invalid list attribute: {}", path),
1090                        _ => emit_error!(span, "Invalid list attribute"),
1091                    }
1092                }
1093                _ => {
1094                    emit_error!(span, invalid_attr_msg);
1095                }
1096            }
1097        }
1098    }
1099
1100    let scopes_stream = scopes
1101        .iter()
1102        .fold(proc_macro2::TokenStream::new(), |mut stream, scope| {
1103            stream.extend(quote! {
1104                oauth2_scopes.insert(#scope.to_string(), #scope.to_string());
1105            });
1106            stream
1107        });
1108
1109    let (security_def, security_def_name) = match (
1110        security_attrs.get("type"),
1111        security_attrs.get("parent"),
1112    ) {
1113        (Some(type_), None) => {
1114            let alias = security_attrs.get("alias").unwrap_or(type_);
1115            let quoted_description = quote_option(security_attrs.get("description"));
1116            let quoted_name = quote_option(security_attrs.get("name"));
1117            let quoted_in = quote_option(security_attrs.get("in"));
1118            let quoted_flow = quote_option(security_attrs.get("flow"));
1119            let quoted_auth_url = quote_option(security_attrs.get("auth_url"));
1120            let quoted_token_url = quote_option(security_attrs.get("token_url"));
1121
1122            (
1123                Some(quote! {
1124                    Some(paperclip::v2::models::SecurityScheme {
1125                        type_: #type_.to_string(),
1126                        name: #quoted_name,
1127                        in_: #quoted_in,
1128                        flow: #quoted_flow,
1129                        auth_url: #quoted_auth_url,
1130                        token_url: #quoted_token_url,
1131                        scopes: std::collections::BTreeMap::new(),
1132                        description: #quoted_description,
1133                    })
1134                }),
1135                Some(quote!(Some(#alias.to_string()))),
1136            )
1137        }
1138        (None, Some(parent)) => {
1139            let parent_ident = Ident::new(parent, proc_macro2::Span::call_site());
1140            // Child of security definition (Scopes will be glued to parent definition).
1141            (
1142                Some(quote! {
1143                    let mut oauth2_scopes = std::collections::BTreeMap::new();
1144                    #scopes_stream
1145                    let mut scheme = #parent_ident::security_scheme()
1146                        .expect("empty schema. did you derive `Apiv2Security` for parent struct?");
1147                    scheme.scopes = oauth2_scopes;
1148                    Some(scheme)
1149                }),
1150                Some(quote!(<#parent_ident as paperclip::v2::schema::Apiv2Schema>::name())),
1151            )
1152        }
1153        (Some(_), Some(_)) => {
1154            emit_error!(
1155                item_ast.span().unwrap(),
1156                "Can't define new security type and use parent attribute together."
1157            );
1158            (None, None)
1159        }
1160        (None, None) => {
1161            emit_error!(
1162                item_ast.span().unwrap(),
1163                "Invalid attributes. Expected security type or parent defined."
1164            );
1165            (None, None)
1166        }
1167    };
1168
1169    let gen = if let (Some(def_block), Some(def_name)) = (security_def, security_def_name) {
1170        quote! {
1171            impl #impl_generics paperclip::v2::schema::Apiv2Schema for #name #ty_generics #where_clause {
1172                fn name() -> Option<String> {
1173                    #def_name
1174                }
1175
1176                fn security_scheme() -> Option<paperclip::v2::models::SecurityScheme> {
1177                    #def_block
1178                }
1179            }
1180
1181            #opt_impl
1182        }
1183    } else {
1184        quote! {}
1185    };
1186
1187    gen.into()
1188}
1189
1190/// Actual parser and emitter for `Apiv2Header` derive macro.
1191pub fn emit_v2_header(input: TokenStream) -> TokenStream {
1192    let item_ast = match crate::expect_struct_or_enum(input) {
1193        Ok(i) => i,
1194        Err(ts) => return ts,
1195    };
1196
1197    if let Some(empty) = check_empty_schema(&item_ast) {
1198        return empty;
1199    }
1200
1201    let name = &item_ast.ident;
1202    // Add `Apiv2Schema` bound for impl if the type is generic.
1203    let mut generics = item_ast.generics.clone();
1204    let bound = syn::parse2::<TraitBound>(quote!(paperclip::v2::schema::Apiv2Schema))
1205        .expect("expected to parse trait bound");
1206    generics.type_params_mut().for_each(|param| {
1207        param.bounds.push(bound.clone().into());
1208    });
1209
1210    let opt_impl = add_optional_impl(name, &generics);
1211    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1212
1213    let mut header_definitions = vec![];
1214
1215    let valid_attrs = vec!["description", "name", "format", "maximum", "minimum"];
1216    let invalid_attr_msg = format!(
1217        "Invalid macro attribute. Should be named attribute {:?}",
1218        valid_attrs
1219    );
1220
1221    fn quote_format(format: &str) -> proc_macro2::TokenStream {
1222        match format {
1223            "int32" => quote! { Some(paperclip::v2::models::DataTypeFormat::Int32) },
1224            "int64" => quote! { Some(paperclip::v2::models::DataTypeFormat::Int64) },
1225            "float" => quote! { Some(paperclip::v2::models::DataTypeFormat::Float) },
1226            "double" => quote! { Some(paperclip::v2::models::DataTypeFormat::Double) },
1227            "byte" => quote! { Some(paperclip::v2::models::DataTypeFormat::Byte) },
1228            "binary" => quote! { Some(paperclip::v2::models::DataTypeFormat::Binary) },
1229            "date" => quote! { Some(paperclip::v2::models::DataTypeFormat::Date) },
1230            "datetime" | "date-time" => {
1231                quote! { Some(paperclip::v2::models::DataTypeFormat::DateTime) }
1232            }
1233            "password" => quote! { Some(paperclip::v2::models::DataTypeFormat::Password) },
1234            "url" => quote! { Some(paperclip::v2::models::DataTypeFormat::Url) },
1235            "uuid" => quote! { Some(paperclip::v2::models::DataTypeFormat::Uuid) },
1236            "ip" => quote! { Some(paperclip::v2::models::DataTypeFormat::Ip) },
1237            "ipv4" => quote! { Some(paperclip::v2::models::DataTypeFormat::IpV4) },
1238            "ipv6" => quote! { Some(paperclip::v2::models::DataTypeFormat::IpV6) },
1239            "other" => quote! { Some(paperclip::v2::models::DataTypeFormat::Other) },
1240            v => {
1241                emit_error!(
1242                    format.span().unwrap(),
1243                    format!("Invalid format attribute value. Got {}", v)
1244                );
1245                quote! { None }
1246            }
1247        }
1248    }
1249
1250    let struct_ast = match &item_ast.data {
1251        Data::Struct(struct_ast) => struct_ast,
1252        Data::Enum(_) | Data::Union(_) => {
1253            emit_error!(
1254                item_ast.span(),
1255                "Invalid data type. Apiv2Header should be defined on a struct"
1256            );
1257            return quote!().into();
1258        }
1259    };
1260
1261    if extract_openapi_attrs(&item_ast.attrs)
1262        .peekable()
1263        .peek()
1264        .is_some()
1265    {
1266        emit_error!(
1267            item_ast.span(),
1268            "Invalid openapi attribute. openapi attribute should be defined at struct fields level"
1269        );
1270        return quote!().into();
1271    }
1272
1273    for field in &struct_ast.fields {
1274        let mut parameter_attrs = HashMap::new();
1275        let field_name = &field.ident;
1276        let docs = extract_documentation(&field.attrs);
1277        let docs = docs.trim();
1278
1279        // Read header params from openapi attr.
1280        for nested in extract_openapi_attrs(&field.attrs) {
1281            for nested_attr in nested {
1282                let span = nested_attr.span().unwrap();
1283                match &nested_attr {
1284                    // Read bare attribute (support for skip attribute)
1285                    NestedMeta::Meta(Meta::Path(attr_path)) => {
1286                        if let Some(attr) = attr_path.get_ident() {
1287                            if *attr == "skip" {
1288                                parameter_attrs.insert("skip".to_owned(), "".to_owned());
1289                            }
1290                        }
1291                    }
1292                    // Read named attribute.
1293                    NestedMeta::Meta(Meta::NameValue(name_value)) => {
1294                        let attr_name = name_value.path.get_ident().map(|id| id.to_string());
1295                        let attr_value = &name_value.lit;
1296
1297                        if let Some(attr_name) = attr_name {
1298                            if valid_attrs.contains(&attr_name.as_str()) {
1299                                if let Some(value) = match attr_value {
1300                                    Lit::Str(attr_value) => Some(attr_value.value()),
1301                                    Lit::Float(x) => Some(x.to_string()),
1302                                    Lit::Int(x) => Some(x.to_string()),
1303                                    _ => {
1304                                        emit_warning!(
1305                                            span,
1306                                            "Invalid value for named attribute: {}",
1307                                            attr_name
1308                                        );
1309                                        None
1310                                    }
1311                                } {
1312                                    if parameter_attrs.insert(attr_name.clone(), value).is_some() {
1313                                        emit_warning!(
1314                                            span,
1315                                            "Attribute {} defined multiple times.",
1316                                            attr_name
1317                                        );
1318                                    }
1319                                }
1320                            } else {
1321                                emit_warning!(span, invalid_attr_msg);
1322                            }
1323                        } else {
1324                            emit_error!(span, invalid_attr_msg);
1325                        }
1326                    }
1327                    _ => {
1328                        emit_error!(span, invalid_attr_msg);
1329                    }
1330                }
1331            }
1332        }
1333
1334        if parameter_attrs.contains_key("skip") {
1335            continue;
1336        }
1337
1338        let docs = (!docs.is_empty()).then(|| docs.to_owned());
1339        let quoted_description = quote_option(parameter_attrs.get("description").or(docs.as_ref()));
1340        let name_string = field_name.as_ref().map(|name| name.to_string());
1341        let quoted_name = if let Some(name) = parameter_attrs.get("name").or(name_string.as_ref()) {
1342            name
1343        } else {
1344            emit_error!(
1345                field.span(),
1346                "Missing header name. Either add a name using the openapi attribute or use named struct parameter"
1347            );
1348            return quote!().into();
1349        };
1350
1351        let (quoted_type, quoted_format) = if let Some(ty_ref) = get_field_type(field) {
1352            (
1353                quote! { {
1354                    use paperclip::v2::schema::TypedData;
1355                    Some(#ty_ref::data_type())
1356                } },
1357                quote! { {
1358                    use paperclip::v2::schema::TypedData;
1359                    #ty_ref::format()
1360                } },
1361            )
1362        } else {
1363            (quote! { None }, quote! { None })
1364        };
1365
1366        let (quoted_type, quoted_format) = if let Some(format) = parameter_attrs.get("format") {
1367            let quoted_format = quote_format(format);
1368            let quoted_type = quote! { #quoted_format.map(|format| format.into()) };
1369            (quoted_type, quoted_format)
1370        } else {
1371            (quoted_type, quoted_format)
1372        };
1373
1374        let quoted_max = quote_option_str_f32(field, parameter_attrs.get("maximum"));
1375        let quoted_min = quote_option_str_f32(field, parameter_attrs.get("minimum"));
1376
1377        let def_block = quote! {
1378            paperclip::v2::models::Parameter::<paperclip::v2::models::DefaultSchemaRaw> {
1379                name: #quoted_name.to_owned(),
1380                in_: paperclip::v2::models::ParameterIn::Header,
1381                description: #quoted_description,
1382                data_type: #quoted_type,
1383                format: #quoted_format,
1384                maximum: #quoted_max,
1385                minimum: #quoted_min,
1386                required: Self::required(),
1387                ..Default::default()
1388            }
1389        };
1390
1391        header_definitions.push(def_block);
1392    }
1393
1394    let gen = quote! {
1395        impl #impl_generics paperclip::v2::schema::Apiv2Schema for #name #ty_generics #where_clause {
1396            fn header_parameter_schema() -> Vec<paperclip::v2::models::Parameter<paperclip::v2::models::DefaultSchemaRaw>> {
1397                vec![
1398                    #(#header_definitions),*
1399                ]
1400            }
1401        }
1402
1403        #opt_impl
1404    };
1405
1406    gen.into()
1407}
1408
1409fn quote_option(value: Option<&String>) -> proc_macro2::TokenStream {
1410    if let Some(value) = value {
1411        quote! { Some(#value.to_string()) }
1412    } else {
1413        quote! { None }
1414    }
1415}
1416fn quote_option_str_f32(field: &Field, value: Option<&String>) -> proc_macro2::TokenStream {
1417    if let Some(x) = value {
1418        let x: f32 = match x.parse() {
1419            Ok(x) => x,
1420            Err(error) => {
1421                emit_error!(field.span(), error.to_string());
1422                0.0
1423            }
1424        };
1425        quote! { Some(#x) }
1426    } else {
1427        quote! { None }
1428    }
1429}
1430
1431#[cfg(feature = "nightly")]
1432fn add_optional_impl(_: &Ident, _: &Generics) -> proc_macro2::TokenStream {
1433    // Empty impl for "nightly" feature because specialization helps us there.
1434    quote!()
1435}
1436
1437#[cfg(not(feature = "nightly"))]
1438fn add_optional_impl(name: &Ident, generics: &Generics) -> proc_macro2::TokenStream {
1439    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1440    quote! {
1441        impl #impl_generics paperclip::actix::OperationModifier for #name #ty_generics #where_clause {}
1442    }
1443}
1444
1445fn get_field_type(field: &Field) -> Option<proc_macro2::TokenStream> {
1446    match field.ty {
1447        Type::Path(_) | Type::Reference(_) | Type::Array(_) => {
1448            Some(address_type_for_fn_call(&field.ty))
1449        }
1450        _ => {
1451            emit_warning!(
1452                field.ty.span().unwrap(),
1453                "unsupported field type will be ignored."
1454            );
1455            None
1456        }
1457    }
1458}
1459
1460/// Generates code for a tuple struct with fields.
1461fn handle_unnamed_field_struct(
1462    fields: &FieldsUnnamed,
1463    struct_attr: &[Attribute],
1464    props_gen: &mut proc_macro2::TokenStream,
1465) {
1466    if fields.unnamed.len() == 1 {
1467        let field = fields.unnamed.iter().next().unwrap();
1468
1469        if let Some(ty_ref) = get_field_type(field) {
1470            let docs = extract_documentation(struct_attr);
1471            let docs = docs.trim();
1472
1473            if SerdeSkip::exists(&field.attrs) {
1474                props_gen.extend(quote!({
1475                    let mut s: DefaultSchemaRaw = Default::default();
1476                    if !#docs.is_empty() {
1477                        s.description = Some(#docs.to_string());
1478                    }
1479                    schema = s;
1480                }));
1481            } else {
1482                props_gen.extend(quote!({
1483                    let mut s = #ty_ref::raw_schema();
1484                    if !#docs.is_empty() {
1485                        s.description = Some(#docs.to_string());
1486                    }
1487                    schema = s;
1488                }));
1489            }
1490        }
1491    } else {
1492        for (inner_field_id, field) in fields.unnamed.iter().enumerate() {
1493            if SerdeSkip::exists(&field.attrs) {
1494                continue;
1495            }
1496
1497            let ty_ref = match get_field_type(field) {
1498                Some(ty_ref) => ty_ref,
1499                None => continue,
1500            };
1501
1502            let docs = extract_documentation(&field.attrs);
1503            let docs = docs.trim();
1504
1505            let gen = if !SerdeFlatten::exists(&field.attrs) {
1506                // this is really not what we'd want to do because that's not how the
1507                // deserialized struct will be like, ideally we want an actual tuple
1508                // this type should therefore not be used for anything else than `Path`
1509                quote!({
1510                    let mut s = #ty_ref::raw_schema();
1511                    if !#docs.is_empty() {
1512                        s.description = Some(#docs.to_string());
1513                    }
1514                    schema.properties.insert(#inner_field_id.to_string(), s.into());
1515                    if #ty_ref::required() {
1516                        schema.required.insert(#inner_field_id.to_string());
1517                    }
1518                })
1519            } else {
1520                quote!({
1521                    let s = #ty_ref::raw_schema();
1522                    schema.properties.extend(s.properties);
1523
1524                    if #ty_ref::required() {
1525                        schema.required.extend(s.required);
1526                    }
1527                })
1528            };
1529
1530            props_gen.extend(gen);
1531        }
1532    }
1533}
1534
1535/// Checks for `api_v2_empty` attributes and removes them.
1536fn extract_openapi_attrs(
1537    field_attrs: &'_ [Attribute],
1538) -> impl Iterator<Item = Punctuated<syn::NestedMeta, syn::token::Comma>> + '_ {
1539    field_attrs.iter().filter_map(|a| match a.parse_meta() {
1540        Ok(Meta::List(list)) if list.path.is_ident(SCHEMA_MACRO_ATTR) => Some(list.nested),
1541        _ => None,
1542    })
1543}
1544
1545fn extract_deprecated(attrs: &[Attribute]) -> bool {
1546    attrs.iter().any(|a| match a.parse_meta() {
1547        Ok(Meta::Path(mp)) if mp.is_ident("deprecated") => true,
1548        Ok(Meta::List(mml)) => mml
1549            .path
1550            .segments
1551            .into_iter()
1552            .any(|p| p.ident == "deprecated"),
1553        _ => false,
1554    })
1555}
1556
1557/// Checks for `api_v2_empty` attributes and removes them.
1558fn extract_documentation(attrs: &[Attribute]) -> String {
1559    attrs
1560        .iter()
1561        .filter_map(|a| match a.parse_meta() {
1562            Ok(Meta::NameValue(mnv)) if mnv.path.is_ident("doc") => match &mnv.lit {
1563                Lit::Str(s) => Some(s.value()),
1564                _ => None,
1565            },
1566            _ => None,
1567        })
1568        .collect::<Vec<String>>()
1569        .join("\n")
1570}
1571
1572/// Checks if an empty schema has been requested and generate if needed.
1573fn check_empty_schema(item_ast: &DeriveInput) -> Option<TokenStream> {
1574    let needs_empty_schema = extract_openapi_attrs(&item_ast.attrs).any(|nested| {
1575        nested.len() == 1
1576            && match &nested[0] {
1577                NestedMeta::Meta(Meta::Path(path)) => path.is_ident("empty"),
1578                _ => false,
1579            }
1580    });
1581
1582    if needs_empty_schema {
1583        let name = &item_ast.ident;
1584        let generics = item_ast.generics.clone();
1585        let opt_impl = add_optional_impl(name, &generics);
1586        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1587        return Some(quote!(
1588            impl #impl_generics paperclip::v2::schema::Apiv2Schema for #name #ty_generics #where_clause {}
1589
1590            #opt_impl
1591        ).into());
1592    }
1593
1594    None
1595}
1596
1597/// Generates code for a struct with fields.
1598fn handle_field_struct(
1599    fields: &FieldsNamed,
1600    struct_attr: &[Attribute],
1601    serde: &SerdeProps,
1602    props_gen: &mut proc_macro2::TokenStream,
1603) {
1604    let docs = extract_documentation(struct_attr);
1605    let docs = docs.trim();
1606
1607    props_gen.extend(quote!({
1608        if !#docs.is_empty() {
1609            schema.description = Some(#docs.to_string());
1610        }
1611    }));
1612    for field in &fields.named {
1613        let mut field_name = field
1614            .ident
1615            .as_ref()
1616            .expect("missing field name?")
1617            .to_string();
1618
1619        //Strip r# prefix if any
1620        field_name = field_name
1621            .strip_prefix("r#")
1622            .map(|n| n.to_string())
1623            .unwrap_or(field_name);
1624
1625        if SerdeSkip::exists(&field.attrs) {
1626            continue;
1627        }
1628
1629        if let Some(renamed) = SerdeRename::from_field_attrs(&field.attrs) {
1630            field_name = renamed;
1631        } else if let Some(prop) = serde.rename {
1632            field_name = prop.rename(&field_name);
1633        }
1634
1635        let ty_ref = match get_field_type(field) {
1636            Some(ty_ref) => ty_ref,
1637            None => continue,
1638        };
1639
1640        let docs = extract_documentation(&field.attrs);
1641        let docs = docs.trim();
1642
1643        let example = if let Some(example) = extract_example(&field.attrs) {
1644            // allow to parse escaped json string or single str value
1645            quote!({
1646                s.example = paperclip::v2::serde_json::from_str::<paperclip::v2::serde_json::Value>(#example).ok().or_else(|| Some(#example.into()));
1647            })
1648        } else {
1649            quote!({})
1650        };
1651
1652        let max = if let Some(max) = extract_openapi_f32(&field.attrs, "maximum") {
1653            quote!({
1654                s.maximum = Some(#max);
1655            })
1656        } else {
1657            quote!({})
1658        };
1659        let min = if let Some(min) = extract_openapi_f32(&field.attrs, "minimum") {
1660            quote!({
1661                s.minimum = Some(#min);
1662            })
1663        } else {
1664            quote!({})
1665        };
1666
1667        let gen = if !SerdeFlatten::exists(&field.attrs) {
1668            quote!({
1669                let mut s = #ty_ref::raw_schema();
1670                if !#docs.is_empty() {
1671                    s.description = Some(#docs.to_string());
1672                }
1673                #example;
1674                #max;
1675                #min;
1676                schema.properties.insert(#field_name.into(), s.into());
1677
1678                if #ty_ref::required() {
1679                    schema.required.insert(#field_name.into());
1680                }
1681            })
1682        } else {
1683            quote!({
1684                let s = #ty_ref::raw_schema();
1685                schema.properties.extend(s.properties);
1686
1687                if #ty_ref::required() {
1688                    schema.required.extend(s.required);
1689                }
1690            })
1691        };
1692
1693        props_gen.extend(gen);
1694    }
1695}
1696
1697/// Generates code for an enum (if supported).
1698fn handle_enum(e: &DataEnum, serde: &SerdeProps, props_gen: &mut proc_macro2::TokenStream) {
1699    props_gen.extend(quote!(
1700        schema.data_type = Some(DataType::String);
1701    ));
1702
1703    for var in &e.variants {
1704        let mut name = var.ident.to_string();
1705        match &var.fields {
1706            Fields::Unit => (),
1707            Fields::Named(ref f) => {
1708                emit_warning!(
1709                    f.span().unwrap(),
1710                    "skipping enum variant with named fields in schema."
1711                );
1712                continue;
1713            }
1714            Fields::Unnamed(ref f) => {
1715                emit_warning!(f.span().unwrap(), "skipping tuple enum variant in schema.");
1716                continue;
1717            }
1718        }
1719
1720        if SerdeSkip::exists(&var.attrs) {
1721            continue;
1722        }
1723
1724        if let Some(renamed) = SerdeRename::from_field_attrs(&var.attrs) {
1725            name = renamed;
1726        } else if let Some(prop) = serde.rename {
1727            name = prop.rename(&name);
1728        }
1729
1730        props_gen.extend(quote!(
1731            schema.enum_.push(paperclip::v2::serde_json::json!(#name));
1732        ));
1733    }
1734}
1735
1736/// An associated function of a generic type, say, a vector cannot be called
1737/// like `Vec::foo` as it doesn't have a default type. We should instead call
1738/// `Vec::<T>::foo`. Something similar applies to `str`. This function takes
1739/// care of that special treatment.
1740fn address_type_for_fn_call(old_ty: &Type) -> proc_macro2::TokenStream {
1741    if matches!(old_ty, Type::Reference(_) | Type::Array(_)) {
1742        return quote!(<(#old_ty)>);
1743    }
1744
1745    let mut ty = old_ty.clone();
1746    if let Type::Path(ref mut p) = &mut ty {
1747        p.path.segments.pairs_mut().for_each(|mut pair| {
1748            let is_empty = pair.value().arguments.is_empty();
1749            let args = &mut pair.value_mut().arguments;
1750            match args {
1751                PathArguments::AngleBracketed(ref mut brack_args) if !is_empty => {
1752                    brack_args.colon2_token = Some(Token![::](proc_macro2::Span::call_site()));
1753                }
1754                _ => (),
1755            }
1756        });
1757    }
1758
1759    quote!(#ty)
1760}
1761
1762/* Serde attributes */
1763
1764/// Supported renaming options in serde (https://serde.rs/variant-attrs.html).
1765#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumString)]
1766enum SerdeRename {
1767    #[strum(serialize = "lowercase")]
1768    Lower,
1769    #[strum(serialize = "UPPERCASE")]
1770    Upper,
1771    #[strum(serialize = "PascalCase")]
1772    Pascal,
1773    #[strum(serialize = "camelCase")]
1774    Camel,
1775    #[strum(serialize = "snake_case")]
1776    Snake,
1777    #[strum(serialize = "SCREAMING_SNAKE_CASE")]
1778    ScreamingSnake,
1779    #[strum(serialize = "kebab-case")]
1780    Kebab,
1781    #[strum(serialize = "SCREAMING-KEBAB-CASE")]
1782    ScreamingKebab,
1783}
1784
1785impl SerdeRename {
1786    /// Traverses the field attributes and returns the renamed value from the first matching
1787    /// `#[serde(rename = "...")]` pattern.
1788    fn from_field_attrs(field_attrs: &[Attribute]) -> Option<String> {
1789        for meta in field_attrs.iter().filter_map(|a| a.parse_meta().ok()) {
1790            let inner_meta = match meta {
1791                Meta::List(ref l)
1792                    if l.path
1793                        .segments
1794                        .last()
1795                        .map(|p| p.ident == "serde")
1796                        .unwrap_or(false) =>
1797                {
1798                    &l.nested
1799                }
1800                _ => continue,
1801            };
1802
1803            for meta in inner_meta {
1804                let rename = match meta {
1805                    NestedMeta::Meta(Meta::NameValue(ref v))
1806                        if v.path
1807                            .segments
1808                            .last()
1809                            .map(|p| p.ident == "rename")
1810                            .unwrap_or(false) =>
1811                    {
1812                        &v.lit
1813                    }
1814                    _ => continue,
1815                };
1816
1817                if let Lit::Str(ref s) = rename {
1818                    return Some(s.value());
1819                }
1820            }
1821        }
1822
1823        None
1824    }
1825
1826    /// Renames the given value using the current option.
1827    fn rename(self, name: &str) -> String {
1828        match self {
1829            SerdeRename::Lower => name.to_lowercase(),
1830            SerdeRename::Upper => name.to_uppercase(),
1831            SerdeRename::Pascal => name.to_pascal_case(),
1832            SerdeRename::Camel => name.to_lower_camel_case(),
1833            SerdeRename::Snake => name.to_snake_case(),
1834            SerdeRename::ScreamingSnake => name.to_shouty_snake_case(),
1835            SerdeRename::Kebab => name.to_kebab_case(),
1836            SerdeRename::ScreamingKebab => name.to_shouty_kebab_case(),
1837        }
1838    }
1839}
1840
1841/// Serde skip (https://serde.rs/variant-attrs.html)
1842/// Never serialize or deserialize this variant.
1843/// There are other variants available (skip_serializing,skip_deserializing) though it's not clear
1844/// how this should be handled since we use the same Schema for Ser/DeSer
1845struct SerdeSkip;
1846
1847impl SerdeSkip {
1848    /// Traverses the field attributes and returns whether the field should be skipped or not
1849    /// dependent on finding the `#[serde(skip]` attribute.
1850    fn exists(field_attrs: &[Attribute]) -> bool {
1851        for meta in field_attrs.iter().filter_map(|a| a.parse_meta().ok()) {
1852            let inner_meta = match meta {
1853                Meta::List(ref l)
1854                    if l.path
1855                        .segments
1856                        .last()
1857                        .map(|p| p.ident == "serde")
1858                        .unwrap_or(false) =>
1859                {
1860                    &l.nested
1861                }
1862                _ => continue,
1863            };
1864            for meta in inner_meta {
1865                if let NestedMeta::Meta(Meta::Path(path)) = meta {
1866                    if path.segments.iter().any(|s| s.ident == "skip") {
1867                        return true;
1868                    }
1869                }
1870            }
1871        }
1872
1873        false
1874    }
1875}
1876
1877#[derive(Clone, Debug, Default)]
1878struct SerdeProps {
1879    rename: Option<SerdeRename>,
1880}
1881
1882impl SerdeProps {
1883    /// Traverses the serde attributes in the given item attributes and returns
1884    /// the applicable properties.
1885    fn from_item_attrs(item_attrs: &[Attribute]) -> Self {
1886        let mut props = Self::default();
1887        for meta in item_attrs.iter().filter_map(|a| a.parse_meta().ok()) {
1888            let inner_meta = match meta {
1889                Meta::List(ref l)
1890                    if l.path
1891                        .segments
1892                        .last()
1893                        .map(|p| p.ident == "serde")
1894                        .unwrap_or(false) =>
1895                {
1896                    &l.nested
1897                }
1898                _ => continue,
1899            };
1900
1901            for meta in inner_meta {
1902                let global_rename = match meta {
1903                    NestedMeta::Meta(Meta::NameValue(ref v))
1904                        if v.path
1905                            .segments
1906                            .last()
1907                            .map(|p| p.ident == "rename_all")
1908                            .unwrap_or(false) =>
1909                    {
1910                        &v.lit
1911                    }
1912                    _ => continue,
1913                };
1914
1915                if let Lit::Str(ref s) = global_rename {
1916                    props.rename = s.value().parse().ok();
1917                }
1918            }
1919        }
1920
1921        props
1922    }
1923}
1924
1925/// Supported flattening of embedded struct (https://serde.rs/variant-attrs.html).
1926struct SerdeFlatten;
1927
1928impl SerdeFlatten {
1929    /// Traverses the field attributes and returns true if there is `#[serde(flatten)]`.
1930    fn exists(field_attrs: &[Attribute]) -> bool {
1931        for meta in field_attrs.iter().filter_map(|a| a.parse_meta().ok()) {
1932            let inner_meta = match meta {
1933                Meta::List(ref l)
1934                    if l.path
1935                        .segments
1936                        .last()
1937                        .map(|p| p.ident == "serde")
1938                        .unwrap_or(false) =>
1939                {
1940                    &l.nested
1941                }
1942                _ => continue,
1943            };
1944
1945            for meta in inner_meta {
1946                if let NestedMeta::Meta(Meta::Path(syn::Path { segments, .. })) = meta {
1947                    if segments.iter().any(|p| p.ident == "flatten") {
1948                        return true;
1949                    }
1950                }
1951            }
1952        }
1953
1954        false
1955    }
1956}
1957
1958macro_rules! doc_comment {
1959    ($x:expr; $($tt:tt)*) => {
1960        #[doc = $x]
1961        $($tt)*
1962    };
1963}
1964
1965#[cfg(feature = "actix")]
1966impl super::Method {
1967    fn handler_uri(attr: TokenStream) -> TokenStream {
1968        let attr = parse_macro_input!(attr as syn::AttributeArgs);
1969        attr.first().into_token_stream().into()
1970    }
1971    fn handler_name(item: TokenStream) -> syn::Result<syn::Ident> {
1972        let handler: ItemFn = syn::parse(item)?;
1973        Ok(handler.sig.ident)
1974    }
1975    pub(crate) fn generate(
1976        &self,
1977        attr: TokenStream,
1978        item: TokenStream,
1979    ) -> syn::Result<proc_macro2::TokenStream> {
1980        let uri: proc_macro2::TokenStream = Self::handler_uri(attr).into();
1981        let handler_name = Self::handler_name(item.clone())?;
1982        let handler_fn: proc_macro2::TokenStream = item.into();
1983        let method: proc_macro2::TokenStream = self.method().parse()?;
1984        let variant: proc_macro2::TokenStream = self.variant().parse()?;
1985        let handler_name_str = handler_name.to_string();
1986
1987        let uri = uri.to_string().replace('\"', ""); // The uri is a string lit, which contains quotes, remove them
1988
1989        let uri_fmt = if !uri.starts_with('/') {
1990            format!("/{}", uri)
1991        } else {
1992            uri
1993        };
1994
1995        Ok(quote! {
1996            #[allow(non_camel_case_types, missing_docs)]
1997            pub struct #handler_name;
1998
1999            impl #handler_name {
2000                fn resource() -> paperclip::actix::web::Resource {
2001                    #handler_fn
2002                    paperclip::actix::web::Resource::new(#uri_fmt)
2003                        .name(#handler_name_str)
2004                        .guard(actix_web::guard::#variant())
2005                        .route(paperclip::actix::web::#method().to(#handler_name))
2006                }
2007            }
2008
2009            impl actix_web::dev::HttpServiceFactory for #handler_name {
2010                fn register(self, config: &mut actix_web::dev::AppService) {
2011                    Self::resource().register(config);
2012                }
2013            }
2014
2015            impl paperclip::actix::Mountable for #handler_name {
2016                fn path(&self) -> &str {
2017                    #uri_fmt
2018                }
2019
2020                fn operations(
2021                    &mut self,
2022                ) -> std::collections::BTreeMap<
2023                    paperclip::v2::models::HttpMethod,
2024                    paperclip::v2::models::DefaultOperationRaw,
2025                > {
2026                    Self::resource().operations()
2027                }
2028
2029                fn definitions(
2030                    &mut self,
2031                ) -> std::collections::BTreeMap<
2032                    String,
2033                    paperclip::v2::models::DefaultSchemaRaw,
2034                > {
2035                    Self::resource().definitions()
2036                }
2037
2038                fn security_definitions(
2039                    &mut self,
2040                ) -> std::collections::BTreeMap<String, paperclip::v2::models::SecurityScheme>
2041                {
2042                    Self::resource().security_definitions()
2043                }
2044            }
2045        })
2046    }
2047}
2048
2049macro_rules! rest_methods {
2050    (
2051        $($variant:ident, $method:ident, )+
2052    ) => {
2053        /// All available Rest methods
2054        #[derive(Debug, PartialEq, Eq, Hash)]
2055        pub(crate) enum Method {
2056            $(
2057                $variant,
2058            )+
2059        }
2060
2061        impl Method {
2062            fn method(&self) -> &'static str {
2063                match self {
2064                    $(Self::$variant => stringify!($method),)+
2065                }
2066            }
2067            fn variant(&self) -> &'static str {
2068                match self {
2069                    $(Self::$variant => stringify!($variant),)+
2070                }
2071            }
2072        }
2073
2074        $(doc_comment! {
2075            concat!("
2076Creates route handler with `paperclip::actix::web::Resource", "`.
2077In order to control the output type and status codes the return value/response must implement the
2078trait actix_web::Responder.
2079
2080# Syntax
2081```text
2082#[", stringify!($method), r#"("path"[, attributes])]
2083```
2084
2085# Attributes
2086- `"path"` - Raw literal string with path for which to register handler.
2087
2088# Example
2089
2090/// use paperclip::actix::web::Json;
2091/// use paperclip_macros::"#, stringify!($method), ";
2092/// #[", stringify!($method), r#"("/")]
2093/// async fn example() {
2094/// }
2095"#);
2096            #[cfg(feature = "actix")]
2097            #[proc_macro_attribute]
2098            pub fn $method(attr: TokenStream, item: TokenStream) -> TokenStream {
2099                match Method::$variant.generate(attr, item) {
2100                    Ok(v) => v.into(),
2101                    Err(e) => e.to_compile_error().into(),
2102                }
2103            }
2104        })+
2105    };
2106}