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