Skip to main content

salvo_oapi_macros/
lib.rs

1//! Procedural macros used by `salvo_oapi`.
2//!
3//! This crate contains the implementation of the `#[endpoint]`,
4//! `#[derive(ToSchema)]`, `#[derive(ToParameters)]`,
5//! `#[derive(ToResponse)]`, and `#[derive(ToResponses)]` macros. Most users
6//! should import these macros from `salvo_oapi` or from `salvo` with the
7//! `oapi` feature enabled so the generated paths and documentation links line
8//! up with the public API.
9
10#![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")]
11#![doc(html_logo_url = "https://salvo.rs/images/logo.svg")]
12#![cfg_attr(docsrs, feature(doc_cfg))]
13#![cfg_attr(test, allow(clippy::unwrap_used))]
14
15use proc_macro::TokenStream;
16use quote::ToTokens;
17use syn::parse::{Parse, ParseStream};
18use syn::token::Bracket;
19use syn::{Ident, Item, Token, bracketed, parse_macro_input};
20
21mod attribute;
22pub(crate) mod bound;
23mod component;
24mod diagnostic;
25mod doc_comment;
26mod endpoint;
27pub(crate) mod feature;
28mod operation;
29mod parameter;
30pub(crate) mod parse_utils;
31mod response;
32mod schema;
33mod schema_type;
34mod security_requirement;
35mod server;
36mod shared;
37mod type_tree;
38
39pub(crate) use salvo_serde_util::{self as serde_util, RenameRule, SerdeContainer, SerdeValue};
40
41pub(crate) use self::component::{ComponentSchema, ComponentSchemaProps};
42pub(crate) use self::diagnostic::{Diagnostic, Level as DiagLevel};
43pub(crate) use self::endpoint::EndpointAttr;
44pub(crate) use self::feature::Feature;
45pub(crate) use self::operation::Operation;
46pub(crate) use self::parameter::Parameter;
47pub(crate) use self::response::Response;
48pub(crate) use self::server::Server;
49pub(crate) use self::shared::*;
50pub(crate) use self::type_tree::TypeTree;
51
52/// Turns a Salvo handler function into an OpenAPI-aware endpoint.
53///
54/// `#[endpoint]` behaves like Salvo's `#[handler]` macro and also records
55/// OpenAPI operation metadata. The generated endpoint can be registered on a
56/// router and later collected with `OpenApi::merge_router`.
57///
58/// Function doc comments are used for generated operation text: the first
59/// paragraph becomes the summary and the remaining paragraphs become the
60/// description.
61///
62/// Common attributes:
63///
64/// - `operation_id = ...`
65/// - `tags(...)`
66/// - `parameters(...)`
67/// - `request_body = ...` or `request_body(...)`
68/// - `responses(...)`
69/// - `status_codes(...)`
70/// - `security(...)`
71///
72/// Rust's own `#[deprecated]` attribute is reflected as OpenAPI deprecation.
73///
74/// # Example
75///
76/// ```rust,ignore
77/// use salvo_oapi::endpoint;
78///
79/// /// Fetch one user.
80/// ///
81/// /// Returns `404` when the user does not exist.
82/// #[endpoint(tags("users"), responses((status_code = 200, body = String)))]
83/// async fn get_user() -> String {
84///     "Alice".to_owned()
85/// }
86/// ```
87///
88/// Full attribute reference:
89/// <https://docs.rs/salvo-oapi/latest/salvo_oapi/attr.endpoint.html>
90#[proc_macro_attribute]
91pub fn endpoint(attr: TokenStream, input: TokenStream) -> TokenStream {
92    let attr = syn::parse_macro_input!(attr as EndpointAttr);
93    let item = parse_macro_input!(input as Item);
94    match endpoint::generate(attr, item) {
95        Ok(stream) => stream.into(),
96        Err(e) => e.to_compile_error().into(),
97    }
98}
99
100/// Derives `salvo_oapi::ToSchema` for a Rust type.
101///
102/// The generated implementation produces an OpenAPI schema for structs and
103/// enums. Type and field doc comments become schema descriptions unless they
104/// are overridden with `#[salvo(schema(...))]`.
105///
106/// Common `#[salvo(schema(...))]` options include:
107///
108/// - `description = ...`
109/// - `example = json!(...)`
110/// - `default` or `default = ...`
111/// - `rename_all = "..."`
112/// - `rename = "..."`
113/// - `value_type = ...`
114/// - `format = ...`
115/// - `inline`
116/// - `required = ...`
117/// - `nullable`
118/// - `skip`
119///
120/// The derive also reflects supported serde rename/default/skip attributes and
121/// Rust's `#[deprecated]` attribute into the generated OpenAPI schema.
122///
123/// Full attribute reference:
124/// <https://docs.rs/salvo-oapi/latest/salvo_oapi/derive.ToSchema.html>
125#[proc_macro_derive(ToSchema, attributes(salvo))] //attributes(schema)
126pub fn derive_to_schema(input: TokenStream) -> TokenStream {
127    match schema::to_schema(syn::parse_macro_input!(input)) {
128        Ok(stream) => stream.into(),
129        Err(e) => e.emit_as_item_tokens().into(),
130    }
131}
132
133/// Derives `salvo_oapi::ToParameters` for a parameter struct.
134///
135/// The generated implementation converts fields into OpenAPI parameters,
136/// usually for query, path, header, or cookie data used by an endpoint.
137/// Field doc comments become parameter descriptions.
138///
139/// Container attributes use `#[salvo(parameters(...))]` and include:
140///
141/// - `names(...)` for unnamed tuple fields.
142/// - `style = ...`
143/// - `default_parameter_in = ...`
144/// - `rename_all = "..."`
145///
146/// Field attributes use `#[salvo(parameter(...))]` and include:
147///
148/// - `parameter_in = ...`
149/// - `style = ...`
150/// - `explode`
151/// - `allow_reserved`
152/// - `example = ...`
153/// - `value_type = ...`
154/// - `inline`
155/// - `required = ...`
156/// - `nullable`
157/// - `rename = "..."`
158///
159/// The derive also reflects supported serde rename/default/skip attributes and
160/// Rust's `#[deprecated]` attribute into the generated parameters.
161///
162/// Full attribute reference:
163/// <https://docs.rs/salvo-oapi/latest/salvo_oapi/derive.ToParameters.html>
164#[proc_macro_derive(ToParameters, attributes(salvo))] //attributes(parameter, parameters)
165pub fn derive_to_parameters(input: TokenStream) -> TokenStream {
166    match parameter::to_parameters(syn::parse_macro_input!(input)) {
167        Ok(stream) => stream.into(),
168        Err(e) => e.emit_as_item_tokens().into(),
169    }
170}
171
172/// Derives `salvo_oapi::ToResponse` for one reusable OpenAPI response.
173///
174/// Use this derive when a type should describe a single response component
175/// that can be referenced from endpoint `responses(...)` attributes. Struct
176/// and enum doc comments become the response description unless overridden.
177///
178/// Common `#[salvo(response(...))]` options include:
179///
180/// - `description = "..."`
181/// - `content_type = "..."`
182/// - `headers(...)`
183/// - `example = json!(...)`
184/// - `examples(...)`
185///
186/// Enum variants can use `#[salvo(content(...))]` to describe alternate
187/// response content types. `#[salvo(schema(...))]` can inline a schema for
188/// unnamed fields or content variants whose type implements `ToSchema`.
189///
190/// Full attribute reference:
191/// <https://docs.rs/salvo-oapi/latest/salvo_oapi/derive.ToResponse.html>
192#[proc_macro_derive(ToResponse, attributes(salvo))] //attributes(response, content, schema))
193pub fn derive_to_response(input: TokenStream) -> TokenStream {
194    match response::to_response(syn::parse_macro_input!(input)) {
195        Ok(stream) => stream.into(),
196        Err(e) => e.emit_as_item_tokens().into(),
197    }
198}
199
200/// Derives `salvo_oapi::ToResponses` for a response map.
201///
202/// Use this derive when a type should describe all responses for an endpoint.
203/// A struct produces one response entry; an enum produces one response entry
204/// per variant. The generated map can be used directly in
205/// `#[endpoint(responses(...))]`.
206///
207/// `#[salvo(response(status_code = ...))]` is the central attribute and is
208/// required on most response structs or enum variants. It also accepts the same
209/// response metadata options as `ToResponse`, including `description`,
210/// `content_type`, `headers`, `example`, and `examples`.
211///
212/// Unnamed response fields are represented as schemas by default; use
213/// `#[salvo(response(inline))]` on a field to inline that schema.
214///
215/// Full attribute reference:
216/// <https://docs.rs/salvo-oapi/latest/salvo_oapi/derive.ToResponses.html>
217#[proc_macro_derive(ToResponses, attributes(salvo))] //attributes(response, schema, ref_response, response))
218pub fn to_responses(input: TokenStream) -> TokenStream {
219    match response::to_responses(syn::parse_macro_input!(input)) {
220        Ok(stream) => stream.into(),
221        Err(e) => e.emit_as_item_tokens().into(),
222    }
223}
224
225#[doc(hidden)]
226#[proc_macro]
227pub fn schema(input: TokenStream) -> TokenStream {
228    struct Schema {
229        inline: bool,
230        ty: syn::Type,
231    }
232    impl Parse for Schema {
233        fn parse(input: ParseStream) -> syn::Result<Self> {
234            let inline = if input.peek(Token![#]) && input.peek2(Bracket) {
235                input.parse::<Token![#]>()?;
236
237                let inline;
238                bracketed!(inline in input);
239                let i = inline.parse::<Ident>()?;
240                i == "inline"
241            } else {
242                false
243            };
244
245            let ty = input.parse()?;
246            Ok(Self { inline, ty })
247        }
248    }
249
250    let schema = syn::parse_macro_input!(input as Schema);
251    let type_tree = match TypeTree::from_type(&schema.ty) {
252        Ok(type_tree) => type_tree,
253        Err(diag) => return diag.emit_as_item_tokens().into(),
254    };
255
256    let stream = ComponentSchema::new(ComponentSchemaProps {
257        features: Some(vec![Feature::Inline(schema.inline.into())]),
258        type_tree: &type_tree,
259        deprecated: None,
260        description: None,
261        object_name: "",
262        compose_context: None,
263    })
264    .map(|s| s.to_token_stream());
265    match stream {
266        Ok(stream) => stream.into(),
267        Err(diag) => diag.emit_as_item_tokens().into(),
268    }
269}
270
271pub(crate) trait IntoInner<T> {
272    fn into_inner(self) -> T;
273}
274
275#[cfg(test)]
276mod tests {
277    use quote::quote;
278    use syn::parse2;
279
280    use super::*;
281
282    #[test]
283    fn test_endpoint_for_fn() {
284        let input = quote! {
285            #[endpoint]
286            async fn hello() {
287                res.render_plain_text("Hello World");
288            }
289        };
290        let item = parse2(input).unwrap();
291        assert_eq!(
292            endpoint::generate(parse2(quote! {}).unwrap(), item)
293                .unwrap()
294                .to_string(),
295            quote! {
296                #[allow(non_camel_case_types)]
297                #[derive(Debug)]
298                struct hello;
299                impl hello {
300                    async fn hello() {
301                        {res.render_plain_text("Hello World");}
302                    }
303                }
304                #[salvo::async_trait]
305                impl salvo::Handler for hello {
306                    async fn handle(
307                        &self,
308                        __macro_gen_req: &mut salvo::Request,
309                        __macro_gen_depot: &mut salvo::Depot,
310                        __macro_gen_res: &mut salvo::Response,
311                        __macro_gen_ctrl: &mut salvo::FlowCtrl
312                    ) {
313                        Self::hello().await
314                    }
315                }
316                fn __macro_gen_oapi_endpoint_type_id_hello() -> ::std::any::TypeId {
317                    ::std::any::TypeId::of::<hello>()
318                }
319                fn __macro_gen_oapi_endpoint_creator_hello() -> salvo::oapi::Endpoint {
320                    let mut components = salvo::oapi::Components::new();
321                    let status_codes: &[salvo::http::StatusCode] = &[];
322                    let mut operation = salvo::oapi::Operation::new();
323                    if operation.operation_id.is_none() {
324                        operation.operation_id = Some(salvo::oapi::naming::assign_name::<hello>(salvo::oapi::naming::NameRule::Auto));
325                    }
326                    if !status_codes.is_empty() {
327                        let responses = std::ops::DerefMut::deref_mut(&mut operation.responses);
328                        responses.retain(|k, _| {
329                            if let Ok(code) = <salvo::http::StatusCode as std::str::FromStr>::from_str(k) {
330                                status_codes.contains(&code)
331                            } else {
332                                true
333                            }
334                        });
335                    }
336                    salvo::oapi::Endpoint {
337                        operation,
338                        components,
339                    }
340                }
341                salvo::oapi::__private::inventory::submit! {
342                    salvo::oapi::EndpointRegistry::save(__macro_gen_oapi_endpoint_type_id_hello, __macro_gen_oapi_endpoint_creator_hello)
343                }
344            }
345            .to_string()
346        );
347    }
348
349    #[test]
350    fn test_to_schema_struct() {
351        let input = quote! {
352            /// This is user.
353            ///
354            /// This is user description.
355            #[derive(ToSchema)]
356            struct User {
357                #[salvo(schema(examples("chris"), min_length = 1, max_length = 100, required))]
358                name: String,
359                #[salvo(schema(example = 16, default = 0, maximum=100, minimum=0,format = "int32"))]
360                age: i32,
361                #[deprecated = "There is deprecated"]
362                high: u32,
363            }
364        };
365        let result = schema::to_schema(parse2(input).unwrap())
366            .unwrap()
367            .to_string();
368        // Should contain both ComposeSchema and ToSchema impls
369        assert!(
370            result.contains("impl salvo :: oapi :: ComposeSchema for User"),
371            "Expected ComposeSchema impl in output"
372        );
373        assert!(
374            result.contains("impl salvo :: oapi :: ToSchema for User"),
375            "Expected ToSchema impl in output"
376        );
377        // Verify schema body content
378        assert!(result.contains("\"name\""), "Expected 'name' property");
379        assert!(result.contains("\"age\""), "Expected 'age' property");
380        assert!(result.contains("\"high\""), "Expected 'high' property");
381        assert!(
382            result.contains("This is user.\\n\\nThis is user description."),
383            "Expected description"
384        );
385    }
386
387    #[test]
388    fn test_to_schema_generics() {
389        let input = quote! {
390            #[derive(Serialize, Deserialize, ToSchema, Debug)]
391            #[salvo(schema(aliases(MyI32 = MyObject<i32>, MyStr = MyObject<String>)))]
392            struct MyObject<T: ToSchema + std::fmt::Debug + 'static> {
393                value: T,
394            }
395        };
396        let result = schema::to_schema(parse2(input).unwrap())
397            .unwrap()
398            .to_string()
399            .replace("< ", "<")
400            .replace("> ", ">");
401        // Should contain both ComposeSchema and ToSchema impls
402        assert!(
403            result.contains("salvo :: oapi :: ComposeSchema for MyObject"),
404            "Expected ComposeSchema impl in output"
405        );
406        assert!(
407            result.contains("salvo :: oapi :: ToSchema for MyObject"),
408            "Expected ToSchema impl in output"
409        );
410        // ComposeSchema should use __compose_generics for generic param T
411        assert!(
412            result.contains("__compose_generics"),
413            "Expected __compose_generics usage in ComposeSchema impl"
414        );
415        // ToSchema should still use ToSchema::to_schema for type aliases
416        assert!(result.contains("MyI32"), "Expected MyI32 alias");
417        assert!(result.contains("MyStr"), "Expected MyStr alias");
418    }
419
420    #[test]
421    fn test_to_schema_enum() {
422        let input = quote! {
423            #[derive(Serialize, Deserialize, ToSchema, Debug)]
424            #[salvo(schema(rename_all = "camelCase"))]
425            enum People {
426                Man,
427                Woman,
428            }
429        };
430        let result = schema::to_schema(parse2(input).unwrap())
431            .unwrap()
432            .to_string();
433        // Should contain both ComposeSchema and ToSchema impls
434        assert!(
435            result.contains("impl salvo :: oapi :: ComposeSchema for People"),
436            "Expected ComposeSchema impl in output"
437        );
438        assert!(
439            result.contains("impl salvo :: oapi :: ToSchema for People"),
440            "Expected ToSchema impl in output"
441        );
442        // Verify enum values
443        assert!(result.contains("\"man\""), "Expected 'man' variant");
444        assert!(result.contains("\"woman\""), "Expected 'woman' variant");
445    }
446
447    #[test]
448    fn test_to_response() {
449        let input = quote! {
450            #[derive(ToResponse)]
451            #[salvo(response(description = "Person response returns single Person entity"))]
452            struct User{
453                name: String,
454                age: i32,
455            }
456        };
457        assert_eq!(
458            response::to_response(parse2(input).unwrap()).unwrap()
459                .to_string(),
460            quote! {
461                impl salvo::oapi::ToResponse for User {
462                    fn to_response(
463                        components: &mut salvo::oapi::Components
464                    ) -> salvo::oapi::RefOr<salvo::oapi::Response> {
465                        let response = salvo::oapi::Response::new("Person response returns single Person entity").add_content(
466                            "application/json",
467                            salvo::oapi::Content::new(
468                                salvo::oapi::Object::new()
469                                    .property(
470                                        "name",
471                                        salvo::oapi::Object::new().schema_type(salvo::oapi::schema::SchemaType::basic(salvo::oapi::schema::BasicType::String))
472                                    )
473                                    .required("name")
474                                    .property(
475                                        "age",
476                                        salvo::oapi::Object::new()
477                                            .schema_type(salvo::oapi::schema::SchemaType::basic(salvo::oapi::schema::BasicType::Integer))
478                                            .format(salvo::oapi::SchemaFormat::KnownFormat(
479                                                salvo::oapi::KnownFormat::Int32
480                                            ))
481                                    )
482                                    .required("age")
483                            )
484                        );
485                        components.responses.insert("User", response);
486                        salvo::oapi::RefOr::Ref(salvo::oapi::Ref::new(format!("#/components/responses/{}", "User")))
487                    }
488                }
489                impl salvo::oapi::EndpointOutRegister for User {
490                    fn register(components: &mut salvo::oapi::Components, operation: &mut salvo::oapi::Operation) {
491                        operation
492                            .responses
493                            .insert("200", <Self as salvo::oapi::ToResponse>::to_response(components))
494                    }
495                }
496            } .to_string()
497        );
498    }
499
500    #[test]
501    fn test_to_responses() {
502        let input = quote! {
503            #[derive(salvo_oapi::ToResponses)]
504            enum UserResponses {
505                /// Success response description.
506                #[salvo(response(status_code = 200))]
507                Success { value: String },
508
509                #[salvo(response(status_code = 404))]
510                NotFound,
511
512                #[salvo(response(status_code = 400))]
513                BadRequest(BadRequest),
514
515                #[salvo(response(status_code = 500))]
516                ServerError(Response),
517
518                #[salvo(response(status_code = 418))]
519                TeaPot(Response),
520            }
521        };
522        assert_eq!(
523            response::to_responses(parse2(input).unwrap()).unwrap().to_string(),
524            quote! {
525                impl salvo::oapi::ToResponses for UserResponses {
526                    fn to_responses(components: &mut salvo::oapi::Components) -> salvo::oapi::response::Responses {
527                        [
528                            (
529                                "200",
530                                salvo::oapi::RefOr::from(
531                                    salvo::oapi::Response::new("Success response description.").add_content(
532                                        "application/json",
533                                        salvo::oapi::Content::new(
534                                            salvo::oapi::Object::new()
535                                                .property(
536                                                    "value",
537                                                    salvo::oapi::Object::new().schema_type(salvo::oapi::schema::SchemaType::basic(salvo::oapi::schema::BasicType::String))
538                                                )
539                                                .required("value")
540                                                .description("Success response description.")
541                                        )
542                                    )
543                                )
544                            ),
545                            (
546                                "404",
547                                salvo::oapi::RefOr::from(salvo::oapi::Response::new(""))
548                            ),
549                            (
550                                "400",
551                                salvo::oapi::RefOr::from(salvo::oapi::Response::new("").add_content(
552                                    "application/json",
553                                    salvo::oapi::Content::new(salvo::oapi::RefOr::from(
554                                        <BadRequest as salvo::oapi::ToSchema>::to_schema(components)
555                                    ))
556                                ))
557                            ),
558                            (
559                                "500",
560                                salvo::oapi::RefOr::from(salvo::oapi::Response::new("").add_content(
561                                    "application/json",
562                                    salvo::oapi::Content::new(salvo::oapi::RefOr::from(
563                                        <Response as salvo::oapi::ToSchema>::to_schema(components)
564                                    ))
565                                ))
566                            ),
567                            (
568                                "418",
569                                salvo::oapi::RefOr::from(salvo::oapi::Response::new("").add_content(
570                                    "application/json",
571                                    salvo::oapi::Content::new(salvo::oapi::RefOr::from(
572                                        <Response as salvo::oapi::ToSchema>::to_schema(components)
573                                    ))
574                                ))
575                            ),
576                        ]
577                        .into()
578                    }
579                }
580                impl salvo::oapi::EndpointOutRegister for UserResponses {
581                    fn register(components: &mut salvo::oapi::Components, operation: &mut salvo::oapi::Operation) {
582                        operation
583                            .responses
584                            .append(&mut <Self as salvo::oapi::ToResponses>::to_responses(components));
585                    }
586                }
587            }
588            .to_string()
589        );
590    }
591
592    #[test]
593    fn test_to_parameters() {
594        let input = quote! {
595            #[derive(Deserialize, ToParameters)]
596            struct PetQuery {
597                /// Name of pet
598                name: Option<String>,
599                /// Age of pet
600                age: Option<i32>,
601                /// Kind of pet
602                #[salvo(parameter(inline))]
603                kind: PetKind
604            }
605        };
606        assert_eq!(
607            parameter::to_parameters(parse2(input).unwrap()).unwrap().to_string(),
608            quote! {
609                impl<'__macro_gen_ex> salvo::oapi::ToParameters<'__macro_gen_ex> for PetQuery {
610                    fn to_parameters(components: &mut salvo::oapi::Components) -> salvo::oapi::Parameters {
611                        salvo::oapi::Parameters(
612                            [
613                                salvo::oapi::parameter::Parameter::new("name")
614                                    .location(salvo::oapi::parameter::ParameterIn::Query)
615                                    .description("Name of pet")
616                                    .required(salvo::oapi::Required::False)
617                                    .schema(
618                                        salvo::oapi::Object::new()
619                                            .schema_type(salvo::oapi::schema::SchemaType::basic(salvo::oapi::schema::BasicType::String))
620                                    ),
621                                salvo::oapi::parameter::Parameter::new("age")
622                                    .location(salvo::oapi::parameter::ParameterIn::Query)
623                                    .description("Age of pet")
624                                    .required(salvo::oapi::Required::False)
625                                    .schema(
626                                        salvo::oapi::Object::new()
627                                            .schema_type(salvo::oapi::schema::SchemaType::basic(salvo::oapi::schema::BasicType::Integer))
628                                            .format(salvo::oapi::SchemaFormat::KnownFormat(
629                                                salvo::oapi::KnownFormat::Int32
630                                            ))
631                                    ),
632                                salvo::oapi::parameter::Parameter::new("kind")
633                                    .location(salvo::oapi::parameter::ParameterIn::Query)
634                                    .description("Kind of pet")
635                                    .required(salvo::oapi::Required::True)
636                                    .schema(<PetKind as salvo::oapi::ComposeSchema>::compose(components, ::std::vec::Vec::new())),
637                            ]
638                            .to_vec()
639                        )
640                    }
641                }
642                impl salvo::oapi::EndpointArgRegister for PetQuery {
643                    fn register(
644                        components: &mut salvo::oapi::Components,
645                        operation: &mut salvo::oapi::Operation,
646                        _arg: &str
647                    ) {
648                        for parameter in <Self as salvo::oapi::ToParameters>::to_parameters(components) {
649                            operation.parameters.insert(parameter);
650                        }
651                    }
652                }
653                impl<'__macro_gen_ex> salvo::Extractible<'__macro_gen_ex> for PetQuery {
654                    fn metadata() -> &'static salvo::extract::Metadata {
655                        static METADATA: ::std::sync::OnceLock<salvo::extract::Metadata> = ::std::sync::OnceLock::new();
656                        METADATA.get_or_init(||
657                            salvo::extract::Metadata::new("PetQuery")
658                                .default_sources(vec![salvo::extract::metadata::Source::new(
659                                    salvo::extract::metadata::SourceFrom::Query,
660                                    salvo::extract::metadata::SourceParser::MultiMap
661                                )])
662                                .fields(vec![
663                                    salvo::extract::metadata::Field::new("name"),
664                                    salvo::extract::metadata::Field::new("age"),
665                                    salvo::extract::metadata::Field::new("kind")
666                                ])
667                        )
668                    }
669                    async fn extract(
670                        req: &'__macro_gen_ex mut salvo::Request,
671                        depot: &'__macro_gen_ex mut salvo::Depot
672                    ) -> ::std::result::Result<Self, impl salvo::Writer + Send + std::fmt::Debug + 'static> {
673                        salvo::serde::from_request(req, depot, Self::metadata()).await
674                    }
675                    async fn extract_with_arg(
676                        req: &'__macro_gen_ex mut salvo::Request,
677                        depot: &'__macro_gen_ex mut salvo::Depot,
678                        _arg: &str
679                    ) -> ::std::result::Result<Self, impl salvo::Writer + Send + std::fmt::Debug + 'static> {
680                        Self::extract(req, depot).await
681                    }
682                }
683            }
684            .to_string()
685        );
686    }
687}