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;
185    pub use serde_json;
186}
187
188#[rustfmt::skip]
189impl_to_schema_primitive!(
190    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool, f32, f64, String, str, char
191);
192impl_to_schema!(&str);
193
194impl_to_schema!(std::net::Ipv4Addr);
195impl_to_schema!(std::net::Ipv6Addr);
196
197impl ToSchema for std::net::IpAddr {
198    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
199        crate::RefOr::Type(Schema::OneOf(
200            OneOf::default()
201                .item(std::net::Ipv4Addr::to_schema(components))
202                .item(std::net::Ipv6Addr::to_schema(components)),
203        ))
204    }
205}
206
207#[cfg(feature = "chrono")]
208impl_to_schema_primitive!(chrono::NaiveDate, chrono::Duration, chrono::NaiveDateTime);
209#[cfg(feature = "chrono")]
210impl<T: chrono::TimeZone> ToSchema for chrono::DateTime<T> {
211    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
212        schema!(#[inline] DateTime<T>).into()
213    }
214}
215#[cfg(feature = "compact_str")]
216impl_to_schema_primitive!(compact_str::CompactString);
217#[cfg(any(feature = "decimal", feature = "decimal-float"))]
218impl_to_schema!(rust_decimal::Decimal);
219#[cfg(feature = "url")]
220impl_to_schema!(url::Url);
221#[cfg(feature = "uuid")]
222impl_to_schema!(uuid::Uuid);
223#[cfg(feature = "ulid")]
224impl_to_schema!(ulid::Ulid);
225#[cfg(feature = "time")]
226impl_to_schema_primitive!(
227    time::Date,
228    time::PrimitiveDateTime,
229    time::OffsetDateTime,
230    time::Duration
231);
232#[cfg(feature = "smallvec")]
233impl<T: ToSchema + smallvec::Array> ToSchema for smallvec::SmallVec<T> {
234    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
235        schema!(#[inline] smallvec::SmallVec<T>).into()
236    }
237}
238#[cfg(feature = "indexmap")]
239impl<K: ToSchema, V: ToSchema> ToSchema for indexmap::IndexMap<K, V> {
240    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
241        schema!(#[inline] indexmap::IndexMap<K, V>).into()
242    }
243}
244
245impl<T: ToSchema> ToSchema for Vec<T> {
246    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
247        schema!(#[inline] Vec<T>).into()
248    }
249}
250
251impl<T: ToSchema> ToSchema for LinkedList<T> {
252    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
253        schema!(#[inline] LinkedList<T>).into()
254    }
255}
256
257impl<T: ToSchema> ToSchema for [T] {
258    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
259        schema!(
260            #[inline]
261            [T]
262        )
263        .into()
264    }
265}
266impl<T: ToSchema, const N: usize> ToSchema for [T; N] {
267    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
268        schema!(
269            #[inline]
270            [T; N]
271        )
272        .into()
273    }
274}
275
276impl<T: ToSchema> ToSchema for &[T] {
277    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
278        schema!(
279            #[inline]
280            &[T]
281        )
282        .into()
283    }
284}
285
286impl<T: ToSchema> ToSchema for Option<T> {
287    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
288        schema!(#[inline] Option<T>).into()
289    }
290}
291
292impl<T> ToSchema for PhantomData<T> {
293    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
294        Schema::Object(Box::default()).into()
295    }
296}
297
298impl<K: ToSchema, V: ToSchema> ToSchema for BTreeMap<K, V> {
299    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
300        schema!(#[inline]BTreeMap<K, V>).into()
301    }
302}
303
304impl<K: ToSchema, V: ToSchema> ToSchema for HashMap<K, V> {
305    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
306        schema!(#[inline]HashMap<K, V>).into()
307    }
308}
309
310impl ToSchema for StatusError {
311    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
312        let name = crate::naming::assign_name::<Self>(Default::default());
313        let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
314        if !components.schemas.contains_key(&name) {
315            components.schemas.insert(name.clone(), ref_or.clone());
316            let schema = Schema::from(
317                Object::new()
318                    .property("code", u16::to_schema(components))
319                    .required("code")
320                    .required("name")
321                    .property("name", String::to_schema(components))
322                    .required("brief")
323                    .property("brief", String::to_schema(components))
324                    .required("detail")
325                    .property("detail", String::to_schema(components))
326                    .property("cause", String::to_schema(components)),
327            );
328            components.schemas.insert(name, schema);
329        }
330        ref_or
331    }
332}
333impl ToSchema for salvo_core::Error {
334    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
335        StatusError::to_schema(components)
336    }
337}
338
339impl<T, E> ToSchema for Result<T, E>
340where
341    T: ToSchema,
342    E: ToSchema,
343{
344    fn to_schema(components: &mut Components) -> RefOr<schema::Schema> {
345        let name = crate::naming::assign_name::<StatusError>(Default::default());
346        let ref_or = crate::RefOr::Ref(crate::Ref::new(format!("#/components/schemas/{name}")));
347        if !components.schemas.contains_key(&name) {
348            components.schemas.insert(name.clone(), ref_or.clone());
349            let schema = OneOf::new()
350                .item(T::to_schema(components))
351                .item(E::to_schema(components));
352            components.schemas.insert(name, schema);
353        }
354        ref_or
355    }
356}
357
358impl ToSchema for serde_json::Value {
359    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
360        Schema::Object(Box::default()).into()
361    }
362}
363impl ToSchema for serde_json::Map<String, serde_json::Value> {
364    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
365        schema!(#[inline]HashMap<K, V>).into()
366    }
367}
368
369/// Trait used to convert implementing type to OpenAPI parameters.
370///
371/// This trait is [derivable][derive] for structs which are used to describe `path` or `query`
372/// parameters. For more details of `#[derive(ToParameters)]` refer to [derive
373/// documentation][derive].
374///
375/// # Examples
376///
377/// Derive [`ToParameters`] implementation. This example will fail to compile because
378/// [`ToParameters`] cannot be used alone and it need to be used together with endpoint using the
379/// params as well. See [derive documentation][derive] for more details.
380/// ```
381/// use salvo_core::prelude::*;
382/// use salvo_oapi::{Components, EndpointArgRegister, Operation, ToParameters};
383/// use serde::Deserialize;
384///
385/// #[derive(Deserialize, ToParameters)]
386/// struct PetParams {
387///     /// Id of pet
388///     id: i64,
389///     /// Name of pet
390///     name: String,
391/// }
392/// ```
393///
394/// Roughly equal manual implementation of [`ToParameters`] trait.
395/// ```
396/// # use serde::Deserialize;
397/// # use salvo_oapi::{ToParameters, EndpointArgRegister, Components, Operation};
398/// # use salvo_core::prelude::*;
399/// # use salvo_core::extract::{Metadata, Extractible};
400/// #[derive(Deserialize)]
401/// # struct PetParams {
402/// #    /// Id of pet
403/// #    id: i64,
404/// #    /// Name of pet
405/// #    name: String,
406/// # }
407/// impl<'de> salvo_oapi::ToParameters<'de> for PetParams {
408///     fn to_parameters(_components: &mut Components) -> salvo_oapi::Parameters {
409///         salvo_oapi::Parameters::new()
410///             .parameter(
411///                 salvo_oapi::Parameter::new("id")
412///                     .required(salvo_oapi::Required::True)
413///                     .parameter_in(salvo_oapi::ParameterIn::Path)
414///                     .description("Id of pet")
415///                     .schema(
416///                         salvo_oapi::Object::new()
417///                             .schema_type(salvo_oapi::schema::BasicType::Integer)
418///                             .format(salvo_oapi::SchemaFormat::KnownFormat(
419///                                 salvo_oapi::schema::KnownFormat::Int64,
420///                             )),
421///                     ),
422///             )
423///             .parameter(
424///                 salvo_oapi::Parameter::new("name")
425///                     .required(salvo_oapi::Required::True)
426///                     .parameter_in(salvo_oapi::ParameterIn::Query)
427///                     .description("Name of pet")
428///                     .schema(
429///                         salvo_oapi::Object::new()
430///                             .schema_type(salvo_oapi::schema::BasicType::String),
431///                     ),
432///             )
433///     }
434/// }
435///
436/// impl<'ex> Extractible<'ex> for PetParams {
437///     fn metadata() -> &'static Metadata {
438///         static METADATA: Metadata = Metadata::new("");
439///         &METADATA
440///     }
441///     #[allow(refining_impl_trait)]
442///     async fn extract(
443///         req: &'ex mut Request,
444///         depot: &'ex mut Depot,
445///     ) -> Result<Self, salvo_core::http::ParseError> {
446///         salvo_core::serde::from_request(req, depot, Self::metadata()).await
447///     }
448///     #[allow(refining_impl_trait)]
449///     async fn extract_with_arg(
450///         req: &'ex mut Request,
451///         depot: &'ex mut Depot,
452///         _arg: &str,
453///     ) -> Result<Self, salvo_core::http::ParseError> {
454///         Self::extract(req, depot).await
455///     }
456/// }
457///
458/// impl EndpointArgRegister for PetParams {
459///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
460///         operation
461///             .parameters
462///             .append(&mut PetParams::to_parameters(components));
463///     }
464/// }
465/// ```
466/// [derive]: derive.ToParameters.html
467pub trait ToParameters<'de>: Extractible<'de> {
468    /// Provide [`Vec`] of [`Parameter`]s to caller. The result is used in `salvo-oapi-macros`
469    /// library to provide OpenAPI parameter information for the endpoint using the parameters.
470    fn to_parameters(components: &mut Components) -> Parameters;
471}
472
473/// Trait used to give [`Parameter`] information for OpenAPI.
474pub trait ToParameter {
475    /// Returns a `Parameter`.
476    fn to_parameter(components: &mut Components) -> Parameter;
477}
478
479/// This trait is implemented to document a type (like an enum) which can represent
480/// request body, to be used in operation.
481///
482/// # Examples
483///
484/// ```
485/// use std::collections::BTreeMap;
486///
487/// use salvo_oapi::{
488///     Components, Content, EndpointArgRegister, Operation, RequestBody, ToRequestBody, ToSchema,
489/// };
490/// use serde::Deserialize;
491///
492/// #[derive(ToSchema, Deserialize, Debug)]
493/// struct MyPayload {
494///     name: String,
495/// }
496///
497/// impl ToRequestBody for MyPayload {
498///     fn to_request_body(components: &mut Components) -> RequestBody {
499///         RequestBody::new().add_content(
500///             "application/json",
501///             Content::new(MyPayload::to_schema(components)),
502///         )
503///     }
504/// }
505/// impl EndpointArgRegister for MyPayload {
506///     fn register(components: &mut Components, operation: &mut Operation, _arg: &str) {
507///         operation.request_body = Some(Self::to_request_body(components));
508///     }
509/// }
510/// ```
511pub trait ToRequestBody {
512    /// Returns `RequestBody`.
513    fn to_request_body(components: &mut Components) -> RequestBody;
514}
515
516/// This trait is implemented to document a type (like an enum) which can represent multiple
517/// responses, to be used in operation.
518///
519/// # Examples
520///
521/// ```
522/// use std::collections::BTreeMap;
523///
524/// use salvo_oapi::{Components, RefOr, Response, Responses, ToResponses};
525///
526/// enum MyResponse {
527///     Ok,
528///     NotFound,
529/// }
530///
531/// impl ToResponses for MyResponse {
532///     fn to_responses(_components: &mut Components) -> Responses {
533///         Responses::new()
534///             .response("200", Response::new("Ok"))
535///             .response("404", Response::new("Not Found"))
536///     }
537/// }
538/// ```
539pub trait ToResponses {
540    /// Returns an ordered map of response codes to responses.
541    fn to_responses(components: &mut Components) -> Responses;
542}
543
544impl<C> ToResponses for writing::Json<C>
545where
546    C: ToSchema,
547{
548    fn to_responses(components: &mut Components) -> Responses {
549        Responses::new().response(
550            "200",
551            Response::new("Response json format data")
552                .add_content("application/json", Content::new(C::to_schema(components))),
553        )
554    }
555}
556
557impl ToResponses for StatusError {
558    fn to_responses(components: &mut Components) -> Responses {
559        let mut responses = Responses::new();
560        let errors = vec![
561            Self::bad_request(),
562            Self::unauthorized(),
563            Self::payment_required(),
564            Self::forbidden(),
565            Self::not_found(),
566            Self::method_not_allowed(),
567            Self::not_acceptable(),
568            Self::proxy_authentication_required(),
569            Self::request_timeout(),
570            Self::conflict(),
571            Self::gone(),
572            Self::length_required(),
573            Self::precondition_failed(),
574            Self::payload_too_large(),
575            Self::uri_too_long(),
576            Self::unsupported_media_type(),
577            Self::range_not_satisfiable(),
578            Self::expectation_failed(),
579            Self::im_a_teapot(),
580            Self::misdirected_request(),
581            Self::unprocessable_entity(),
582            Self::locked(),
583            Self::failed_dependency(),
584            Self::upgrade_required(),
585            Self::precondition_required(),
586            Self::too_many_requests(),
587            Self::request_header_fields_toolarge(),
588            Self::unavailable_for_legalreasons(),
589            Self::internal_server_error(),
590            Self::not_implemented(),
591            Self::bad_gateway(),
592            Self::service_unavailable(),
593            Self::gateway_timeout(),
594            Self::http_version_not_supported(),
595            Self::variant_also_negotiates(),
596            Self::insufficient_storage(),
597            Self::loop_detected(),
598            Self::not_extended(),
599            Self::network_authentication_required(),
600        ];
601        for Self { code, brief, .. } in errors {
602            responses.insert(
603                code.as_str(),
604                Response::new(brief).add_content(
605                    "application/json",
606                    Content::new(Self::to_schema(components)),
607                ),
608            )
609        }
610        responses
611    }
612}
613impl ToResponses for salvo_core::Error {
614    fn to_responses(components: &mut Components) -> Responses {
615        StatusError::to_responses(components)
616    }
617}
618
619/// This trait is implemented to document a type which represents a single response which can be
620/// referenced or reused as a component in multiple operations.
621///
622/// _`ToResponse`_ trait can also be derived with [`#[derive(ToResponse)]`][derive].
623///
624/// # Examples
625///
626/// ```
627/// use salvo_oapi::{Components, RefOr, Response, ToResponse};
628///
629/// struct MyResponse;
630/// impl ToResponse for MyResponse {
631///     fn to_response(_components: &mut Components) -> RefOr<Response> {
632///         Response::new("My Response").into()
633///     }
634/// }
635/// ```
636///
637/// [derive]: derive.ToResponse.html
638pub trait ToResponse {
639    /// Returns a tuple of response component name (to be referenced) to a response.
640    fn to_response(components: &mut Components) -> RefOr<crate::Response>;
641}
642
643impl<C> ToResponse for writing::Json<C>
644where
645    C: ToSchema,
646{
647    fn to_response(components: &mut Components) -> RefOr<Response> {
648        let schema = <C as ToSchema>::to_schema(components);
649        Response::new("Response with json format data")
650            .add_content("application/json", Content::new(schema))
651            .into()
652    }
653}
654
655#[cfg(test)]
656mod tests {
657    use assert_json_diff::assert_json_eq;
658    use serde_json::json;
659
660    use super::*;
661
662    #[test]
663    fn test_primitive_schema() {
664        let mut components = Components::new();
665        for (name, schema, value) in [
666            (
667                "i8",
668                i8::to_schema(&mut components),
669                json!({"type": "integer", "format": "int8"}),
670            ),
671            (
672                "i16",
673                i16::to_schema(&mut components),
674                json!({"type": "integer", "format": "int16"}),
675            ),
676            (
677                "i32",
678                i32::to_schema(&mut components),
679                json!({"type": "integer", "format": "int32"}),
680            ),
681            (
682                "i64",
683                i64::to_schema(&mut components),
684                json!({"type": "integer", "format": "int64"}),
685            ),
686            (
687                "i128",
688                i128::to_schema(&mut components),
689                json!({"type": "integer"}),
690            ),
691            (
692                "isize",
693                isize::to_schema(&mut components),
694                json!({"type": "integer"}),
695            ),
696            (
697                "u8",
698                u8::to_schema(&mut components),
699                json!({"type": "integer", "format": "uint8", "minimum": 0.0}),
700            ),
701            (
702                "u16",
703                u16::to_schema(&mut components),
704                json!({"type": "integer", "format": "uint16", "minimum": 0.0}),
705            ),
706            (
707                "u32",
708                u32::to_schema(&mut components),
709                json!({"type": "integer", "format": "uint32", "minimum": 0.0}),
710            ),
711            (
712                "u64",
713                u64::to_schema(&mut components),
714                json!({"type": "integer", "format": "uint64", "minimum": 0.0}),
715            ),
716            (
717                "u128",
718                u128::to_schema(&mut components),
719                json!({"type": "integer", "minimum": 0.0}),
720            ),
721            (
722                "usize",
723                usize::to_schema(&mut components),
724                json!({"type": "integer", "minimum": 0.0 }),
725            ),
726            (
727                "bool",
728                bool::to_schema(&mut components),
729                json!({"type": "boolean"}),
730            ),
731            (
732                "str",
733                str::to_schema(&mut components),
734                json!({"type": "string"}),
735            ),
736            (
737                "String",
738                String::to_schema(&mut components),
739                json!({"type": "string"}),
740            ),
741            (
742                "char",
743                char::to_schema(&mut components),
744                json!({"type": "string"}),
745            ),
746            (
747                "f32",
748                f32::to_schema(&mut components),
749                json!({"type": "number", "format": "float"}),
750            ),
751            (
752                "f64",
753                f64::to_schema(&mut components),
754                json!({"type": "number", "format": "double"}),
755            ),
756        ] {
757            println!(
758                "{name}: {json}",
759                json = serde_json::to_string(&schema).unwrap()
760            );
761            let schema = serde_json::to_value(schema).unwrap();
762            assert_json_eq!(schema, value);
763        }
764    }
765}