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.as_ref().to_owned()
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
842impl ToSchema for serde_json::value::RawValue {
843    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
844        Schema::Object(Box::default()).into()
845    }
846}
847impl ComposeSchema for serde_json::value::RawValue {
848    fn compose(
849        _components: &mut Components,
850        _generics: Vec<RefOr<schema::Schema>>,
851    ) -> RefOr<schema::Schema> {
852        Schema::Object(Box::default()).into()
853    }
854}
855
856/// Trait used to convert implementing type to OpenAPI parameters.
857///
858/// This trait is [derivable][derive] for structs which are used to describe `path` or `query`
859/// parameters. For more details of `#[derive(ToParameters)]` refer to [derive
860/// documentation][derive].
861///
862/// # Examples
863///
864/// Derive [`ToParameters`] implementation. This example will fail to compile because
865/// [`ToParameters`] cannot be used alone and it need to be used together with endpoint using the
866/// params as well. See [derive documentation][derive] for more details.
867/// ```
868/// use salvo_core::prelude::*;
869/// use salvo_oapi::{Components, EndpointArgRegister, Operation, ToParameters};
870/// use serde::Deserialize;
871///
872/// #[derive(Deserialize, ToParameters)]
873/// struct PetParams {
874///     /// Id of pet
875///     id: i64,
876///     /// Name of pet
877///     name: String,
878/// }
879/// ```
880///
881/// Roughly equal manual implementation of [`ToParameters`] trait.
882/// ```
883/// # use serde::Deserialize;
884/// # use salvo_oapi::{ToParameters, EndpointArgRegister, Components, Operation};
885/// # use salvo_core::prelude::*;
886/// # use salvo_core::extract::{Metadata, Extractible};
887/// #[derive(Deserialize)]
888/// # struct PetParams {
889/// #    /// Id of pet
890/// #    id: i64,
891/// #    /// Name of pet
892/// #    name: String,
893/// # }
894/// impl<'de> salvo_oapi::ToParameters<'de> for PetParams {
895///     fn to_parameters(_components: &mut Components) -> salvo_oapi::Parameters {
896///         salvo_oapi::Parameters::new()
897///             .parameter(
898///                 salvo_oapi::Parameter::new("id")
899///                     .required(salvo_oapi::Required::True)
900///                     .parameter_in(salvo_oapi::ParameterIn::Path)
901///                     .description("Id of pet")
902///                     .schema(
903///                         salvo_oapi::Object::new()
904///                             .schema_type(salvo_oapi::schema::BasicType::Integer)
905///                             .format(salvo_oapi::SchemaFormat::KnownFormat(
906///                                 salvo_oapi::schema::KnownFormat::Int64,
907///                             )),
908///                     ),
909///             )
910///             .parameter(
911///                 salvo_oapi::Parameter::new("name")
912///                     .required(salvo_oapi::Required::True)
913///                     .parameter_in(salvo_oapi::ParameterIn::Query)
914///                     .description("Name of pet")
915///                     .schema(
916///                         salvo_oapi::Object::new()
917///                             .schema_type(salvo_oapi::schema::BasicType::String),
918///                     ),
919///             )
920///     }
921/// }
922///
923/// impl<'ex> Extractible<'ex> for PetParams {
924///     fn metadata() -> &'static Metadata {
925///         static METADATA: Metadata = Metadata::new("");
926///         &METADATA
927///     }
928///     #[allow(refining_impl_trait)]
929///     async fn extract(
930///         req: &'ex mut Request,
931///         depot: &'ex mut Depot,
932///     ) -> Result<Self, salvo_core::http::ParseError> {
933///         salvo_core::serde::from_request(req, depot, Self::metadata()).await
934///     }
935///     #[allow(refining_impl_trait)]
936///     async fn extract_with_arg(
937///         req: &'ex mut Request,
938///         depot: &'ex mut Depot,
939///         _arg: &str,
940///     ) -> Result<Self, salvo_core::http::ParseError> {
941///         Self::extract(req, depot).await
942///     }
943/// }
944///
945/// impl EndpointArgRegister for PetParams {
946///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
947///         operation
948///             .parameters
949///             .append(&mut PetParams::to_parameters(components));
950///     }
951/// }
952/// ```
953/// [derive]: derive.ToParameters.html
954pub trait ToParameters<'de>: Extractible<'de> {
955    /// Provide [`Vec`] of [`Parameter`]s to caller. The result is used in `salvo-oapi-macros`
956    /// library to provide OpenAPI parameter information for the endpoint using the parameters.
957    fn to_parameters(components: &mut Components) -> Parameters;
958}
959
960/// Trait used to give [`Parameter`] information for OpenAPI.
961pub trait ToParameter {
962    /// Returns a `Parameter`.
963    fn to_parameter(components: &mut Components) -> Parameter;
964}
965
966/// This trait is implemented to document a type (like an enum) which can represent
967/// request body, to be used in operation.
968///
969/// # Examples
970///
971/// ```
972/// use std::collections::BTreeMap;
973///
974/// use salvo_oapi::{
975///     Components, Content, EndpointArgRegister, Operation, RequestBody, ToRequestBody, ToSchema,
976/// };
977/// use serde::Deserialize;
978///
979/// #[derive(ToSchema, Deserialize, Debug)]
980/// struct MyPayload {
981///     name: String,
982/// }
983///
984/// impl ToRequestBody for MyPayload {
985///     fn to_request_body(components: &mut Components) -> RequestBody {
986///         RequestBody::new().add_content(
987///             "application/json",
988///             Content::new(MyPayload::to_schema(components)),
989///         )
990///     }
991/// }
992/// impl EndpointArgRegister for MyPayload {
993///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
994///         operation.request_body = Some(Self::to_request_body(components));
995///     }
996/// }
997/// ```
998pub trait ToRequestBody {
999    /// Returns `RequestBody`.
1000    fn to_request_body(components: &mut Components) -> RequestBody;
1001}
1002
1003/// This trait is implemented to document a type (like an enum) which can represent multiple
1004/// responses, to be used in operation.
1005///
1006/// # Examples
1007///
1008/// ```
1009/// use std::collections::BTreeMap;
1010///
1011/// use salvo_oapi::{Components, RefOr, Response, Responses, ToResponses};
1012///
1013/// enum MyResponse {
1014///     Ok,
1015///     NotFound,
1016/// }
1017///
1018/// impl ToResponses for MyResponse {
1019///     fn to_responses(_components: &mut Components) -> Responses {
1020///         Responses::new()
1021///             .response("200", Response::new("Ok"))
1022///             .response("404", Response::new("Not Found"))
1023///     }
1024/// }
1025/// ```
1026pub trait ToResponses {
1027    /// Returns an ordered map of response codes to responses.
1028    fn to_responses(components: &mut Components) -> Responses;
1029}
1030
1031impl<C> ToResponses for writing::Json<C>
1032where
1033    C: ToSchema,
1034{
1035    fn to_responses(components: &mut Components) -> Responses {
1036        Responses::new().response(
1037            "200",
1038            Response::new("Response json format data")
1039                .add_content("application/json", Content::new(C::to_schema(components))),
1040        )
1041    }
1042}
1043
1044impl ToResponses for StatusError {
1045    fn to_responses(components: &mut Components) -> Responses {
1046        let mut responses = Responses::new();
1047        let errors = vec![
1048            Self::bad_request(),
1049            Self::unauthorized(),
1050            Self::payment_required(),
1051            Self::forbidden(),
1052            Self::not_found(),
1053            Self::method_not_allowed(),
1054            Self::not_acceptable(),
1055            Self::proxy_authentication_required(),
1056            Self::request_timeout(),
1057            Self::conflict(),
1058            Self::gone(),
1059            Self::length_required(),
1060            Self::precondition_failed(),
1061            Self::payload_too_large(),
1062            Self::uri_too_long(),
1063            Self::unsupported_media_type(),
1064            Self::range_not_satisfiable(),
1065            Self::expectation_failed(),
1066            Self::im_a_teapot(),
1067            Self::misdirected_request(),
1068            Self::unprocessable_entity(),
1069            Self::locked(),
1070            Self::failed_dependency(),
1071            Self::upgrade_required(),
1072            Self::precondition_required(),
1073            Self::too_many_requests(),
1074            Self::request_header_fields_toolarge(),
1075            Self::unavailable_for_legalreasons(),
1076            Self::internal_server_error(),
1077            Self::not_implemented(),
1078            Self::bad_gateway(),
1079            Self::service_unavailable(),
1080            Self::gateway_timeout(),
1081            Self::http_version_not_supported(),
1082            Self::variant_also_negotiates(),
1083            Self::insufficient_storage(),
1084            Self::loop_detected(),
1085            Self::not_extended(),
1086            Self::network_authentication_required(),
1087        ];
1088        for Self { code, brief, .. } in errors {
1089            responses.insert(
1090                code.as_str(),
1091                Response::new(brief).add_content(
1092                    "application/json",
1093                    Content::new(Self::to_schema(components)),
1094                ),
1095            )
1096        }
1097        responses
1098    }
1099}
1100impl ToResponses for salvo_core::Error {
1101    fn to_responses(components: &mut Components) -> Responses {
1102        StatusError::to_responses(components)
1103    }
1104}
1105
1106/// This trait is implemented to document a type which represents a single response which can be
1107/// referenced or reused as a component in multiple operations.
1108///
1109/// _`ToResponse`_ trait can also be derived with [`#[derive(ToResponse)]`][derive].
1110///
1111/// # Examples
1112///
1113/// ```
1114/// use salvo_oapi::{Components, RefOr, Response, ToResponse};
1115///
1116/// struct MyResponse;
1117/// impl ToResponse for MyResponse {
1118///     fn to_response(_components: &mut Components) -> RefOr<Response> {
1119///         Response::new("My Response").into()
1120///     }
1121/// }
1122/// ```
1123///
1124/// [derive]: derive.ToResponse.html
1125pub trait ToResponse {
1126    /// Returns a tuple of response component name (to be referenced) to a response.
1127    fn to_response(components: &mut Components) -> RefOr<crate::Response>;
1128}
1129
1130impl<C> ToResponse for writing::Json<C>
1131where
1132    C: ToSchema,
1133{
1134    fn to_response(components: &mut Components) -> RefOr<Response> {
1135        let schema = <C as ToSchema>::to_schema(components);
1136        Response::new("Response with json format data")
1137            .add_content("application/json", Content::new(schema))
1138            .into()
1139    }
1140}
1141
1142#[cfg(test)]
1143mod tests {
1144    use assert_json_diff::assert_json_eq;
1145    use serde_json::json;
1146
1147    use super::*;
1148
1149    #[test]
1150    fn test_primitive_schema() {
1151        let mut components = Components::new();
1152
1153        // Format expectations differ based on whether "non-strict-integers" feature is enabled.
1154        // With the feature: each integer type gets its own format (int8, uint8, int16, etc.)
1155        // Without: smaller integers collapse to int32/int64 per OpenAPI convention.
1156        let non_strict = cfg!(feature = "non-strict-integers");
1157
1158        for (name, schema, value) in [
1159            (
1160                "i8",
1161                i8::to_schema(&mut components),
1162                if non_strict {
1163                    json!({"type": "integer", "format": "int8"})
1164                } else {
1165                    json!({"type": "integer", "format": "int32"})
1166                },
1167            ),
1168            (
1169                "i16",
1170                i16::to_schema(&mut components),
1171                if non_strict {
1172                    json!({"type": "integer", "format": "int16"})
1173                } else {
1174                    json!({"type": "integer", "format": "int32"})
1175                },
1176            ),
1177            (
1178                "i32",
1179                i32::to_schema(&mut components),
1180                json!({"type": "integer", "format": "int32"}),
1181            ),
1182            (
1183                "i64",
1184                i64::to_schema(&mut components),
1185                json!({"type": "integer", "format": "int64"}),
1186            ),
1187            (
1188                "i128",
1189                i128::to_schema(&mut components),
1190                json!({"type": "integer"}),
1191            ),
1192            (
1193                "isize",
1194                isize::to_schema(&mut components),
1195                json!({"type": "integer"}),
1196            ),
1197            (
1198                "u8",
1199                u8::to_schema(&mut components),
1200                if non_strict {
1201                    json!({"type": "integer", "format": "uint8", "minimum": 0})
1202                } else {
1203                    json!({"type": "integer", "format": "int32", "minimum": 0})
1204                },
1205            ),
1206            (
1207                "u16",
1208                u16::to_schema(&mut components),
1209                if non_strict {
1210                    json!({"type": "integer", "format": "uint16", "minimum": 0})
1211                } else {
1212                    json!({"type": "integer", "format": "int32", "minimum": 0})
1213                },
1214            ),
1215            (
1216                "u32",
1217                u32::to_schema(&mut components),
1218                if non_strict {
1219                    json!({"type": "integer", "format": "uint32", "minimum": 0})
1220                } else {
1221                    json!({"type": "integer", "format": "int32", "minimum": 0})
1222                },
1223            ),
1224            (
1225                "u64",
1226                u64::to_schema(&mut components),
1227                if non_strict {
1228                    json!({"type": "integer", "format": "uint64", "minimum": 0})
1229                } else {
1230                    json!({"type": "integer", "format": "int64", "minimum": 0})
1231                },
1232            ),
1233            (
1234                "u128",
1235                u128::to_schema(&mut components),
1236                json!({"type": "integer", "minimum": 0}),
1237            ),
1238            (
1239                "usize",
1240                usize::to_schema(&mut components),
1241                json!({"type": "integer", "minimum": 0}),
1242            ),
1243            (
1244                "bool",
1245                bool::to_schema(&mut components),
1246                json!({"type": "boolean"}),
1247            ),
1248            (
1249                "str",
1250                str::to_schema(&mut components),
1251                json!({"type": "string"}),
1252            ),
1253            (
1254                "String",
1255                String::to_schema(&mut components),
1256                json!({"type": "string"}),
1257            ),
1258            (
1259                "char",
1260                char::to_schema(&mut components),
1261                json!({"type": "string"}),
1262            ),
1263            (
1264                "f32",
1265                f32::to_schema(&mut components),
1266                json!({"type": "number", "format": "float"}),
1267            ),
1268            (
1269                "f64",
1270                f64::to_schema(&mut components),
1271                json!({"type": "number", "format": "double"}),
1272            ),
1273        ] {
1274            println!(
1275                "{name}: {json}",
1276                json = serde_json::to_string(&schema).unwrap()
1277            );
1278            let schema = serde_json::to_value(schema).unwrap();
1279            assert_json_eq!(schema, value);
1280        }
1281    }
1282}