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