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_to_schema_primitive!(
328    std::ffi::OsStr,
329    std::ffi::OsString,
330    std::path::Path,
331    std::path::PathBuf
332);
333impl_to_schema!(&std::ffi::OsStr);
334impl_to_schema!(&std::path::Path);
335
336impl ToSchema for std::net::IpAddr {
337    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
338        crate::RefOr::Type(Schema::OneOf(
339            OneOf::default()
340                .item(std::net::Ipv4Addr::to_schema(components))
341                .item(std::net::Ipv6Addr::to_schema(components)),
342        ))
343    }
344}
345impl ComposeSchema for std::net::IpAddr {
346    fn compose(
347        components: &mut Components,
348        _generics: Vec<RefOr<schema::Schema>>,
349    ) -> RefOr<schema::Schema> {
350        Self::to_schema(components)
351    }
352}
353
354#[cfg(feature = "chrono")]
355impl_to_schema_primitive!(chrono::NaiveDate, chrono::Duration, chrono::NaiveDateTime);
356#[cfg(feature = "chrono")]
357impl<T: chrono::TimeZone> ToSchema for chrono::DateTime<T> {
358    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
359        schema!(#[inline] DateTime<T>).into()
360    }
361}
362#[cfg(feature = "chrono")]
363impl<T: chrono::TimeZone> ComposeSchema for chrono::DateTime<T> {
364    fn compose(
365        _components: &mut Components,
366        _generics: Vec<RefOr<schema::Schema>>,
367    ) -> RefOr<schema::Schema> {
368        schema!(#[inline] DateTime<T>).into()
369    }
370}
371#[cfg(feature = "compact_str")]
372impl_to_schema_primitive!(compact_str::CompactString);
373#[cfg(any(feature = "decimal", feature = "decimal-float"))]
374impl_to_schema!(rust_decimal::Decimal);
375#[cfg(feature = "url")]
376impl_to_schema!(url::Url);
377#[cfg(feature = "uuid")]
378impl_to_schema!(uuid::Uuid);
379#[cfg(feature = "ulid")]
380impl_to_schema!(ulid::Ulid);
381#[cfg(feature = "time")]
382impl_to_schema_primitive!(
383    time::Date,
384    time::PrimitiveDateTime,
385    time::OffsetDateTime,
386    time::Duration
387);
388#[cfg(feature = "smallvec")]
389impl<T: ToSchema + smallvec::Array> ToSchema for smallvec::SmallVec<T> {
390    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
391        schema!(#[inline] smallvec::SmallVec<T>).into()
392    }
393}
394#[cfg(feature = "smallvec")]
395impl<T: ComposeSchema + smallvec::Array> ComposeSchema for smallvec::SmallVec<T> {
396    fn compose(
397        components: &mut Components,
398        generics: Vec<RefOr<schema::Schema>>,
399    ) -> RefOr<schema::Schema> {
400        let t_schema = generics
401            .first()
402            .cloned()
403            .unwrap_or_else(|| T::compose(components, vec![]));
404        schema::Array::new().items(t_schema).into()
405    }
406}
407#[cfg(feature = "indexmap")]
408impl<K: ToSchema, V: ToSchema> ToSchema for indexmap::IndexMap<K, V> {
409    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
410        schema!(#[inline] indexmap::IndexMap<K, V>).into()
411    }
412}
413#[cfg(feature = "indexmap")]
414impl<K: ComposeSchema, V: ComposeSchema> ComposeSchema for indexmap::IndexMap<K, V> {
415    fn compose(
416        components: &mut Components,
417        generics: Vec<RefOr<schema::Schema>>,
418    ) -> RefOr<schema::Schema> {
419        let v_schema = generics
420            .get(1)
421            .cloned()
422            .unwrap_or_else(|| V::compose(components, vec![]));
423        schema::Object::new().additional_properties(v_schema).into()
424    }
425}
426
427impl<T: ToSchema> ToSchema for Vec<T> {
428    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
429        schema!(#[inline] Vec<T>).into()
430    }
431}
432impl<T: ComposeSchema> ComposeSchema for Vec<T> {
433    fn compose(
434        components: &mut Components,
435        generics: Vec<RefOr<schema::Schema>>,
436    ) -> RefOr<schema::Schema> {
437        let t_schema = generics
438            .first()
439            .cloned()
440            .unwrap_or_else(|| T::compose(components, vec![]));
441        schema::Array::new().items(t_schema).into()
442    }
443}
444
445impl<T: ToSchema> ToSchema for LinkedList<T> {
446    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
447        schema!(#[inline] LinkedList<T>).into()
448    }
449}
450impl<T: ComposeSchema> ComposeSchema for LinkedList<T> {
451    fn compose(
452        components: &mut Components,
453        generics: Vec<RefOr<schema::Schema>>,
454    ) -> RefOr<schema::Schema> {
455        let t_schema = generics
456            .first()
457            .cloned()
458            .unwrap_or_else(|| T::compose(components, vec![]));
459        schema::Array::new().items(t_schema).into()
460    }
461}
462
463impl<T: ToSchema> ToSchema for HashSet<T> {
464    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
465        schema::Array::new()
466            .items(T::to_schema(components))
467            .unique_items(true)
468            .into()
469    }
470}
471impl<T: ComposeSchema> ComposeSchema for HashSet<T> {
472    fn compose(
473        components: &mut Components,
474        generics: Vec<RefOr<schema::Schema>>,
475    ) -> RefOr<schema::Schema> {
476        let t_schema = generics
477            .first()
478            .cloned()
479            .unwrap_or_else(|| T::compose(components, vec![]));
480        schema::Array::new()
481            .items(t_schema)
482            .unique_items(true)
483            .into()
484    }
485}
486
487impl<T: ToSchema> ToSchema for BTreeSet<T> {
488    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
489        schema::Array::new()
490            .items(T::to_schema(components))
491            .unique_items(true)
492            .into()
493    }
494}
495impl<T: ComposeSchema> ComposeSchema for BTreeSet<T> {
496    fn compose(
497        components: &mut Components,
498        generics: Vec<RefOr<schema::Schema>>,
499    ) -> RefOr<schema::Schema> {
500        let t_schema = generics
501            .first()
502            .cloned()
503            .unwrap_or_else(|| T::compose(components, vec![]));
504        schema::Array::new()
505            .items(t_schema)
506            .unique_items(true)
507            .into()
508    }
509}
510
511#[cfg(feature = "indexmap")]
512impl<T: ToSchema> ToSchema for indexmap::IndexSet<T> {
513    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
514        schema::Array::new()
515            .items(T::to_schema(components))
516            .unique_items(true)
517            .into()
518    }
519}
520#[cfg(feature = "indexmap")]
521impl<T: ComposeSchema> ComposeSchema for indexmap::IndexSet<T> {
522    fn compose(
523        components: &mut Components,
524        generics: Vec<RefOr<schema::Schema>>,
525    ) -> RefOr<schema::Schema> {
526        let t_schema = generics
527            .first()
528            .cloned()
529            .unwrap_or_else(|| T::compose(components, vec![]));
530        schema::Array::new()
531            .items(t_schema)
532            .unique_items(true)
533            .into()
534    }
535}
536
537impl<T: ToSchema> ToSchema for Box<T> {
538    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
539        T::to_schema(components)
540    }
541}
542impl<T: ComposeSchema> ComposeSchema for Box<T> {
543    fn compose(
544        components: &mut Components,
545        generics: Vec<RefOr<schema::Schema>>,
546    ) -> RefOr<schema::Schema> {
547        T::compose(components, generics)
548    }
549}
550
551impl<T: ToSchema + ToOwned> ToSchema for std::borrow::Cow<'_, T> {
552    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
553        T::to_schema(components)
554    }
555}
556impl<T: ComposeSchema + ToOwned> ComposeSchema for std::borrow::Cow<'_, T> {
557    fn compose(
558        components: &mut Components,
559        generics: Vec<RefOr<schema::Schema>>,
560    ) -> RefOr<schema::Schema> {
561        T::compose(components, generics)
562    }
563}
564
565impl<T: ToSchema> ToSchema for std::cell::RefCell<T> {
566    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
567        T::to_schema(components)
568    }
569}
570impl<T: ComposeSchema> ComposeSchema for std::cell::RefCell<T> {
571    fn compose(
572        components: &mut Components,
573        generics: Vec<RefOr<schema::Schema>>,
574    ) -> RefOr<schema::Schema> {
575        T::compose(components, generics)
576    }
577}
578
579impl<T: ToSchema> ToSchema for std::rc::Rc<T> {
580    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
581        T::to_schema(components)
582    }
583}
584impl<T: ComposeSchema> ComposeSchema for std::rc::Rc<T> {
585    fn compose(
586        components: &mut Components,
587        generics: Vec<RefOr<schema::Schema>>,
588    ) -> RefOr<schema::Schema> {
589        T::compose(components, generics)
590    }
591}
592
593impl<T: ToSchema> ToSchema for std::sync::Arc<T> {
594    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
595        T::to_schema(components)
596    }
597}
598impl<T: ComposeSchema> ComposeSchema for std::sync::Arc<T> {
599    fn compose(
600        components: &mut Components,
601        generics: Vec<RefOr<schema::Schema>>,
602    ) -> RefOr<schema::Schema> {
603        T::compose(components, generics)
604    }
605}
606
607impl<T: ToSchema> ToSchema for [T] {
608    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
609        schema!(
610            #[inline]
611            [T]
612        )
613        .into()
614    }
615}
616impl<T: ComposeSchema> ComposeSchema for [T] {
617    fn compose(
618        components: &mut Components,
619        generics: Vec<RefOr<schema::Schema>>,
620    ) -> RefOr<schema::Schema> {
621        let t_schema = generics
622            .first()
623            .cloned()
624            .unwrap_or_else(|| T::compose(components, vec![]));
625        schema::Array::new().items(t_schema).into()
626    }
627}
628
629impl<T: ToSchema, const N: usize> ToSchema for [T; N] {
630    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
631        schema!(
632            #[inline]
633            [T; N]
634        )
635        .into()
636    }
637}
638impl<T: ComposeSchema, const N: usize> ComposeSchema for [T; N] {
639    fn compose(
640        components: &mut Components,
641        generics: Vec<RefOr<schema::Schema>>,
642    ) -> RefOr<schema::Schema> {
643        let t_schema = generics
644            .first()
645            .cloned()
646            .unwrap_or_else(|| T::compose(components, vec![]));
647        schema::Array::new().items(t_schema).into()
648    }
649}
650
651impl<T: ToSchema> ToSchema for &[T] {
652    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
653        schema!(
654            #[inline]
655            &[T]
656        )
657        .into()
658    }
659}
660impl<T: ComposeSchema> ComposeSchema for &[T] {
661    fn compose(
662        components: &mut Components,
663        generics: Vec<RefOr<schema::Schema>>,
664    ) -> RefOr<schema::Schema> {
665        let t_schema = generics
666            .first()
667            .cloned()
668            .unwrap_or_else(|| T::compose(components, vec![]));
669        schema::Array::new().items(t_schema).into()
670    }
671}
672
673impl<T: ToSchema> ToSchema for Option<T> {
674    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
675        schema!(#[inline] Option<T>).into()
676    }
677}
678impl<T: ComposeSchema> ComposeSchema for Option<T> {
679    fn compose(
680        components: &mut Components,
681        generics: Vec<RefOr<schema::Schema>>,
682    ) -> RefOr<schema::Schema> {
683        let t_schema = generics
684            .first()
685            .cloned()
686            .unwrap_or_else(|| T::compose(components, vec![]));
687        schema::OneOf::new()
688            .item(t_schema)
689            .item(schema::Object::new().schema_type(schema::BasicType::Null))
690            .into()
691    }
692}
693
694impl<T> ToSchema for PhantomData<T> {
695    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
696        Schema::Object(Box::default()).into()
697    }
698}
699impl<T> ComposeSchema for PhantomData<T> {
700    fn compose(
701        _components: &mut Components,
702        _generics: Vec<RefOr<schema::Schema>>,
703    ) -> RefOr<schema::Schema> {
704        Schema::Object(Box::default()).into()
705    }
706}
707
708impl<K: ToSchema, V: ToSchema> ToSchema for BTreeMap<K, V> {
709    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
710        schema!(#[inline]BTreeMap<K, V>).into()
711    }
712}
713impl<K: ComposeSchema, V: ComposeSchema> ComposeSchema for BTreeMap<K, V> {
714    fn compose(
715        components: &mut Components,
716        generics: Vec<RefOr<schema::Schema>>,
717    ) -> RefOr<schema::Schema> {
718        let v_schema = generics
719            .get(1)
720            .cloned()
721            .unwrap_or_else(|| V::compose(components, vec![]));
722        schema::Object::new().additional_properties(v_schema).into()
723    }
724}
725
726impl<K: ToSchema, V: ToSchema> ToSchema for HashMap<K, V> {
727    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
728        schema!(#[inline]HashMap<K, V>).into()
729    }
730}
731impl<K: ComposeSchema, V: ComposeSchema> ComposeSchema for HashMap<K, V> {
732    fn compose(
733        components: &mut Components,
734        generics: Vec<RefOr<schema::Schema>>,
735    ) -> RefOr<schema::Schema> {
736        let v_schema = generics
737            .get(1)
738            .cloned()
739            .unwrap_or_else(|| V::compose(components, vec![]));
740        schema::Object::new().additional_properties(v_schema).into()
741    }
742}
743
744impl ToSchema for StatusError {
745    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
746        let name = crate::naming::assign_name::<Self>(Default::default());
747        let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
748        if !components.schemas.contains_key(&name) {
749            components.schemas.insert(name.clone(), ref_or.clone());
750            let schema = Schema::from(
751                Object::new()
752                    .property("code", u16::to_schema(components))
753                    .required("code")
754                    .required("name")
755                    .property("name", String::to_schema(components))
756                    .required("brief")
757                    .property("brief", String::to_schema(components))
758                    .required("detail")
759                    .property("detail", String::to_schema(components))
760                    .property("cause", String::to_schema(components)),
761            );
762            components.schemas.insert(name, schema);
763        }
764        ref_or
765    }
766}
767impl ComposeSchema for StatusError {
768    fn compose(
769        components: &mut Components,
770        _generics: Vec<RefOr<schema::Schema>>,
771    ) -> RefOr<schema::Schema> {
772        Self::to_schema(components)
773    }
774}
775
776impl ToSchema for salvo_core::Error {
777    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
778        StatusError::to_schema(components)
779    }
780}
781impl ComposeSchema for salvo_core::Error {
782    fn compose(
783        components: &mut Components,
784        _generics: Vec<RefOr<schema::Schema>>,
785    ) -> RefOr<schema::Schema> {
786        Self::to_schema(components)
787    }
788}
789
790impl<T, E> ToSchema for Result<T, E>
791where
792    T: ToSchema,
793    E: ToSchema,
794{
795    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
796        let name = crate::naming::assign_name::<StatusError>(Default::default());
797        let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
798        if !components.schemas.contains_key(&name) {
799            components.schemas.insert(name.clone(), ref_or.clone());
800            let schema = OneOf::new()
801                .item(T::to_schema(components))
802                .item(E::to_schema(components));
803            components.schemas.insert(name, schema);
804        }
805        ref_or
806    }
807}
808impl<T, E> ComposeSchema for Result<T, E>
809where
810    T: ComposeSchema,
811    E: ComposeSchema,
812{
813    fn compose(
814        components: &mut Components,
815        generics: Vec<RefOr<schema::Schema>>,
816    ) -> RefOr<schema::Schema> {
817        let t_schema = generics
818            .first()
819            .cloned()
820            .unwrap_or_else(|| T::compose(components, vec![]));
821        let e_schema = generics
822            .get(1)
823            .cloned()
824            .unwrap_or_else(|| E::compose(components, vec![]));
825        OneOf::new().item(t_schema).item(e_schema).into()
826    }
827}
828
829impl ToSchema for serde_json::Value {
830    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
831        Schema::Object(Box::default()).into()
832    }
833}
834impl ComposeSchema for serde_json::Value {
835    fn compose(
836        _components: &mut Components,
837        _generics: Vec<RefOr<schema::Schema>>,
838    ) -> RefOr<schema::Schema> {
839        Schema::Object(Box::default()).into()
840    }
841}
842
843impl ToSchema for serde_json::Map<String, serde_json::Value> {
844    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
845        Schema::Object(Box::new(schema::Object::new())).into()
846    }
847}
848impl ComposeSchema for serde_json::Map<String, serde_json::Value> {
849    fn compose(
850        _components: &mut Components,
851        _generics: Vec<RefOr<schema::Schema>>,
852    ) -> RefOr<schema::Schema> {
853        Schema::Object(Box::new(schema::Object::new())).into()
854    }
855}
856
857impl ToSchema for serde_json::value::RawValue {
858    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
859        Schema::Object(Box::default()).into()
860    }
861}
862impl ComposeSchema for serde_json::value::RawValue {
863    fn compose(
864        _components: &mut Components,
865        _generics: Vec<RefOr<schema::Schema>>,
866    ) -> RefOr<schema::Schema> {
867        Schema::Object(Box::default()).into()
868    }
869}
870
871/// Trait used to convert implementing type to OpenAPI parameters.
872///
873/// This trait is [derivable][derive] for structs which are used to describe `path` or `query`
874/// parameters. For more details of `#[derive(ToParameters)]` refer to [derive
875/// documentation][derive].
876///
877/// # Examples
878///
879/// Derive [`ToParameters`] implementation. This example will fail to compile because
880/// [`ToParameters`] cannot be used alone and it need to be used together with endpoint using the
881/// params as well. See [derive documentation][derive] for more details.
882/// ```
883/// use salvo_core::prelude::*;
884/// use salvo_oapi::{Components, EndpointArgRegister, Operation, ToParameters};
885/// use serde::Deserialize;
886///
887/// #[derive(Deserialize, ToParameters)]
888/// struct PetParams {
889///     /// Id of pet
890///     id: i64,
891///     /// Name of pet
892///     name: String,
893/// }
894/// ```
895///
896/// Roughly equal manual implementation of [`ToParameters`] trait.
897/// ```
898/// # use serde::Deserialize;
899/// # use salvo_oapi::{ToParameters, EndpointArgRegister, Components, Operation};
900/// # use salvo_core::prelude::*;
901/// # use salvo_core::extract::{Metadata, Extractible};
902/// #[derive(Deserialize)]
903/// # struct PetParams {
904/// #    /// Id of pet
905/// #    id: i64,
906/// #    /// Name of pet
907/// #    name: String,
908/// # }
909/// impl<'de> salvo_oapi::ToParameters<'de> for PetParams {
910///     fn to_parameters(_components: &mut Components) -> salvo_oapi::Parameters {
911///         salvo_oapi::Parameters::new()
912///             .parameter(
913///                 salvo_oapi::Parameter::new("id")
914///                     .required(salvo_oapi::Required::True)
915///                     .location(salvo_oapi::ParameterIn::Path)
916///                     .description("Id of pet")
917///                     .schema(
918///                         salvo_oapi::Object::new()
919///                             .schema_type(salvo_oapi::schema::BasicType::Integer)
920///                             .format(salvo_oapi::SchemaFormat::KnownFormat(
921///                                 salvo_oapi::schema::KnownFormat::Int64,
922///                             )),
923///                     ),
924///             )
925///             .parameter(
926///                 salvo_oapi::Parameter::new("name")
927///                     .required(salvo_oapi::Required::True)
928///                     .location(salvo_oapi::ParameterIn::Query)
929///                     .description("Name of pet")
930///                     .schema(
931///                         salvo_oapi::Object::new()
932///                             .schema_type(salvo_oapi::schema::BasicType::String),
933///                     ),
934///             )
935///     }
936/// }
937///
938/// impl<'ex> Extractible<'ex> for PetParams {
939///     fn metadata() -> &'static Metadata {
940///         static METADATA: Metadata = Metadata::new("");
941///         &METADATA
942///     }
943///     #[allow(refining_impl_trait)]
944///     async fn extract(
945///         req: &'ex mut Request,
946///         depot: &'ex mut Depot,
947///     ) -> Result<Self, salvo_core::http::ParseError> {
948///         salvo_core::serde::from_request(req, depot, Self::metadata()).await
949///     }
950///     #[allow(refining_impl_trait)]
951///     async fn extract_with_arg(
952///         req: &'ex mut Request,
953///         depot: &'ex mut Depot,
954///         _arg: &str,
955///     ) -> Result<Self, salvo_core::http::ParseError> {
956///         Self::extract(req, depot).await
957///     }
958/// }
959///
960/// impl EndpointArgRegister for PetParams {
961///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
962///         operation
963///             .parameters
964///             .append(&mut PetParams::to_parameters(components));
965///     }
966/// }
967/// ```
968/// [derive]: derive.ToParameters.html
969pub trait ToParameters<'de>: Extractible<'de> {
970    /// Provide [`Vec`] of [`Parameter`]s to caller. The result is used in `salvo-oapi-macros`
971    /// library to provide OpenAPI parameter information for the endpoint using the parameters.
972    fn to_parameters(components: &mut Components) -> Parameters;
973}
974
975/// Trait used to give [`Parameter`] information for OpenAPI.
976pub trait ToParameter {
977    /// Returns a `Parameter`.
978    fn to_parameter(components: &mut Components) -> Parameter;
979}
980
981/// This trait is implemented to document a type (like an enum) which can represent
982/// request body, to be used in operation.
983///
984/// # Examples
985///
986/// ```
987/// use std::collections::BTreeMap;
988///
989/// use salvo_oapi::{
990///     Components, Content, EndpointArgRegister, Operation, RequestBody, ToRequestBody, ToSchema,
991/// };
992/// use serde::Deserialize;
993///
994/// #[derive(ToSchema, Deserialize, Debug)]
995/// struct MyPayload {
996///     name: String,
997/// }
998///
999/// impl ToRequestBody for MyPayload {
1000///     fn to_request_body(components: &mut Components) -> RequestBody {
1001///         RequestBody::new().add_content(
1002///             "application/json",
1003///             Content::new(MyPayload::to_schema(components)),
1004///         )
1005///     }
1006/// }
1007/// impl EndpointArgRegister for MyPayload {
1008///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
1009///         operation.request_body = Some(Self::to_request_body(components));
1010///     }
1011/// }
1012/// ```
1013pub trait ToRequestBody {
1014    /// Returns `RequestBody`.
1015    fn to_request_body(components: &mut Components) -> RequestBody;
1016}
1017
1018/// This trait is implemented to document a type (like an enum) which can represent multiple
1019/// responses, to be used in operation.
1020///
1021/// # Examples
1022///
1023/// ```
1024/// use std::collections::BTreeMap;
1025///
1026/// use salvo_oapi::{Components, RefOr, Response, Responses, ToResponses};
1027///
1028/// enum MyResponse {
1029///     Ok,
1030///     NotFound,
1031/// }
1032///
1033/// impl ToResponses for MyResponse {
1034///     fn to_responses(_components: &mut Components) -> Responses {
1035///         Responses::new()
1036///             .response("200", Response::new("Ok"))
1037///             .response("404", Response::new("Not Found"))
1038///     }
1039/// }
1040/// ```
1041pub trait ToResponses {
1042    /// Returns an ordered map of response codes to responses.
1043    fn to_responses(components: &mut Components) -> Responses;
1044}
1045
1046impl<C> ToResponses for writing::Json<C>
1047where
1048    C: ToSchema,
1049{
1050    fn to_responses(components: &mut Components) -> Responses {
1051        Responses::new().response(
1052            "200",
1053            Response::new("JSON response body")
1054                .add_content("application/json", Content::new(C::to_schema(components))),
1055        )
1056    }
1057}
1058
1059impl ToResponses for StatusError {
1060    fn to_responses(components: &mut Components) -> Responses {
1061        let mut responses = Responses::new();
1062        let errors = vec![
1063            Self::bad_request(),
1064            Self::unauthorized(),
1065            Self::payment_required(),
1066            Self::forbidden(),
1067            Self::not_found(),
1068            Self::method_not_allowed(),
1069            Self::not_acceptable(),
1070            Self::proxy_authentication_required(),
1071            Self::request_timeout(),
1072            Self::conflict(),
1073            Self::gone(),
1074            Self::length_required(),
1075            Self::precondition_failed(),
1076            Self::payload_too_large(),
1077            Self::uri_too_long(),
1078            Self::unsupported_media_type(),
1079            Self::range_not_satisfiable(),
1080            Self::expectation_failed(),
1081            Self::im_a_teapot(),
1082            Self::misdirected_request(),
1083            Self::unprocessable_entity(),
1084            Self::locked(),
1085            Self::failed_dependency(),
1086            Self::upgrade_required(),
1087            Self::precondition_required(),
1088            Self::too_many_requests(),
1089            Self::request_header_fields_too_large(),
1090            Self::unavailable_for_legal_reasons(),
1091            Self::internal_server_error(),
1092            Self::not_implemented(),
1093            Self::bad_gateway(),
1094            Self::service_unavailable(),
1095            Self::gateway_timeout(),
1096            Self::http_version_not_supported(),
1097            Self::variant_also_negotiates(),
1098            Self::insufficient_storage(),
1099            Self::loop_detected(),
1100            Self::not_extended(),
1101            Self::network_authentication_required(),
1102        ];
1103        for Self { code, brief, .. } in errors {
1104            responses.insert(
1105                code.as_str(),
1106                Response::new(brief).add_content(
1107                    "application/json",
1108                    Content::new(Self::to_schema(components)),
1109                ),
1110            )
1111        }
1112        responses
1113    }
1114}
1115impl ToResponses for salvo_core::Error {
1116    fn to_responses(components: &mut Components) -> Responses {
1117        StatusError::to_responses(components)
1118    }
1119}
1120
1121/// This trait is implemented to document a type which represents a single response which can be
1122/// referenced or reused as a component in multiple operations.
1123///
1124/// _`ToResponse`_ trait can also be derived with [`#[derive(ToResponse)]`][derive].
1125///
1126/// # Examples
1127///
1128/// ```
1129/// use salvo_oapi::{Components, RefOr, Response, ToResponse};
1130///
1131/// struct MyResponse;
1132/// impl ToResponse for MyResponse {
1133///     fn to_response(_components: &mut Components) -> RefOr<Response> {
1134///         Response::new("My Response").into()
1135///     }
1136/// }
1137/// ```
1138///
1139/// [derive]: derive.ToResponse.html
1140pub trait ToResponse {
1141    /// Returns a tuple of response component name (to be referenced) to a response.
1142    fn to_response(components: &mut Components) -> RefOr<crate::Response>;
1143}
1144
1145impl<C> ToResponse for writing::Json<C>
1146where
1147    C: ToSchema,
1148{
1149    fn to_response(components: &mut Components) -> RefOr<Response> {
1150        let schema = <C as ToSchema>::to_schema(components);
1151        Response::new("Response with json format data")
1152            .add_content("application/json", Content::new(schema))
1153            .into()
1154    }
1155}
1156
1157#[cfg(test)]
1158mod tests {
1159    use assert_json_diff::assert_json_eq;
1160    use serde_json::json;
1161
1162    use super::*;
1163
1164    #[test]
1165    fn test_primitive_schema() {
1166        let mut components = Components::new();
1167
1168        // Format expectations differ based on whether "non-strict-integers" feature is enabled.
1169        // With the feature: each integer type gets its own format (int8, uint8, int16, etc.)
1170        // Without: smaller integers collapse to int32/int64 per OpenAPI convention.
1171        let non_strict = cfg!(feature = "non-strict-integers");
1172
1173        for (name, schema, value) in [
1174            (
1175                "i8",
1176                i8::to_schema(&mut components),
1177                if non_strict {
1178                    json!({"type": "integer", "format": "int8"})
1179                } else {
1180                    json!({"type": "integer", "format": "int32"})
1181                },
1182            ),
1183            (
1184                "i16",
1185                i16::to_schema(&mut components),
1186                if non_strict {
1187                    json!({"type": "integer", "format": "int16"})
1188                } else {
1189                    json!({"type": "integer", "format": "int32"})
1190                },
1191            ),
1192            (
1193                "i32",
1194                i32::to_schema(&mut components),
1195                json!({"type": "integer", "format": "int32"}),
1196            ),
1197            (
1198                "i64",
1199                i64::to_schema(&mut components),
1200                json!({"type": "integer", "format": "int64"}),
1201            ),
1202            (
1203                "i128",
1204                i128::to_schema(&mut components),
1205                json!({"type": "integer"}),
1206            ),
1207            (
1208                "isize",
1209                isize::to_schema(&mut components),
1210                json!({"type": "integer"}),
1211            ),
1212            (
1213                "u8",
1214                u8::to_schema(&mut components),
1215                if non_strict {
1216                    json!({"type": "integer", "format": "uint8", "minimum": 0})
1217                } else {
1218                    json!({"type": "integer", "format": "int32", "minimum": 0})
1219                },
1220            ),
1221            (
1222                "u16",
1223                u16::to_schema(&mut components),
1224                if non_strict {
1225                    json!({"type": "integer", "format": "uint16", "minimum": 0})
1226                } else {
1227                    json!({"type": "integer", "format": "int32", "minimum": 0})
1228                },
1229            ),
1230            (
1231                "u32",
1232                u32::to_schema(&mut components),
1233                if non_strict {
1234                    json!({"type": "integer", "format": "uint32", "minimum": 0})
1235                } else {
1236                    json!({"type": "integer", "format": "int32", "minimum": 0})
1237                },
1238            ),
1239            (
1240                "u64",
1241                u64::to_schema(&mut components),
1242                if non_strict {
1243                    json!({"type": "integer", "format": "uint64", "minimum": 0})
1244                } else {
1245                    json!({"type": "integer", "format": "int64", "minimum": 0})
1246                },
1247            ),
1248            (
1249                "u128",
1250                u128::to_schema(&mut components),
1251                json!({"type": "integer", "minimum": 0}),
1252            ),
1253            (
1254                "usize",
1255                usize::to_schema(&mut components),
1256                json!({"type": "integer", "minimum": 0}),
1257            ),
1258            (
1259                "bool",
1260                bool::to_schema(&mut components),
1261                json!({"type": "boolean"}),
1262            ),
1263            (
1264                "str",
1265                str::to_schema(&mut components),
1266                json!({"type": "string"}),
1267            ),
1268            (
1269                "String",
1270                String::to_schema(&mut components),
1271                json!({"type": "string"}),
1272            ),
1273            (
1274                "char",
1275                char::to_schema(&mut components),
1276                json!({"type": "string"}),
1277            ),
1278            (
1279                "OsStr",
1280                std::ffi::OsStr::to_schema(&mut components),
1281                json!({"type": "string"}),
1282            ),
1283            (
1284                "&OsStr",
1285                <&std::ffi::OsStr>::to_schema(&mut components),
1286                json!({"type": "string"}),
1287            ),
1288            (
1289                "OsString",
1290                std::ffi::OsString::to_schema(&mut components),
1291                json!({"type": "string"}),
1292            ),
1293            (
1294                "Path",
1295                std::path::Path::to_schema(&mut components),
1296                json!({"type": "string"}),
1297            ),
1298            (
1299                "&Path",
1300                <&std::path::Path>::to_schema(&mut components),
1301                json!({"type": "string"}),
1302            ),
1303            (
1304                "PathBuf",
1305                std::path::PathBuf::to_schema(&mut components),
1306                json!({"type": "string"}),
1307            ),
1308            (
1309                "f32",
1310                f32::to_schema(&mut components),
1311                json!({"type": "number", "format": "float"}),
1312            ),
1313            (
1314                "f64",
1315                f64::to_schema(&mut components),
1316                json!({"type": "number", "format": "double"}),
1317            ),
1318        ] {
1319            println!(
1320                "{name}: {json}",
1321                json = serde_json::to_string(&schema).unwrap()
1322            );
1323            let schema = serde_json::to_value(schema).unwrap();
1324            assert_json_eq!(schema, value);
1325        }
1326    }
1327}