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