Skip to main content

salvo_oapi/
lib.rs

1#![doc = include_str!("../docs/lib.md")]
2#![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")]
3#![doc(html_logo_url = "https://salvo.rs/images/logo.svg")]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6#[macro_use]
7mod cfg;
8
9mod openapi;
10pub use openapi::*;
11
12#[doc = include_str!("../docs/endpoint.md")]
13pub mod endpoint;
14pub use endpoint::{Endpoint, EndpointArgRegister, EndpointOutRegister, EndpointRegistry};
15pub mod extract;
16mod routing;
17pub use routing::RouterExt;
18/// Module for name schemas.
19pub mod naming;
20
21cfg_feature! {
22    #![feature ="swagger-ui"]
23    pub mod swagger_ui;
24}
25cfg_feature! {
26    #![feature ="scalar"]
27    pub mod scalar;
28}
29cfg_feature! {
30    #![feature ="rapidoc"]
31    pub mod rapidoc;
32}
33cfg_feature! {
34    #![feature ="redoc"]
35    pub mod redoc;
36}
37
38use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList};
39use std::marker::PhantomData;
40
41use salvo_core::extract::Extractible;
42use salvo_core::http::StatusError;
43use salvo_core::writing;
44#[doc = include_str!("../docs/derive_to_parameters.md")]
45pub use salvo_oapi_macros::ToParameters;
46#[doc = include_str!("../docs/derive_to_response.md")]
47pub use salvo_oapi_macros::ToResponse;
48#[doc = include_str!("../docs/derive_to_responses.md")]
49pub use salvo_oapi_macros::ToResponses;
50#[doc = include_str!("../docs/derive_to_schema.md")]
51pub use salvo_oapi_macros::ToSchema;
52#[doc = include_str!("../docs/endpoint.md")]
53pub use salvo_oapi_macros::endpoint;
54pub(crate) use salvo_oapi_macros::schema;
55
56use crate::oapi::openapi::schema::OneOf;
57
58// https://github.com/bkchr/proc-macro-crate/issues/10
59extern crate self as salvo_oapi;
60
61/// Trait for implementing OpenAPI Schema object.
62///
63/// Generated schemas can be referenced or reused in path operations.
64///
65/// This trait is derivable and can be used with `[#derive]` attribute. For a details of
66/// `#[derive(ToSchema)]` refer to [derive documentation][derive].
67///
68/// [derive]: derive.ToSchema.html
69///
70/// # Examples
71///
72/// Use `#[derive]` to implement `ToSchema` trait.
73/// ```
74/// use salvo_oapi::ToSchema;
75/// #[derive(ToSchema)]
76/// #[salvo(schema(example = json!({"name": "bob the cat", "id": 1})))]
77/// struct Pet {
78///     id: u64,
79///     name: String,
80///     age: Option<i32>,
81/// }
82/// ```
83///
84/// Following manual implementation is equal to above derive one.
85/// ```
86/// use salvo_oapi::{Components, ToSchema, RefOr, Schema, SchemaFormat, BasicType, SchemaType, KnownFormat, Object};
87/// # struct Pet {
88/// #     id: u64,
89/// #     name: String,
90/// #     age: Option<i32>,
91/// # }
92/// #
93/// impl ToSchema for Pet {
94///     fn to_schema(components: &mut Components) -> RefOr<Schema> {
95///         Object::new()
96///             .property(
97///                 "id",
98///                 Object::new()
99///                     .schema_type(BasicType::Integer)
100///                     .format(SchemaFormat::KnownFormat(
101///                         KnownFormat::Int64,
102///                     )),
103///             )
104///             .required("id")
105///             .property(
106///                 "name",
107///                 Object::new()
108///                     .schema_type(BasicType::String),
109///             )
110///             .required("name")
111///             .property(
112///                 "age",
113///                 Object::new()
114///                     .schema_type(BasicType::Integer)
115///                     .format(SchemaFormat::KnownFormat(
116///                         KnownFormat::Int32,
117///                     )),
118///             )
119///             .example(serde_json::json!({
120///               "name":"bob the cat","id":1
121///             }))
122///             .into()
123///     }
124/// }
125/// ```
126pub trait ToSchema {
127    /// Returns a tuple of name and schema or reference to a schema that can be referenced by the
128    /// name or inlined directly to responses, request bodies or parameters.
129    fn to_schema(components: &mut Components) -> RefOr<schema::Schema>;
130}
131
132/// Trait for composing schemas with generic type parameters.
133///
134/// `ComposeSchema` enables generic types to compose their schemas from externally-provided
135/// generic parameter schemas. This separates schema structure generation (compose) from
136/// naming and registration (ToSchema).
137///
138/// For non-generic types, the `generics` parameter is ignored and the schema is generated
139/// directly. For generic types, each element in `generics` corresponds to a type parameter's
140/// schema, in declaration order.
141///
142/// # Examples
143///
144/// Manual implementation for a generic wrapper type:
145/// ```
146/// use salvo_oapi::{BasicType, Components, ComposeSchema, Object, RefOr, Schema};
147///
148/// struct Page<T> {
149///     items: Vec<T>,
150///     total: u64,
151/// }
152///
153/// impl<T: ComposeSchema> ComposeSchema for Page<T> {
154///     fn compose(components: &mut Components, generics: Vec<RefOr<Schema>>) -> RefOr<Schema> {
155///         let t_schema = generics
156///             .first()
157///             .cloned()
158///             .unwrap_or_else(|| T::compose(components, vec![]));
159///         Object::new()
160///             .property("items", salvo_oapi::schema::Array::new().items(t_schema))
161///             .required("items")
162///             .property("total", Object::new().schema_type(BasicType::Integer))
163///             .required("total")
164///             .into()
165///     }
166/// }
167/// ```
168pub trait ComposeSchema {
169    /// Compose a schema using the provided generic parameter schemas.
170    ///
171    /// The `components` parameter allows registering nested schemas.
172    /// The `generics` vector contains pre-resolved schemas for each type parameter,
173    /// in the order they appear in the type definition.
174    fn compose(
175        components: &mut Components,
176        generics: Vec<RefOr<schema::Schema>>,
177    ) -> RefOr<schema::Schema>;
178}
179
180/// Tracks schema references for generic type resolution.
181///
182/// `SchemaReference` represents a schema and its generic parameter references,
183/// enabling recursive schema composition for generic types.
184#[derive(Debug, Clone, Default)]
185pub struct SchemaReference {
186    /// The schema name.
187    pub name: std::borrow::Cow<'static, str>,
188    /// Whether this schema should be inlined rather than referenced.
189    pub inline: bool,
190    /// Child references for generic type parameters.
191    pub references: Vec<SchemaReference>,
192}
193
194impl SchemaReference {
195    /// Create a new `SchemaReference` with the given name.
196    pub fn new(name: impl Into<std::borrow::Cow<'static, str>>) -> Self {
197        Self {
198            name: name.into(),
199            inline: false,
200            references: Vec::new(),
201        }
202    }
203
204    /// Set whether this schema should be inlined.
205    pub fn inline(mut self, inline: bool) -> Self {
206        self.inline = inline;
207        self
208    }
209
210    /// Add a child reference for a generic type parameter.
211    pub fn reference(mut self, reference: SchemaReference) -> Self {
212        self.references.push(reference);
213        self
214    }
215
216    /// Get the composed name including generic parameters.
217    ///
218    /// For example, `Page` with child `User` produces `Page<User>`.
219    pub fn compose_name(&self) -> String {
220        if self.references.is_empty() {
221            self.name.to_string()
222        } else {
223            let generic_names: Vec<String> =
224                self.references.iter().map(|r| r.compose_name()).collect();
225            format!("{}<{}>", self.name, generic_names.join(", "))
226        }
227    }
228
229    /// Get the schemas for the direct generic type parameters.
230    pub fn compose_generics(&self) -> &[SchemaReference] {
231        &self.references
232    }
233
234    /// Collect all child references recursively (depth-first).
235    pub fn compose_child_references(&self) -> Vec<&SchemaReference> {
236        let mut result = Vec::new();
237        for reference in &self.references {
238            result.push(reference);
239            result.extend(reference.compose_child_references());
240        }
241        result
242    }
243}
244
245/// Represents _`nullable`_ type.
246///
247/// This can be used anywhere where "nothing" needs to be evaluated.
248/// This will serialize to _`null`_ in JSON and [`schema::empty`] is used to create the
249/// [`schema::Schema`] for the type.
250pub type TupleUnit = ();
251
252impl ToSchema for TupleUnit {
253    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
254        schema::empty().into()
255    }
256}
257impl ComposeSchema for TupleUnit {
258    fn compose(
259        _components: &mut Components,
260        _generics: Vec<RefOr<schema::Schema>>,
261    ) -> RefOr<schema::Schema> {
262        schema::empty().into()
263    }
264}
265
266macro_rules! impl_to_schema {
267    ($ty:path) => {
268        impl_to_schema!( @impl_schema $ty );
269    };
270    (&$ty:path) => {
271        impl_to_schema!( @impl_schema &$ty );
272    };
273    (@impl_schema $($tt:tt)*) => {
274        impl ToSchema for $($tt)* {
275            fn to_schema(_components: &mut Components) -> crate::RefOr<crate::schema::Schema> {
276                 schema!( $($tt)* ).into()
277            }
278        }
279        impl ComposeSchema for $($tt)* {
280            fn compose(_components: &mut Components, _generics: Vec<crate::RefOr<crate::schema::Schema>>) -> crate::RefOr<crate::schema::Schema> {
281                 schema!( $($tt)* ).into()
282            }
283        }
284    };
285}
286
287macro_rules! impl_to_schema_primitive {
288    ($($tt:path),*) => {
289        $( impl_to_schema!( $tt ); )*
290    };
291}
292
293// Create `salvo-oapi` module so we can use `salvo-oapi-macros` directly
294// from `salvo-oapi` crate. ONLY FOR INTERNAL USE!
295#[doc(hidden)]
296pub mod oapi {
297    pub use super::*;
298}
299
300#[doc(hidden)]
301pub mod __private {
302    pub use inventory;
303    pub use serde_json;
304}
305
306#[rustfmt::skip]
307impl_to_schema_primitive!(
308    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool, f32, f64, String, str, char
309);
310impl_to_schema!(&str);
311
312impl_to_schema!(std::net::Ipv4Addr);
313impl_to_schema!(std::net::Ipv6Addr);
314
315impl ToSchema for std::net::IpAddr {
316    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
317        crate::RefOr::Type(Schema::OneOf(
318            OneOf::default()
319                .item(std::net::Ipv4Addr::to_schema(components))
320                .item(std::net::Ipv6Addr::to_schema(components)),
321        ))
322    }
323}
324impl ComposeSchema for std::net::IpAddr {
325    fn compose(
326        components: &mut Components,
327        _generics: Vec<RefOr<schema::Schema>>,
328    ) -> RefOr<schema::Schema> {
329        Self::to_schema(components)
330    }
331}
332
333#[cfg(feature = "chrono")]
334impl_to_schema_primitive!(chrono::NaiveDate, chrono::Duration, chrono::NaiveDateTime);
335#[cfg(feature = "chrono")]
336impl<T: chrono::TimeZone> ToSchema for chrono::DateTime<T> {
337    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
338        schema!(#[inline] DateTime<T>).into()
339    }
340}
341#[cfg(feature = "chrono")]
342impl<T: chrono::TimeZone> ComposeSchema for chrono::DateTime<T> {
343    fn compose(
344        _components: &mut Components,
345        _generics: Vec<RefOr<schema::Schema>>,
346    ) -> RefOr<schema::Schema> {
347        schema!(#[inline] DateTime<T>).into()
348    }
349}
350#[cfg(feature = "compact_str")]
351impl_to_schema_primitive!(compact_str::CompactString);
352#[cfg(any(feature = "decimal", feature = "decimal-float"))]
353impl_to_schema!(rust_decimal::Decimal);
354#[cfg(feature = "url")]
355impl_to_schema!(url::Url);
356#[cfg(feature = "uuid")]
357impl_to_schema!(uuid::Uuid);
358#[cfg(feature = "ulid")]
359impl_to_schema!(ulid::Ulid);
360#[cfg(feature = "time")]
361impl_to_schema_primitive!(
362    time::Date,
363    time::PrimitiveDateTime,
364    time::OffsetDateTime,
365    time::Duration
366);
367#[cfg(feature = "smallvec")]
368impl<T: ToSchema + smallvec::Array> ToSchema for smallvec::SmallVec<T> {
369    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
370        schema!(#[inline] smallvec::SmallVec<T>).into()
371    }
372}
373#[cfg(feature = "smallvec")]
374impl<T: ComposeSchema + smallvec::Array> ComposeSchema for smallvec::SmallVec<T> {
375    fn compose(
376        components: &mut Components,
377        generics: Vec<RefOr<schema::Schema>>,
378    ) -> RefOr<schema::Schema> {
379        let t_schema = generics
380            .first()
381            .cloned()
382            .unwrap_or_else(|| T::compose(components, vec![]));
383        schema::Array::new().items(t_schema).into()
384    }
385}
386#[cfg(feature = "indexmap")]
387impl<K: ToSchema, V: ToSchema> ToSchema for indexmap::IndexMap<K, V> {
388    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
389        schema!(#[inline] indexmap::IndexMap<K, V>).into()
390    }
391}
392#[cfg(feature = "indexmap")]
393impl<K: ComposeSchema, V: ComposeSchema> ComposeSchema for indexmap::IndexMap<K, V> {
394    fn compose(
395        components: &mut Components,
396        generics: Vec<RefOr<schema::Schema>>,
397    ) -> RefOr<schema::Schema> {
398        let v_schema = generics
399            .get(1)
400            .cloned()
401            .unwrap_or_else(|| V::compose(components, vec![]));
402        schema::Object::new().additional_properties(v_schema).into()
403    }
404}
405
406impl<T: ToSchema> ToSchema for Vec<T> {
407    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
408        schema!(#[inline] Vec<T>).into()
409    }
410}
411impl<T: ComposeSchema> ComposeSchema for Vec<T> {
412    fn compose(
413        components: &mut Components,
414        generics: Vec<RefOr<schema::Schema>>,
415    ) -> RefOr<schema::Schema> {
416        let t_schema = generics
417            .first()
418            .cloned()
419            .unwrap_or_else(|| T::compose(components, vec![]));
420        schema::Array::new().items(t_schema).into()
421    }
422}
423
424impl<T: ToSchema> ToSchema for LinkedList<T> {
425    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
426        schema!(#[inline] LinkedList<T>).into()
427    }
428}
429impl<T: ComposeSchema> ComposeSchema for LinkedList<T> {
430    fn compose(
431        components: &mut Components,
432        generics: Vec<RefOr<schema::Schema>>,
433    ) -> RefOr<schema::Schema> {
434        let t_schema = generics
435            .first()
436            .cloned()
437            .unwrap_or_else(|| T::compose(components, vec![]));
438        schema::Array::new().items(t_schema).into()
439    }
440}
441
442impl<T: ToSchema> ToSchema for HashSet<T> {
443    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
444        schema::Array::new()
445            .items(T::to_schema(components))
446            .unique_items(true)
447            .into()
448    }
449}
450impl<T: ComposeSchema> ComposeSchema for HashSet<T> {
451    fn compose(
452        components: &mut Components,
453        generics: Vec<RefOr<schema::Schema>>,
454    ) -> RefOr<schema::Schema> {
455        let t_schema = generics
456            .first()
457            .cloned()
458            .unwrap_or_else(|| T::compose(components, vec![]));
459        schema::Array::new()
460            .items(t_schema)
461            .unique_items(true)
462            .into()
463    }
464}
465
466impl<T: ToSchema> ToSchema for BTreeSet<T> {
467    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
468        schema::Array::new()
469            .items(T::to_schema(components))
470            .unique_items(true)
471            .into()
472    }
473}
474impl<T: ComposeSchema> ComposeSchema for BTreeSet<T> {
475    fn compose(
476        components: &mut Components,
477        generics: Vec<RefOr<schema::Schema>>,
478    ) -> RefOr<schema::Schema> {
479        let t_schema = generics
480            .first()
481            .cloned()
482            .unwrap_or_else(|| T::compose(components, vec![]));
483        schema::Array::new()
484            .items(t_schema)
485            .unique_items(true)
486            .into()
487    }
488}
489
490#[cfg(feature = "indexmap")]
491impl<T: ToSchema> ToSchema for indexmap::IndexSet<T> {
492    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
493        schema::Array::new()
494            .items(T::to_schema(components))
495            .unique_items(true)
496            .into()
497    }
498}
499#[cfg(feature = "indexmap")]
500impl<T: ComposeSchema> ComposeSchema for indexmap::IndexSet<T> {
501    fn compose(
502        components: &mut Components,
503        generics: Vec<RefOr<schema::Schema>>,
504    ) -> RefOr<schema::Schema> {
505        let t_schema = generics
506            .first()
507            .cloned()
508            .unwrap_or_else(|| T::compose(components, vec![]));
509        schema::Array::new()
510            .items(t_schema)
511            .unique_items(true)
512            .into()
513    }
514}
515
516impl<T: ToSchema> ToSchema for Box<T> {
517    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
518        T::to_schema(components)
519    }
520}
521impl<T: ComposeSchema> ComposeSchema for Box<T> {
522    fn compose(
523        components: &mut Components,
524        generics: Vec<RefOr<schema::Schema>>,
525    ) -> RefOr<schema::Schema> {
526        T::compose(components, generics)
527    }
528}
529
530impl<T: ToSchema + ToOwned> ToSchema for std::borrow::Cow<'_, T> {
531    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
532        T::to_schema(components)
533    }
534}
535impl<T: ComposeSchema + ToOwned> ComposeSchema for std::borrow::Cow<'_, T> {
536    fn compose(
537        components: &mut Components,
538        generics: Vec<RefOr<schema::Schema>>,
539    ) -> RefOr<schema::Schema> {
540        T::compose(components, generics)
541    }
542}
543
544impl<T: ToSchema> ToSchema for std::cell::RefCell<T> {
545    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
546        T::to_schema(components)
547    }
548}
549impl<T: ComposeSchema> ComposeSchema for std::cell::RefCell<T> {
550    fn compose(
551        components: &mut Components,
552        generics: Vec<RefOr<schema::Schema>>,
553    ) -> RefOr<schema::Schema> {
554        T::compose(components, generics)
555    }
556}
557
558impl<T: ToSchema> ToSchema for std::rc::Rc<T> {
559    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
560        T::to_schema(components)
561    }
562}
563impl<T: ComposeSchema> ComposeSchema for std::rc::Rc<T> {
564    fn compose(
565        components: &mut Components,
566        generics: Vec<RefOr<schema::Schema>>,
567    ) -> RefOr<schema::Schema> {
568        T::compose(components, generics)
569    }
570}
571
572impl<T: ToSchema> ToSchema for std::sync::Arc<T> {
573    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
574        T::to_schema(components)
575    }
576}
577impl<T: ComposeSchema> ComposeSchema for std::sync::Arc<T> {
578    fn compose(
579        components: &mut Components,
580        generics: Vec<RefOr<schema::Schema>>,
581    ) -> RefOr<schema::Schema> {
582        T::compose(components, generics)
583    }
584}
585
586impl<T: ToSchema> ToSchema for [T] {
587    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
588        schema!(
589            #[inline]
590            [T]
591        )
592        .into()
593    }
594}
595impl<T: ComposeSchema> ComposeSchema for [T] {
596    fn compose(
597        components: &mut Components,
598        generics: Vec<RefOr<schema::Schema>>,
599    ) -> RefOr<schema::Schema> {
600        let t_schema = generics
601            .first()
602            .cloned()
603            .unwrap_or_else(|| T::compose(components, vec![]));
604        schema::Array::new().items(t_schema).into()
605    }
606}
607
608impl<T: ToSchema, const N: usize> ToSchema for [T; N] {
609    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
610        schema!(
611            #[inline]
612            [T; N]
613        )
614        .into()
615    }
616}
617impl<T: ComposeSchema, const N: usize> ComposeSchema for [T; N] {
618    fn compose(
619        components: &mut Components,
620        generics: Vec<RefOr<schema::Schema>>,
621    ) -> RefOr<schema::Schema> {
622        let t_schema = generics
623            .first()
624            .cloned()
625            .unwrap_or_else(|| T::compose(components, vec![]));
626        schema::Array::new().items(t_schema).into()
627    }
628}
629
630impl<T: ToSchema> ToSchema for &[T] {
631    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
632        schema!(
633            #[inline]
634            &[T]
635        )
636        .into()
637    }
638}
639impl<T: ComposeSchema> ComposeSchema for &[T] {
640    fn compose(
641        components: &mut Components,
642        generics: Vec<RefOr<schema::Schema>>,
643    ) -> RefOr<schema::Schema> {
644        let t_schema = generics
645            .first()
646            .cloned()
647            .unwrap_or_else(|| T::compose(components, vec![]));
648        schema::Array::new().items(t_schema).into()
649    }
650}
651
652impl<T: ToSchema> ToSchema for Option<T> {
653    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
654        schema!(#[inline] Option<T>).into()
655    }
656}
657impl<T: ComposeSchema> ComposeSchema for Option<T> {
658    fn compose(
659        components: &mut Components,
660        generics: Vec<RefOr<schema::Schema>>,
661    ) -> RefOr<schema::Schema> {
662        let t_schema = generics
663            .first()
664            .cloned()
665            .unwrap_or_else(|| T::compose(components, vec![]));
666        schema::OneOf::new()
667            .item(t_schema)
668            .item(schema::Object::new().schema_type(schema::BasicType::Null))
669            .into()
670    }
671}
672
673impl<T> ToSchema for PhantomData<T> {
674    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
675        Schema::Object(Box::default()).into()
676    }
677}
678impl<T> ComposeSchema for PhantomData<T> {
679    fn compose(
680        _components: &mut Components,
681        _generics: Vec<RefOr<schema::Schema>>,
682    ) -> RefOr<schema::Schema> {
683        Schema::Object(Box::default()).into()
684    }
685}
686
687impl<K: ToSchema, V: ToSchema> ToSchema for BTreeMap<K, V> {
688    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
689        schema!(#[inline]BTreeMap<K, V>).into()
690    }
691}
692impl<K: ComposeSchema, V: ComposeSchema> ComposeSchema for BTreeMap<K, V> {
693    fn compose(
694        components: &mut Components,
695        generics: Vec<RefOr<schema::Schema>>,
696    ) -> RefOr<schema::Schema> {
697        let v_schema = generics
698            .get(1)
699            .cloned()
700            .unwrap_or_else(|| V::compose(components, vec![]));
701        schema::Object::new().additional_properties(v_schema).into()
702    }
703}
704
705impl<K: ToSchema, V: ToSchema> ToSchema for HashMap<K, V> {
706    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
707        schema!(#[inline]HashMap<K, V>).into()
708    }
709}
710impl<K: ComposeSchema, V: ComposeSchema> ComposeSchema for HashMap<K, V> {
711    fn compose(
712        components: &mut Components,
713        generics: Vec<RefOr<schema::Schema>>,
714    ) -> RefOr<schema::Schema> {
715        let v_schema = generics
716            .get(1)
717            .cloned()
718            .unwrap_or_else(|| V::compose(components, vec![]));
719        schema::Object::new().additional_properties(v_schema).into()
720    }
721}
722
723impl ToSchema for StatusError {
724    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
725        let name = crate::naming::assign_name::<Self>(Default::default());
726        let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
727        if !components.schemas.contains_key(&name) {
728            components.schemas.insert(name.clone(), ref_or.clone());
729            let schema = Schema::from(
730                Object::new()
731                    .property("code", u16::to_schema(components))
732                    .required("code")
733                    .required("name")
734                    .property("name", String::to_schema(components))
735                    .required("brief")
736                    .property("brief", String::to_schema(components))
737                    .required("detail")
738                    .property("detail", String::to_schema(components))
739                    .property("cause", String::to_schema(components)),
740            );
741            components.schemas.insert(name, schema);
742        }
743        ref_or
744    }
745}
746impl ComposeSchema for StatusError {
747    fn compose(
748        components: &mut Components,
749        _generics: Vec<RefOr<schema::Schema>>,
750    ) -> RefOr<schema::Schema> {
751        Self::to_schema(components)
752    }
753}
754
755impl ToSchema for salvo_core::Error {
756    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
757        StatusError::to_schema(components)
758    }
759}
760impl ComposeSchema for salvo_core::Error {
761    fn compose(
762        components: &mut Components,
763        _generics: Vec<RefOr<schema::Schema>>,
764    ) -> RefOr<schema::Schema> {
765        Self::to_schema(components)
766    }
767}
768
769impl<T, E> ToSchema for Result<T, E>
770where
771    T: ToSchema,
772    E: ToSchema,
773{
774    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
775        let name = crate::naming::assign_name::<StatusError>(Default::default());
776        let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
777        if !components.schemas.contains_key(&name) {
778            components.schemas.insert(name.clone(), ref_or.clone());
779            let schema = OneOf::new()
780                .item(T::to_schema(components))
781                .item(E::to_schema(components));
782            components.schemas.insert(name, schema);
783        }
784        ref_or
785    }
786}
787impl<T, E> ComposeSchema for Result<T, E>
788where
789    T: ComposeSchema,
790    E: ComposeSchema,
791{
792    fn compose(
793        components: &mut Components,
794        generics: Vec<RefOr<schema::Schema>>,
795    ) -> RefOr<schema::Schema> {
796        let t_schema = generics
797            .first()
798            .cloned()
799            .unwrap_or_else(|| T::compose(components, vec![]));
800        let e_schema = generics
801            .get(1)
802            .cloned()
803            .unwrap_or_else(|| E::compose(components, vec![]));
804        OneOf::new().item(t_schema).item(e_schema).into()
805    }
806}
807
808impl ToSchema for serde_json::Value {
809    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
810        Schema::Object(Box::default()).into()
811    }
812}
813impl ComposeSchema for serde_json::Value {
814    fn compose(
815        _components: &mut Components,
816        _generics: Vec<RefOr<schema::Schema>>,
817    ) -> RefOr<schema::Schema> {
818        Schema::Object(Box::default()).into()
819    }
820}
821
822impl ToSchema for serde_json::Map<String, serde_json::Value> {
823    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
824        Schema::Object(Box::new(schema::Object::new())).into()
825    }
826}
827impl ComposeSchema for serde_json::Map<String, serde_json::Value> {
828    fn compose(
829        _components: &mut Components,
830        _generics: Vec<RefOr<schema::Schema>>,
831    ) -> RefOr<schema::Schema> {
832        Schema::Object(Box::new(schema::Object::new())).into()
833    }
834}
835
836/// Trait used to convert implementing type to OpenAPI parameters.
837///
838/// This trait is [derivable][derive] for structs which are used to describe `path` or `query`
839/// parameters. For more details of `#[derive(ToParameters)]` refer to [derive
840/// documentation][derive].
841///
842/// # Examples
843///
844/// Derive [`ToParameters`] implementation. This example will fail to compile because
845/// [`ToParameters`] cannot be used alone and it need to be used together with endpoint using the
846/// params as well. See [derive documentation][derive] for more details.
847/// ```
848/// use salvo_core::prelude::*;
849/// use salvo_oapi::{Components, EndpointArgRegister, Operation, ToParameters};
850/// use serde::Deserialize;
851///
852/// #[derive(Deserialize, ToParameters)]
853/// struct PetParams {
854///     /// Id of pet
855///     id: i64,
856///     /// Name of pet
857///     name: String,
858/// }
859/// ```
860///
861/// Roughly equal manual implementation of [`ToParameters`] trait.
862/// ```
863/// # use serde::Deserialize;
864/// # use salvo_oapi::{ToParameters, EndpointArgRegister, Components, Operation};
865/// # use salvo_core::prelude::*;
866/// # use salvo_core::extract::{Metadata, Extractible};
867/// #[derive(Deserialize)]
868/// # struct PetParams {
869/// #    /// Id of pet
870/// #    id: i64,
871/// #    /// Name of pet
872/// #    name: String,
873/// # }
874/// impl<'de> salvo_oapi::ToParameters<'de> for PetParams {
875///     fn to_parameters(_components: &mut Components) -> salvo_oapi::Parameters {
876///         salvo_oapi::Parameters::new()
877///             .parameter(
878///                 salvo_oapi::Parameter::new("id")
879///                     .required(salvo_oapi::Required::True)
880///                     .parameter_in(salvo_oapi::ParameterIn::Path)
881///                     .description("Id of pet")
882///                     .schema(
883///                         salvo_oapi::Object::new()
884///                             .schema_type(salvo_oapi::schema::BasicType::Integer)
885///                             .format(salvo_oapi::SchemaFormat::KnownFormat(
886///                                 salvo_oapi::schema::KnownFormat::Int64,
887///                             )),
888///                     ),
889///             )
890///             .parameter(
891///                 salvo_oapi::Parameter::new("name")
892///                     .required(salvo_oapi::Required::True)
893///                     .parameter_in(salvo_oapi::ParameterIn::Query)
894///                     .description("Name of pet")
895///                     .schema(
896///                         salvo_oapi::Object::new()
897///                             .schema_type(salvo_oapi::schema::BasicType::String),
898///                     ),
899///             )
900///     }
901/// }
902///
903/// impl<'ex> Extractible<'ex> for PetParams {
904///     fn metadata() -> &'static Metadata {
905///         static METADATA: Metadata = Metadata::new("");
906///         &METADATA
907///     }
908///     #[allow(refining_impl_trait)]
909///     async fn extract(
910///         req: &'ex mut Request,
911///         depot: &'ex mut Depot,
912///     ) -> Result<Self, salvo_core::http::ParseError> {
913///         salvo_core::serde::from_request(req, depot, Self::metadata()).await
914///     }
915///     #[allow(refining_impl_trait)]
916///     async fn extract_with_arg(
917///         req: &'ex mut Request,
918///         depot: &'ex mut Depot,
919///         _arg: &str,
920///     ) -> Result<Self, salvo_core::http::ParseError> {
921///         Self::extract(req, depot).await
922///     }
923/// }
924///
925/// impl EndpointArgRegister for PetParams {
926///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
927///         operation
928///             .parameters
929///             .append(&mut PetParams::to_parameters(components));
930///     }
931/// }
932/// ```
933/// [derive]: derive.ToParameters.html
934pub trait ToParameters<'de>: Extractible<'de> {
935    /// Provide [`Vec`] of [`Parameter`]s to caller. The result is used in `salvo-oapi-macros`
936    /// library to provide OpenAPI parameter information for the endpoint using the parameters.
937    fn to_parameters(components: &mut Components) -> Parameters;
938}
939
940/// Trait used to give [`Parameter`] information for OpenAPI.
941pub trait ToParameter {
942    /// Returns a `Parameter`.
943    fn to_parameter(components: &mut Components) -> Parameter;
944}
945
946/// This trait is implemented to document a type (like an enum) which can represent
947/// request body, to be used in operation.
948///
949/// # Examples
950///
951/// ```
952/// use std::collections::BTreeMap;
953///
954/// use salvo_oapi::{
955///     Components, Content, EndpointArgRegister, Operation, RequestBody, ToRequestBody, ToSchema,
956/// };
957/// use serde::Deserialize;
958///
959/// #[derive(ToSchema, Deserialize, Debug)]
960/// struct MyPayload {
961///     name: String,
962/// }
963///
964/// impl ToRequestBody for MyPayload {
965///     fn to_request_body(components: &mut Components) -> RequestBody {
966///         RequestBody::new().add_content(
967///             "application/json",
968///             Content::new(MyPayload::to_schema(components)),
969///         )
970///     }
971/// }
972/// impl EndpointArgRegister for MyPayload {
973///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
974///         operation.request_body = Some(Self::to_request_body(components));
975///     }
976/// }
977/// ```
978pub trait ToRequestBody {
979    /// Returns `RequestBody`.
980    fn to_request_body(components: &mut Components) -> RequestBody;
981}
982
983/// This trait is implemented to document a type (like an enum) which can represent multiple
984/// responses, to be used in operation.
985///
986/// # Examples
987///
988/// ```
989/// use std::collections::BTreeMap;
990///
991/// use salvo_oapi::{Components, RefOr, Response, Responses, ToResponses};
992///
993/// enum MyResponse {
994///     Ok,
995///     NotFound,
996/// }
997///
998/// impl ToResponses for MyResponse {
999///     fn to_responses(_components: &mut Components) -> Responses {
1000///         Responses::new()
1001///             .response("200", Response::new("Ok"))
1002///             .response("404", Response::new("Not Found"))
1003///     }
1004/// }
1005/// ```
1006pub trait ToResponses {
1007    /// Returns an ordered map of response codes to responses.
1008    fn to_responses(components: &mut Components) -> Responses;
1009}
1010
1011impl<C> ToResponses for writing::Json<C>
1012where
1013    C: ToSchema,
1014{
1015    fn to_responses(components: &mut Components) -> Responses {
1016        Responses::new().response(
1017            "200",
1018            Response::new("Response json format data")
1019                .add_content("application/json", Content::new(C::to_schema(components))),
1020        )
1021    }
1022}
1023
1024impl ToResponses for StatusError {
1025    fn to_responses(components: &mut Components) -> Responses {
1026        let mut responses = Responses::new();
1027        let errors = vec![
1028            Self::bad_request(),
1029            Self::unauthorized(),
1030            Self::payment_required(),
1031            Self::forbidden(),
1032            Self::not_found(),
1033            Self::method_not_allowed(),
1034            Self::not_acceptable(),
1035            Self::proxy_authentication_required(),
1036            Self::request_timeout(),
1037            Self::conflict(),
1038            Self::gone(),
1039            Self::length_required(),
1040            Self::precondition_failed(),
1041            Self::payload_too_large(),
1042            Self::uri_too_long(),
1043            Self::unsupported_media_type(),
1044            Self::range_not_satisfiable(),
1045            Self::expectation_failed(),
1046            Self::im_a_teapot(),
1047            Self::misdirected_request(),
1048            Self::unprocessable_entity(),
1049            Self::locked(),
1050            Self::failed_dependency(),
1051            Self::upgrade_required(),
1052            Self::precondition_required(),
1053            Self::too_many_requests(),
1054            Self::request_header_fields_toolarge(),
1055            Self::unavailable_for_legalreasons(),
1056            Self::internal_server_error(),
1057            Self::not_implemented(),
1058            Self::bad_gateway(),
1059            Self::service_unavailable(),
1060            Self::gateway_timeout(),
1061            Self::http_version_not_supported(),
1062            Self::variant_also_negotiates(),
1063            Self::insufficient_storage(),
1064            Self::loop_detected(),
1065            Self::not_extended(),
1066            Self::network_authentication_required(),
1067        ];
1068        for Self { code, brief, .. } in errors {
1069            responses.insert(
1070                code.as_str(),
1071                Response::new(brief).add_content(
1072                    "application/json",
1073                    Content::new(Self::to_schema(components)),
1074                ),
1075            )
1076        }
1077        responses
1078    }
1079}
1080impl ToResponses for salvo_core::Error {
1081    fn to_responses(components: &mut Components) -> Responses {
1082        StatusError::to_responses(components)
1083    }
1084}
1085
1086/// This trait is implemented to document a type which represents a single response which can be
1087/// referenced or reused as a component in multiple operations.
1088///
1089/// _`ToResponse`_ trait can also be derived with [`#[derive(ToResponse)]`][derive].
1090///
1091/// # Examples
1092///
1093/// ```
1094/// use salvo_oapi::{Components, RefOr, Response, ToResponse};
1095///
1096/// struct MyResponse;
1097/// impl ToResponse for MyResponse {
1098///     fn to_response(_components: &mut Components) -> RefOr<Response> {
1099///         Response::new("My Response").into()
1100///     }
1101/// }
1102/// ```
1103///
1104/// [derive]: derive.ToResponse.html
1105pub trait ToResponse {
1106    /// Returns a tuple of response component name (to be referenced) to a response.
1107    fn to_response(components: &mut Components) -> RefOr<crate::Response>;
1108}
1109
1110impl<C> ToResponse for writing::Json<C>
1111where
1112    C: ToSchema,
1113{
1114    fn to_response(components: &mut Components) -> RefOr<Response> {
1115        let schema = <C as ToSchema>::to_schema(components);
1116        Response::new("Response with json format data")
1117            .add_content("application/json", Content::new(schema))
1118            .into()
1119    }
1120}
1121
1122#[cfg(test)]
1123mod tests {
1124    use assert_json_diff::assert_json_eq;
1125    use serde_json::json;
1126
1127    use super::*;
1128
1129    #[test]
1130    fn test_primitive_schema() {
1131        let mut components = Components::new();
1132
1133        // Format expectations differ based on whether "non-strict-integers" feature is enabled.
1134        // With the feature: each integer type gets its own format (int8, uint8, int16, etc.)
1135        // Without: smaller integers collapse to int32/int64 per OpenAPI convention.
1136        let non_strict = cfg!(feature = "non-strict-integers");
1137
1138        for (name, schema, value) in [
1139            (
1140                "i8",
1141                i8::to_schema(&mut components),
1142                if non_strict {
1143                    json!({"type": "integer", "format": "int8"})
1144                } else {
1145                    json!({"type": "integer", "format": "int32"})
1146                },
1147            ),
1148            (
1149                "i16",
1150                i16::to_schema(&mut components),
1151                if non_strict {
1152                    json!({"type": "integer", "format": "int16"})
1153                } else {
1154                    json!({"type": "integer", "format": "int32"})
1155                },
1156            ),
1157            (
1158                "i32",
1159                i32::to_schema(&mut components),
1160                json!({"type": "integer", "format": "int32"}),
1161            ),
1162            (
1163                "i64",
1164                i64::to_schema(&mut components),
1165                json!({"type": "integer", "format": "int64"}),
1166            ),
1167            (
1168                "i128",
1169                i128::to_schema(&mut components),
1170                json!({"type": "integer"}),
1171            ),
1172            (
1173                "isize",
1174                isize::to_schema(&mut components),
1175                json!({"type": "integer"}),
1176            ),
1177            (
1178                "u8",
1179                u8::to_schema(&mut components),
1180                if non_strict {
1181                    json!({"type": "integer", "format": "uint8", "minimum": 0})
1182                } else {
1183                    json!({"type": "integer", "format": "int32", "minimum": 0})
1184                },
1185            ),
1186            (
1187                "u16",
1188                u16::to_schema(&mut components),
1189                if non_strict {
1190                    json!({"type": "integer", "format": "uint16", "minimum": 0})
1191                } else {
1192                    json!({"type": "integer", "format": "int32", "minimum": 0})
1193                },
1194            ),
1195            (
1196                "u32",
1197                u32::to_schema(&mut components),
1198                if non_strict {
1199                    json!({"type": "integer", "format": "uint32", "minimum": 0})
1200                } else {
1201                    json!({"type": "integer", "format": "int32", "minimum": 0})
1202                },
1203            ),
1204            (
1205                "u64",
1206                u64::to_schema(&mut components),
1207                if non_strict {
1208                    json!({"type": "integer", "format": "uint64", "minimum": 0})
1209                } else {
1210                    json!({"type": "integer", "format": "int64", "minimum": 0})
1211                },
1212            ),
1213            (
1214                "u128",
1215                u128::to_schema(&mut components),
1216                json!({"type": "integer", "minimum": 0}),
1217            ),
1218            (
1219                "usize",
1220                usize::to_schema(&mut components),
1221                json!({"type": "integer", "minimum": 0}),
1222            ),
1223            (
1224                "bool",
1225                bool::to_schema(&mut components),
1226                json!({"type": "boolean"}),
1227            ),
1228            (
1229                "str",
1230                str::to_schema(&mut components),
1231                json!({"type": "string"}),
1232            ),
1233            (
1234                "String",
1235                String::to_schema(&mut components),
1236                json!({"type": "string"}),
1237            ),
1238            (
1239                "char",
1240                char::to_schema(&mut components),
1241                json!({"type": "string"}),
1242            ),
1243            (
1244                "f32",
1245                f32::to_schema(&mut components),
1246                json!({"type": "number", "format": "float"}),
1247            ),
1248            (
1249                "f64",
1250                f64::to_schema(&mut components),
1251                json!({"type": "number", "format": "double"}),
1252            ),
1253        ] {
1254            println!(
1255                "{name}: {json}",
1256                json = serde_json::to_string(&schema).unwrap()
1257            );
1258            let schema = serde_json::to_value(schema).unwrap();
1259            assert_json_eq!(schema, value);
1260        }
1261    }
1262}