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