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
6#[macro_use]
7mod cfg;
8
9mod openapi;
10pub use openapi::*;
11
12#[doc = include_str!("../docs/endpoint.md")]
13pub mod endpoint;
14pub use endpoint::{Endpoint, EndpointArgRegister, EndpointOutRegister, EndpointRegistry};
15pub mod extract;
16mod routing;
17pub use routing::RouterExt;
18/// Module for name schemas.
19pub mod naming;
20
21cfg_feature! {
22    #![feature ="swagger-ui"]
23    pub mod swagger_ui;
24}
25cfg_feature! {
26    #![feature ="scalar"]
27    pub mod scalar;
28}
29cfg_feature! {
30    #![feature ="rapidoc"]
31    pub mod rapidoc;
32}
33cfg_feature! {
34    #![feature ="redoc"]
35    pub mod redoc;
36}
37
38use std::collections::{BTreeMap, HashMap, LinkedList};
39use std::marker::PhantomData;
40
41use salvo_core::extract::Extractible;
42use salvo_core::http::StatusError;
43use salvo_core::writing;
44#[doc = include_str!("../docs/derive_to_parameters.md")]
45pub use salvo_oapi_macros::ToParameters;
46#[doc = include_str!("../docs/derive_to_response.md")]
47pub use salvo_oapi_macros::ToResponse;
48#[doc = include_str!("../docs/derive_to_responses.md")]
49pub use salvo_oapi_macros::ToResponses;
50#[doc = include_str!("../docs/derive_to_schema.md")]
51pub use salvo_oapi_macros::ToSchema;
52#[doc = include_str!("../docs/endpoint.md")]
53pub use salvo_oapi_macros::endpoint;
54pub(crate) use salvo_oapi_macros::schema;
55
56use crate::oapi::openapi::schema::OneOf;
57
58// https://github.com/bkchr/proc-macro-crate/issues/10
59extern crate self as salvo_oapi;
60
61/// Trait for implementing OpenAPI Schema object.
62///
63/// Generated schemas can be referenced or reused in path operations.
64///
65/// This trait is derivable and can be used with `[#derive]` attribute. For a details of
66/// `#[derive(ToSchema)]` refer to [derive documentation][derive].
67///
68/// [derive]: derive.ToSchema.html
69///
70/// # Examples
71///
72/// Use `#[derive]` to implement `ToSchema` trait.
73/// ```
74/// use salvo_oapi::ToSchema;
75/// #[derive(ToSchema)]
76/// #[salvo(schema(example = json!({"name": "bob the cat", "id": 1})))]
77/// struct Pet {
78///     id: u64,
79///     name: String,
80///     age: Option<i32>,
81/// }
82/// ```
83///
84/// Following manual implementation is equal to above derive one.
85/// ```
86/// use salvo_oapi::{Components, ToSchema, RefOr, Schema, SchemaFormat, BasicType, SchemaType, KnownFormat, Object};
87/// # struct Pet {
88/// #     id: u64,
89/// #     name: String,
90/// #     age: Option<i32>,
91/// # }
92/// #
93/// impl ToSchema for Pet {
94///     fn to_schema(components: &mut Components) -> RefOr<Schema> {
95///         Object::new()
96///             .property(
97///                 "id",
98///                 Object::new()
99///                     .schema_type(BasicType::Integer)
100///                     .format(SchemaFormat::KnownFormat(
101///                         KnownFormat::Int64,
102///                     )),
103///             )
104///             .required("id")
105///             .property(
106///                 "name",
107///                 Object::new()
108///                     .schema_type(BasicType::String),
109///             )
110///             .required("name")
111///             .property(
112///                 "age",
113///                 Object::new()
114///                     .schema_type(BasicType::Integer)
115///                     .format(SchemaFormat::KnownFormat(
116///                         KnownFormat::Int32,
117///                     )),
118///             )
119///             .example(serde_json::json!({
120///               "name":"bob the cat","id":1
121///             }))
122///             .into()
123///     }
124/// }
125/// ```
126pub trait ToSchema {
127    /// Returns a tuple of name and schema or reference to a schema that can be referenced by the
128    /// name or inlined directly to responses, request bodies or parameters.
129    fn to_schema(components: &mut Components) -> RefOr<schema::Schema>;
130
131    // /// Optional set of alias schemas for the [`ToSchema::schema`].
132    // ///
133    // /// Typically there is no need to manually implement this method but it is instead
134    // implemented /// by derive [`macro@ToSchema`] when `#[aliases(...)]` attribute is defined.
135    // fn aliases() -> Vec<schema::Schema> {
136    //     Vec::new()
137    // }
138}
139
140/// Represents _`nullable`_ type.
141///
142/// This can be used anywhere where "nothing" needs to be evaluated.
143/// This will serialize to _`null`_ in JSON and [`schema::empty`] is used to create the
144/// [`schema::Schema`] for the type.
145pub type TupleUnit = ();
146
147impl ToSchema for TupleUnit {
148    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
149        schema::empty().into()
150    }
151}
152
153macro_rules! impl_to_schema {
154    ($ty:path) => {
155        impl_to_schema!( @impl_schema $ty );
156    };
157    (&$ty:path) => {
158        impl_to_schema!( @impl_schema &$ty );
159    };
160    (@impl_schema $($tt:tt)*) => {
161        impl ToSchema for $($tt)* {
162            fn to_schema(_components: &mut Components) -> crate::RefOr<crate::schema::Schema> {
163                 schema!( $($tt)* ).into()
164            }
165        }
166    };
167}
168
169macro_rules! impl_to_schema_primitive {
170    ($($tt:path),*) => {
171        $( impl_to_schema!( $tt ); )*
172    };
173}
174
175// Create `salvo-oapi` module so we can use `salvo-oapi-macros` directly
176// from `salvo-oapi` crate. ONLY FOR INTERNAL USE!
177#[doc(hidden)]
178pub mod oapi {
179    pub use super::*;
180}
181
182#[doc(hidden)]
183pub mod __private {
184    pub use {inventory, serde_json};
185}
186
187#[rustfmt::skip]
188impl_to_schema_primitive!(
189    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool, f32, f64, String, str, char
190);
191impl_to_schema!(&str);
192
193impl_to_schema!(std::net::Ipv4Addr);
194impl_to_schema!(std::net::Ipv6Addr);
195
196impl ToSchema for std::net::IpAddr {
197    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
198        crate::RefOr::Type(Schema::OneOf(
199            OneOf::default()
200                .item(std::net::Ipv4Addr::to_schema(components))
201                .item(std::net::Ipv6Addr::to_schema(components)),
202        ))
203    }
204}
205
206#[cfg(feature = "chrono")]
207impl_to_schema_primitive!(chrono::NaiveDate, chrono::Duration, chrono::NaiveDateTime);
208#[cfg(feature = "chrono")]
209impl<T: chrono::TimeZone> ToSchema for chrono::DateTime<T> {
210    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
211        schema!(#[inline] DateTime<T>).into()
212    }
213}
214#[cfg(feature = "compact_str")]
215impl_to_schema_primitive!(compact_str::CompactString);
216#[cfg(any(feature = "decimal", feature = "decimal-float"))]
217impl_to_schema!(rust_decimal::Decimal);
218#[cfg(feature = "url")]
219impl_to_schema!(url::Url);
220#[cfg(feature = "uuid")]
221impl_to_schema!(uuid::Uuid);
222#[cfg(feature = "ulid")]
223impl_to_schema!(ulid::Ulid);
224#[cfg(feature = "time")]
225impl_to_schema_primitive!(
226    time::Date,
227    time::PrimitiveDateTime,
228    time::OffsetDateTime,
229    time::Duration
230);
231#[cfg(feature = "smallvec")]
232impl<T: ToSchema + smallvec::Array> ToSchema for smallvec::SmallVec<T> {
233    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
234        schema!(#[inline] smallvec::SmallVec<T>).into()
235    }
236}
237#[cfg(feature = "indexmap")]
238impl<K: ToSchema, V: ToSchema> ToSchema for indexmap::IndexMap<K, V> {
239    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
240        schema!(#[inline] indexmap::IndexMap<K, V>).into()
241    }
242}
243
244impl<T: ToSchema> ToSchema for Vec<T> {
245    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
246        schema!(#[inline] Vec<T>).into()
247    }
248}
249
250impl<T: ToSchema> ToSchema for LinkedList<T> {
251    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
252        schema!(#[inline] LinkedList<T>).into()
253    }
254}
255
256impl<T: ToSchema> ToSchema for [T] {
257    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
258        schema!(
259            #[inline]
260            [T]
261        )
262        .into()
263    }
264}
265impl<T: ToSchema, const N: usize> ToSchema for [T; N] {
266    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
267        schema!(
268            #[inline]
269            [T; N]
270        )
271        .into()
272    }
273}
274
275impl<T: ToSchema> ToSchema for &[T] {
276    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
277        schema!(
278            #[inline]
279            &[T]
280        )
281        .into()
282    }
283}
284
285impl<T: ToSchema> ToSchema for Option<T> {
286    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
287        schema!(#[inline] Option<T>).into()
288    }
289}
290
291impl<T> ToSchema for PhantomData<T> {
292    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
293        Schema::Object(Box::default()).into()
294    }
295}
296
297impl<K: ToSchema, V: ToSchema> ToSchema for BTreeMap<K, V> {
298    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
299        schema!(#[inline]BTreeMap<K, V>).into()
300    }
301}
302
303impl<K: ToSchema, V: ToSchema> ToSchema for HashMap<K, V> {
304    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
305        schema!(#[inline]HashMap<K, V>).into()
306    }
307}
308
309impl ToSchema for StatusError {
310    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
311        let name = crate::naming::assign_name::<Self>(Default::default());
312        let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
313        if !components.schemas.contains_key(&name) {
314            components.schemas.insert(name.clone(), ref_or.clone());
315            let schema = Schema::from(
316                Object::new()
317                    .property("code", u16::to_schema(components))
318                    .required("code")
319                    .required("name")
320                    .property("name", String::to_schema(components))
321                    .required("brief")
322                    .property("brief", String::to_schema(components))
323                    .required("detail")
324                    .property("detail", String::to_schema(components))
325                    .property("cause", String::to_schema(components)),
326            );
327            components.schemas.insert(name, schema);
328        }
329        ref_or
330    }
331}
332impl ToSchema for salvo_core::Error {
333    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
334        StatusError::to_schema(components)
335    }
336}
337
338impl<T, E> ToSchema for Result<T, E>
339where
340    T: ToSchema,
341    E: ToSchema,
342{
343    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
344        let name = crate::naming::assign_name::<StatusError>(Default::default());
345        let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
346        if !components.schemas.contains_key(&name) {
347            components.schemas.insert(name.clone(), ref_or.clone());
348            let schema = OneOf::new()
349                .item(T::to_schema(components))
350                .item(E::to_schema(components));
351            components.schemas.insert(name, schema);
352        }
353        ref_or
354    }
355}
356
357impl ToSchema for serde_json::Value {
358    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
359        Schema::Object(Box::default()).into()
360    }
361}
362impl ToSchema for serde_json::Map<String, serde_json::Value> {
363    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
364        schema!(#[inline]HashMap<K, V>).into()
365    }
366}
367
368/// Trait used to convert implementing type to OpenAPI parameters.
369///
370/// This trait is [derivable][derive] for structs which are used to describe `path` or `query`
371/// parameters. For more details of `#[derive(ToParameters)]` refer to [derive
372/// documentation][derive].
373///
374/// # Examples
375///
376/// Derive [`ToParameters`] implementation. This example will fail to compile because
377/// [`ToParameters`] cannot be used alone and it need to be used together with endpoint using the
378/// params as well. See [derive documentation][derive] for more details.
379/// ```
380/// use salvo_core::prelude::*;
381/// use salvo_oapi::{Components, EndpointArgRegister, Operation, ToParameters};
382/// use serde::Deserialize;
383///
384/// #[derive(Deserialize, ToParameters)]
385/// struct PetParams {
386///     /// Id of pet
387///     id: i64,
388///     /// Name of pet
389///     name: String,
390/// }
391/// ```
392///
393/// Roughly equal manual implementation of [`ToParameters`] trait.
394/// ```
395/// # use serde::Deserialize;
396/// # use salvo_oapi::{ToParameters, EndpointArgRegister, Components, Operation};
397/// # use salvo_core::prelude::*;
398/// # use salvo_core::extract::{Metadata, Extractible};
399/// #[derive(Deserialize)]
400/// # struct PetParams {
401/// #    /// Id of pet
402/// #    id: i64,
403/// #    /// Name of pet
404/// #    name: String,
405/// # }
406/// impl<'de> salvo_oapi::ToParameters<'de> for PetParams {
407///     fn to_parameters(_components: &mut Components) -> salvo_oapi::Parameters {
408///         salvo_oapi::Parameters::new()
409///             .parameter(
410///                 salvo_oapi::Parameter::new("id")
411///                     .required(salvo_oapi::Required::True)
412///                     .parameter_in(salvo_oapi::ParameterIn::Path)
413///                     .description("Id of pet")
414///                     .schema(
415///                         salvo_oapi::Object::new()
416///                             .schema_type(salvo_oapi::schema::BasicType::Integer)
417///                             .format(salvo_oapi::SchemaFormat::KnownFormat(
418///                                 salvo_oapi::schema::KnownFormat::Int64,
419///                             )),
420///                     ),
421///             )
422///             .parameter(
423///                 salvo_oapi::Parameter::new("name")
424///                     .required(salvo_oapi::Required::True)
425///                     .parameter_in(salvo_oapi::ParameterIn::Query)
426///                     .description("Name of pet")
427///                     .schema(
428///                         salvo_oapi::Object::new()
429///                             .schema_type(salvo_oapi::schema::BasicType::String),
430///                     ),
431///             )
432///     }
433/// }
434///
435/// impl<'ex> Extractible<'ex> for PetParams {
436///     fn metadata() -> &'static Metadata {
437///         static METADATA: Metadata = Metadata::new("");
438///         &METADATA
439///     }
440///     #[allow(refining_impl_trait)]
441///     async fn extract(
442///         req: &'ex mut Request,
443///         depot: &'ex mut Depot,
444///     ) -> Result<Self, salvo_core::http::ParseError> {
445///         salvo_core::serde::from_request(req, depot, Self::metadata()).await
446///     }
447///     #[allow(refining_impl_trait)]
448///     async fn extract_with_arg(
449///         req: &'ex mut Request,
450///         depot: &'ex mut Depot,
451///         _arg: &str,
452///     ) -> Result<Self, salvo_core::http::ParseError> {
453///         Self::extract(req, depot).await
454///     }
455/// }
456///
457/// impl EndpointArgRegister for PetParams {
458///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
459///         operation
460///             .parameters
461///             .append(&mut PetParams::to_parameters(components));
462///     }
463/// }
464/// ```
465/// [derive]: derive.ToParameters.html
466pub trait ToParameters<'de>: Extractible<'de> {
467    /// Provide [`Vec`] of [`Parameter`]s to caller. The result is used in `salvo-oapi-macros`
468    /// library to provide OpenAPI parameter information for the endpoint using the parameters.
469    fn to_parameters(components: &mut Components) -> Parameters;
470}
471
472/// Trait used to give [`Parameter`] information for OpenAPI.
473pub trait ToParameter {
474    /// Returns a `Parameter`.
475    fn to_parameter(components: &mut Components) -> Parameter;
476}
477
478/// This trait is implemented to document a type (like an enum) which can represent
479/// request body, to be used in operation.
480///
481/// # Examples
482///
483/// ```
484/// use std::collections::BTreeMap;
485///
486/// use salvo_oapi::{
487///     Components, Content, EndpointArgRegister, Operation, RequestBody, ToRequestBody, ToSchema,
488/// };
489/// use serde::Deserialize;
490///
491/// #[derive(ToSchema, Deserialize, Debug)]
492/// struct MyPayload {
493///     name: String,
494/// }
495///
496/// impl ToRequestBody for MyPayload {
497///     fn to_request_body(components: &mut Components) -> RequestBody {
498///         RequestBody::new().add_content(
499///             "application/json",
500///             Content::new(MyPayload::to_schema(components)),
501///         )
502///     }
503/// }
504/// impl EndpointArgRegister for MyPayload {
505///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
506///         operation.request_body = Some(Self::to_request_body(components));
507///     }
508/// }
509/// ```
510pub trait ToRequestBody {
511    /// Returns `RequestBody`.
512    fn to_request_body(components: &mut Components) -> RequestBody;
513}
514
515/// This trait is implemented to document a type (like an enum) which can represent multiple
516/// responses, to be used in operation.
517///
518/// # Examples
519///
520/// ```
521/// use std::collections::BTreeMap;
522///
523/// use salvo_oapi::{Components, RefOr, Response, Responses, ToResponses};
524///
525/// enum MyResponse {
526///     Ok,
527///     NotFound,
528/// }
529///
530/// impl ToResponses for MyResponse {
531///     fn to_responses(_components: &mut Components) -> Responses {
532///         Responses::new()
533///             .response("200", Response::new("Ok"))
534///             .response("404", Response::new("Not Found"))
535///     }
536/// }
537/// ```
538pub trait ToResponses {
539    /// Returns an ordered map of response codes to responses.
540    fn to_responses(components: &mut Components) -> Responses;
541}
542
543impl<C> ToResponses for writing::Json<C>
544where
545    C: ToSchema,
546{
547    fn to_responses(components: &mut Components) -> Responses {
548        Responses::new().response(
549            "200",
550            Response::new("Response json format data")
551                .add_content("application/json", Content::new(C::to_schema(components))),
552        )
553    }
554}
555
556impl ToResponses for StatusError {
557    fn to_responses(components: &mut Components) -> Responses {
558        let mut responses = Responses::new();
559        let errors = vec![
560            Self::bad_request(),
561            Self::unauthorized(),
562            Self::payment_required(),
563            Self::forbidden(),
564            Self::not_found(),
565            Self::method_not_allowed(),
566            Self::not_acceptable(),
567            Self::proxy_authentication_required(),
568            Self::request_timeout(),
569            Self::conflict(),
570            Self::gone(),
571            Self::length_required(),
572            Self::precondition_failed(),
573            Self::payload_too_large(),
574            Self::uri_too_long(),
575            Self::unsupported_media_type(),
576            Self::range_not_satisfiable(),
577            Self::expectation_failed(),
578            Self::im_a_teapot(),
579            Self::misdirected_request(),
580            Self::unprocessable_entity(),
581            Self::locked(),
582            Self::failed_dependency(),
583            Self::upgrade_required(),
584            Self::precondition_required(),
585            Self::too_many_requests(),
586            Self::request_header_fields_toolarge(),
587            Self::unavailable_for_legalreasons(),
588            Self::internal_server_error(),
589            Self::not_implemented(),
590            Self::bad_gateway(),
591            Self::service_unavailable(),
592            Self::gateway_timeout(),
593            Self::http_version_not_supported(),
594            Self::variant_also_negotiates(),
595            Self::insufficient_storage(),
596            Self::loop_detected(),
597            Self::not_extended(),
598            Self::network_authentication_required(),
599        ];
600        for Self { code, brief, .. } in errors {
601            responses.insert(
602                code.as_str(),
603                Response::new(brief).add_content(
604                    "application/json",
605                    Content::new(Self::to_schema(components)),
606                ),
607            )
608        }
609        responses
610    }
611}
612impl ToResponses for salvo_core::Error {
613    fn to_responses(components: &mut Components) -> Responses {
614        StatusError::to_responses(components)
615    }
616}
617
618/// This trait is implemented to document a type which represents a single response which can be
619/// referenced or reused as a component in multiple operations.
620///
621/// _`ToResponse`_ trait can also be derived with [`#[derive(ToResponse)]`][derive].
622///
623/// # Examples
624///
625/// ```
626/// use salvo_oapi::{Components, RefOr, Response, ToResponse};
627///
628/// struct MyResponse;
629/// impl ToResponse for MyResponse {
630///     fn to_response(_components: &mut Components) -> RefOr<Response> {
631///         Response::new("My Response").into()
632///     }
633/// }
634/// ```
635///
636/// [derive]: derive.ToResponse.html
637pub trait ToResponse {
638    /// Returns a tuple of response component name (to be referenced) to a response.
639    fn to_response(components: &mut Components) -> RefOr<crate::Response>;
640}
641
642impl<C> ToResponse for writing::Json<C>
643where
644    C: ToSchema,
645{
646    fn to_response(components: &mut Components) -> RefOr<Response> {
647        let schema = <C as ToSchema>::to_schema(components);
648        Response::new("Response with json format data")
649            .add_content("application/json", Content::new(schema))
650            .into()
651    }
652}
653
654#[cfg(test)]
655mod tests {
656    use assert_json_diff::assert_json_eq;
657    use serde_json::json;
658
659    use super::*;
660
661    #[test]
662    fn test_primitive_schema() {
663        let mut components = Components::new();
664        for (name, schema, value) in [
665            (
666                "i8",
667                i8::to_schema(&mut components),
668                json!({"type": "integer", "format": "int8"}),
669            ),
670            (
671                "i16",
672                i16::to_schema(&mut components),
673                json!({"type": "integer", "format": "int16"}),
674            ),
675            (
676                "i32",
677                i32::to_schema(&mut components),
678                json!({"type": "integer", "format": "int32"}),
679            ),
680            (
681                "i64",
682                i64::to_schema(&mut components),
683                json!({"type": "integer", "format": "int64"}),
684            ),
685            (
686                "i128",
687                i128::to_schema(&mut components),
688                json!({"type": "integer"}),
689            ),
690            (
691                "isize",
692                isize::to_schema(&mut components),
693                json!({"type": "integer"}),
694            ),
695            (
696                "u8",
697                u8::to_schema(&mut components),
698                json!({"type": "integer", "format": "uint8", "minimum": 0.0}),
699            ),
700            (
701                "u16",
702                u16::to_schema(&mut components),
703                json!({"type": "integer", "format": "uint16", "minimum": 0.0}),
704            ),
705            (
706                "u32",
707                u32::to_schema(&mut components),
708                json!({"type": "integer", "format": "uint32", "minimum": 0.0}),
709            ),
710            (
711                "u64",
712                u64::to_schema(&mut components),
713                json!({"type": "integer", "format": "uint64", "minimum": 0.0}),
714            ),
715            (
716                "u128",
717                u128::to_schema(&mut components),
718                json!({"type": "integer", "minimum": 0.0}),
719            ),
720            (
721                "usize",
722                usize::to_schema(&mut components),
723                json!({"type": "integer", "minimum": 0.0 }),
724            ),
725            (
726                "bool",
727                bool::to_schema(&mut components),
728                json!({"type": "boolean"}),
729            ),
730            (
731                "str",
732                str::to_schema(&mut components),
733                json!({"type": "string"}),
734            ),
735            (
736                "String",
737                String::to_schema(&mut components),
738                json!({"type": "string"}),
739            ),
740            (
741                "char",
742                char::to_schema(&mut components),
743                json!({"type": "string"}),
744            ),
745            (
746                "f32",
747                f32::to_schema(&mut components),
748                json!({"type": "number", "format": "float"}),
749            ),
750            (
751                "f64",
752                f64::to_schema(&mut components),
753                json!({"type": "number", "format": "double"}),
754            ),
755        ] {
756            println!(
757                "{name}: {json}",
758                json = serde_json::to_string(&schema).unwrap()
759            );
760            let schema = serde_json::to_value(schema).unwrap();
761            assert_json_eq!(schema, value);
762        }
763    }
764}