fastapi/
lib.rs

1#![warn(missing_docs)]
2#![warn(rustdoc::broken_intra_doc_links)]
3#![cfg_attr(doc_cfg, feature(doc_cfg))]
4//! Want to have your API documented with OpenAPI? But you don't want to see the
5//! trouble with manual yaml or json tweaking? Would like it to be so easy that it would almost
6//! be like utopic? Don't worry fastapi is just there to fill this gap. It aims to do if not all then
7//! the most of heavy lifting for you enabling you to focus writing the actual API logic instead of
8//! documentation. It aims to be *minimal*, *simple* and *fast*. It uses simple proc macros which
9//! you can use to annotate your code to have items documented.
10//!
11//! Fastapi crate provides autogenerated OpenAPI documentation for Rust REST APIs. It treats
12//! code first approach as a first class citizen and simplifies API documentation by providing
13//! simple macros for generating the documentation from your code.
14//!
15//! It also contains Rust types of OpenAPI spec allowing you to write the OpenAPI spec only using
16//! Rust if auto-generation is not your flavor or does not fit your purpose.
17//!
18//! Long term goal of the library is to be the place to go when OpenAPI documentation is needed in Rust
19//! codebase.
20//!
21//! Fastapi is framework agnostic and could be used together with any web framework or even without one. While
22//! being portable and standalone one of it's key aspects is simple integration with web frameworks.
23//!
24//! Currently fastapi provides simple integration with actix-web framework but is not limited to the actix-web
25//! framework. All functionalities are not restricted to any specific framework.
26//!
27//! # Choose your flavor and document your API with ice cold IPA
28//!
29//! |Flavor|Support|
30//! |--|--|
31//! |[actix-web](https://github.com/actix/actix-web)|Parse path, path parameters and query parameters, recognize request body and response body, [`fastapi-actix-web` bindings](https://docs.rs/fastapi-actix-web). See more at [docs][actix_path]|
32//! |[axum](https://github.com/tokio-rs/axum)|Parse path and query parameters, recognize request body and response body, [`fastapi-axum` bindings](https://docs.rs/fastapi-axum). See more at [docs][axum_path]|
33//! |[rocket](https://github.com/SergioBenitez/Rocket)| Parse path, path parameters and query parameters, recognize request body and response body. See more at [docs][rocket_path]|
34//! |Others*| Plain `fastapi` without extra flavor. This gives you all the basic benefits listed below in **[Features](#features)** section but with little less automation.|
35//!
36//! > Others* = For example [warp](https://github.com/seanmonstar/warp) but could be anything.
37//!
38//! Refer to the existing [examples](https://github.com/nxpkg/fastapi/tree/master/examples) to find out more.
39//!
40//! ## Features
41//!
42//! * OpenAPI 3.1
43//! * Pluggable, easy setup and integration with frameworks.
44//! * No bloat, enable what you need.
45//! * Support for generic types
46//!   * **Note!**<br>
47//!     Tuples, arrays and slices cannot be used as generic arguments on types. Types implementing `ToSchema` manually should not have generic arguments, as
48//!     they are not composeable and will result compile error.
49//! * Automatic schema collection from usages recursively.
50//!   * Request body from either handler function arguments (if supported by framework) or from `request_body` attribute.
51//!   * Response body from response `body` attribute or response `content` attribute.
52//! * Various OpenAPI visualization tools supported out of the box.
53//! * Rust type aliases via [`fastapi-config`][fastapi_config].
54//!
55//! # What's up with the word play?
56//!
57//! The name comes from words `utopic` and `api` where `uto` is the first three letters of _utopic_
58//! and the `ipa` is _api_ reversed. Aaand... `ipa` is also awesome type of beer.
59//!
60//! # Crate Features
61//!
62//! * **`macros`** Enable `fastapi-gen` macros. **This is enabled by default.**
63//! * **`yaml`** Enables **serde_yaml** serialization of OpenAPI objects.
64//! * **`actix_extras`** Enhances [actix-web](https://github.com/actix/actix-web/) integration with being able to
65//!   parse `path`, `path` and `query` parameters from actix web path attribute macros. See [actix extras support][actix_path] or
66//!   [examples](https://github.com/nxpkg/fastapi/tree/master/examples) for more details.
67//! * **`rocket_extras`** Enhances [rocket](https://github.com/SergioBenitez/Rocket) framework integration with being
68//!   able to parse `path`, `path` and `query` parameters from rocket path attribute macros. See [rocket extras support][rocket_path]
69//!   or [examples](https://github.com/nxpkg/fastapi/tree/master/examples) for more details
70//! * **`axum_extras`** Enhances [axum](https://github.com/tokio-rs/axum) framework integration allowing users to use `IntoParams`
71//!   without defining the `parameter_in` attribute. See [axum extras support][axum_path]
72//!   or [examples](https://github.com/nxpkg/fastapi/tree/master/examples) for more details.
73//! * **`debug`** Add extra traits such as debug traits to openapi definitions and elsewhere.
74//! * **`chrono`** Add support for [chrono](https://crates.io/crates/chrono) `DateTime`, `Date`, `NaiveDate`, `NaiveTime` and `Duration`
75//!   types. By default these types are parsed to `string` types with additional `format` information.
76//!   `format: date-time` for `DateTime` and `format: date` for `Date` and `NaiveDate` according
77//!   [RFC3339](https://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14) as `ISO-8601`. To
78//!   override default `string` representation users have to use `value_type` attribute to override the type.
79//!   See [docs](https://docs.rs/fastapi/latest/fastapi/derive.ToSchema.html) for more details.
80//! * **`time`** Add support for [time](https://crates.io/crates/time) `OffsetDateTime`, `PrimitiveDateTime`, `Date`, and `Duration` types.
81//!   By default these types are parsed as `string`. `OffsetDateTime` and `PrimitiveDateTime` will use `date-time` format. `Date` will use
82//!   `date` format and `Duration` will not have any format. To override default `string` representation users have to use `value_type` attribute
83//!   to override the type. See [docs](https://docs.rs/fastapi/latest/fastapi/derive.ToSchema.html) for more details.
84//! * **`decimal`** Add support for [rust_decimal](https://crates.io/crates/rust_decimal) `Decimal` type. **By default**
85//!   it is interpreted as `String`. If you wish to change the format you need to override the type.
86//!   See the `value_type` in [`ToSchema` derive docs][to_schema_derive].
87//! * **`decimal_float`** Add support for [rust_decimal](https://crates.io/crates/rust_decimal) `Decimal` type. **By default**
88//!   it is interpreted as `Number`. This feature is mutually exclusive with **decimal** and allow to change the default type used in your
89//!   documentation for `Decimal` much like `serde_with_float` feature exposed by rust_decimal.
90//! * **`uuid`** Add support for [uuid](https://github.com/uuid-rs/uuid). `Uuid` type will be presented as `String` with
91//!   format `uuid` in OpenAPI spec.
92//! * **`ulid`** Add support for [ulid](https://github.com/dylanhart/ulid-rs). `Ulid` type will be presented as `String` with
93//!   format `ulid` in OpenAPI spec.
94//! * **`url`** Add support for [url](https://github.com/servo/rust-url). `Url` type will be presented as `String` with
95//!   format `uri` in OpenAPI spec.
96//! * **`smallvec`** Add support for [smallvec](https://crates.io/crates/smallvec). `SmallVec` will be treated as `Vec`.
97//! * **`openapi_extensions`** Adds convenience functions for documenting common scenarios, such as JSON request bodies and responses.
98//!   See the [`request_body`](https://docs.rs/fastapi/latest/fastapi/openapi/request_body/index.html) and
99//!   [`response`](https://docs.rs/fastapi/latest/fastapi/openapi/response/index.html) docs for examples.
100//! * **`repr`** Add support for [repr_serde](https://github.com/dtolnay/serde-repr)'s `repr(u*)` and `repr(i*)` attributes to unit type enums for
101//!   C-like enum representation. See [docs](https://docs.rs/fastapi/latest/fastapi/derive.ToSchema.html) for more details.
102//! * **`preserve_order`** Preserve order of properties when serializing the schema for a component.
103//!   When enabled, the properties are listed in order of fields in the corresponding struct definition.
104//!   When disabled, the properties are listed in alphabetical order.
105//! * **`preserve_path_order`** Preserve order of OpenAPI Paths according to order they have been
106//!   introduced to the `#[openapi(paths(...))]` macro attribute. If disabled the paths will be
107//!   ordered in alphabetical order. **However** the operations order under the path **will** be always constant according to
108//!   [specification](https://spec.openapis.org/oas/latest.html#fixed-fields-6)
109//! * **`indexmap`** Add support for [indexmap](https://crates.io/crates/indexmap). When enabled `IndexMap` will be rendered as a map similar to
110//!   `BTreeMap` and `HashMap`.
111//! * **`non_strict_integers`** Add support for non-standard integer formats `int8`, `int16`, `uint8`, `uint16`, `uint32`, and `uint64`.
112//! * **`rc_schema`** Add `ToSchema` support for `Arc<T>` and `Rc<T>` types. **Note!** serde `rc` feature flag must be enabled separately to allow
113//!   serialization and deserialization of `Arc<T>` and `Rc<T>` types. See more about [serde feature flags](https://serde.rs/feature-flags.html).
114//! * **`config`** Enables [`fastapi-config`](https://docs.rs/fastapi-config/) for the project which allows
115//!   defining global configuration options for `fastapi`.
116//!
117//! ### Default Library Support
118//!
119//! * Implicit partial support for `serde` attributes. See [`ToSchema` derive][serde] for more details.
120//! * Support for [http](https://crates.io/crates/http) `StatusCode` in responses.
121//!
122//! # Install
123//!
124//! Add dependency declaration to Cargo.toml.
125//! ```toml
126//! [dependencies]
127//! fastapi = "5"
128//! ```
129//!
130//! # Examples
131//!
132//! _**Create type with `ToSchema` and use it in `#[fastapi::path(...)]` that is registered to the `OpenApi`.**_
133//!
134//! ```rust
135//! use fastapi::{OpenApi, ToSchema};
136//!
137//! #[derive(ToSchema)]
138//! struct Pet {
139//!    id: u64,
140//!    name: String,
141//!    age: Option<i32>,
142//! }
143//! # #[derive(Debug)]
144//! # struct NotFound;
145//! #
146//! # impl std::error::Error for NotFound {}
147//! #
148//! # impl std::fmt::Display for NotFound {
149//! #    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150//! #        f.write_str("NotFound")
151//! #    }
152//! # }
153//!
154//! /// Get pet by id
155//! ///
156//! /// Get pet from database by pet id
157//! #[fastapi::path(
158//!     get,
159//!     path = "/pets/{id}",
160//!     responses(
161//!         (status = 200, description = "Pet found successfully", body = Pet),
162//!         (status = NOT_FOUND, description = "Pet was not found")
163//!     ),
164//!     params(
165//!         ("id" = u64, Path, description = "Pet database id to get Pet for"),
166//!     )
167//! )]
168//! async fn get_pet_by_id(pet_id: u64) -> Result<Pet, NotFound> {
169//!     Ok(Pet {
170//!         id: pet_id,
171//!         age: None,
172//!         name: "lightning".to_string(),
173//!     })
174//! }
175//!
176//! #[derive(OpenApi)]
177//! #[openapi(paths(get_pet_by_id))]
178//! struct ApiDoc;
179//!
180//! println!("{}", ApiDoc::openapi().to_pretty_json().unwrap());
181//! ```
182//!
183//! # Modify OpenAPI at runtime
184//!
185//! You can modify generated OpenAPI at runtime either via generated types directly or using
186//! [`Modify`] trait.
187//!
188//! _**Modify generated OpenAPI via types directly.**_
189//! ```rust
190//! # use fastapi::OpenApi;
191//! #[derive(OpenApi)]
192//! #[openapi(
193//!     info(description = "My Api description"),
194//! )]
195//! struct ApiDoc;
196//!
197//! let mut doc = ApiDoc::openapi();
198//! doc.info.title = String::from("My Api");
199//! ```
200//!
201//! _**You can even convert the generated [`OpenApi`] to [`openapi::OpenApiBuilder`].**_
202//! ```rust
203//! # use fastapi::openapi::OpenApiBuilder;
204//! # use fastapi::OpenApi;
205//! #[derive(OpenApi)]
206//! #[openapi(
207//!     info(description = "My Api description"),
208//! )]
209//! struct ApiDoc;
210//!
211//! let builder: OpenApiBuilder = ApiDoc::openapi().into();
212//! ```
213//!
214//! See [`Modify`] trait for examples on how to modify generated OpenAPI via it.
215//!
216//! # Go beyond the surface
217//!
218//! * See how to serve OpenAPI doc via Swagger UI check [`fastapi-swagger-ui`][fastapi_swagger] crate for more details.
219//! * Browse to [examples](https://github.com/nxpkg/fastapi/tree/master/examples) for more comprehensive examples.
220//! * Check [`derive@IntoResponses`] and [`derive@ToResponse`] for examples on deriving responses.
221//! * More about OpenAPI security in [security documentation][security].
222//! * Dump generated API doc to file at build time. See [issue 214 comment](https://github.com/nxpkg/fastapi/issues/214#issuecomment-1179589373).
223//!
224//! [path]: attr.path.html
225//! [rocket_path]: attr.path.html#rocket_extras-feature-support-for-rocket
226//! [actix_path]: attr.path.html#actix_extras-feature-support-for-actix-web
227//! [axum_path]: attr.path.html#axum_extras-feature-support-for-axum
228//! [serde]: derive.ToSchema.html#partial-serde-attributes-support
229//! [fastapi_swagger]: https://docs.rs/fastapi-swagger-ui/
230//! [fastapi_config]: https://docs.rs/fastapi-config/
231//!
232//! [security]: openapi/security/index.html
233//! [to_schema_derive]: derive.ToSchema.html
234
235pub mod openapi;
236
237use std::borrow::Cow;
238use std::collections::BTreeMap;
239use std::option::Option;
240
241#[cfg(feature = "macros")]
242#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
243pub use fastapi_gen::*;
244
245/// Trait for implementing OpenAPI specification in Rust.
246///
247/// This trait is derivable and can be used with `#[derive]` attribute. The derived implementation
248/// will use Cargo provided environment variables to implement the default information. For a details of
249/// `#[derive(ToSchema)]` refer to [derive documentation][derive].
250///
251/// # Examples
252///
253/// Below is derived example of `OpenApi`.
254/// ```rust
255/// use fastapi::OpenApi;
256/// #[derive(OpenApi)]
257/// #[openapi()]
258/// struct OpenApiDoc;
259/// ```
260///
261/// This manual `OpenApi` trait implementation is approximately equal to the above derived one except the derive
262/// implementation will by default use the Cargo environment variables to set defaults for *application name,
263/// version, application description, license, author name & email*.
264///
265/// ```rust
266/// struct OpenApiDoc;
267///
268/// impl fastapi::OpenApi for OpenApiDoc {
269///     fn openapi() -> fastapi::openapi::OpenApi {
270///         use fastapi::{ToSchema, Path};
271///         fastapi::openapi::OpenApiBuilder::new()
272///             .info(fastapi::openapi::InfoBuilder::new()
273///                 .title("application name")
274///                 .version("version")
275///                 .description(Some("application description"))
276///                 .license(Some(fastapi::openapi::License::new("MIT")))
277///                 .contact(
278///                     Some(fastapi::openapi::ContactBuilder::new()
279///                         .name(Some("author name"))
280///                         .email(Some("author email")).build()),
281///             ).build())
282///             .paths(fastapi::openapi::path::Paths::new())
283///             .components(Some(fastapi::openapi::Components::new()))
284///             .build()
285///     }
286/// }
287/// ```
288/// [derive]: derive.OpenApi.html
289pub trait OpenApi {
290    /// Return the [`openapi::OpenApi`] instance which can be parsed with serde or served via
291    /// OpenAPI visualization tool such as Swagger UI.
292    fn openapi() -> openapi::OpenApi;
293}
294
295/// Trait for implementing OpenAPI Schema object.
296///
297/// Generated schemas can be referenced or reused in path operations.
298///
299/// This trait is derivable and can be used with `[#derive]` attribute. For a details of
300/// `#[derive(ToSchema)]` refer to [derive documentation][derive].
301///
302/// [derive]: derive.ToSchema.html
303///
304/// # Examples
305///
306/// Use `#[derive]` to implement `ToSchema` trait.
307/// ```rust
308/// # use fastapi::ToSchema;
309/// #[derive(ToSchema)]
310/// #[schema(example = json!({"name": "bob the cat", "id": 1}))]
311/// struct Pet {
312///     id: u64,
313///     name: String,
314///     age: Option<i32>,
315/// }
316/// ```
317///
318/// Following manual implementation is equal to above derive one.
319/// ```rust
320/// # struct Pet {
321/// #     id: u64,
322/// #     name: String,
323/// #     age: Option<i32>,
324/// # }
325/// #
326/// impl fastapi::ToSchema for Pet {
327///     fn name() -> std::borrow::Cow<'static, str> {
328///         std::borrow::Cow::Borrowed("Pet")
329///     }
330/// }
331/// impl fastapi::PartialSchema for Pet {
332///     fn schema() -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
333///         fastapi::openapi::ObjectBuilder::new()
334///             .property(
335///                 "id",
336///                 fastapi::openapi::ObjectBuilder::new()
337///                     .schema_type(fastapi::openapi::schema::Type::Integer)
338///                     .format(Some(fastapi::openapi::SchemaFormat::KnownFormat(
339///                         fastapi::openapi::KnownFormat::Int64,
340///                     ))),
341///             )
342///             .required("id")
343///             .property(
344///                 "name",
345///                 fastapi::openapi::ObjectBuilder::new()
346///                     .schema_type(fastapi::openapi::schema::Type::String),
347///             )
348///             .required("name")
349///             .property(
350///                 "age",
351///                 fastapi::openapi::ObjectBuilder::new()
352///                     .schema_type(fastapi::openapi::schema::Type::Integer)
353///                     .format(Some(fastapi::openapi::SchemaFormat::KnownFormat(
354///                         fastapi::openapi::KnownFormat::Int32,
355///                     ))),
356///             )
357///             .example(Some(serde_json::json!({
358///               "name":"bob the cat","id":1
359///             })))
360///             .into()
361///     }
362/// }
363/// ```
364pub trait ToSchema: PartialSchema {
365    /// Return name of the schema.
366    ///
367    /// Name is used by referencing objects to point to this schema object returned with
368    /// [`PartialSchema::schema`] within the OpenAPI document.
369    ///
370    /// In case a generic schema the _`name`_ will be used as prefix for the name in the OpenAPI
371    /// documentation.
372    ///
373    /// The default implementation naively takes the TypeName by removing
374    /// the module path and generic elements.
375    /// But you probably don't want to use the default implementation for generic elements.
376    /// That will produce collision between generics. (eq. `Foo<String>` )
377    ///
378    /// # Example
379    ///
380    /// ```rust
381    /// # use fastapi::ToSchema;
382    /// #
383    /// struct Foo<T>(T);
384    ///
385    /// impl<T: ToSchema> ToSchema for Foo<T> {}
386    /// # impl<T: ToSchema> fastapi::PartialSchema for Foo<T> {
387    /// #     fn schema() -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
388    /// #         Default::default()
389    /// #     }
390    /// # }
391    ///
392    /// assert_eq!(Foo::<()>::name(), std::borrow::Cow::Borrowed("Foo"));
393    /// assert_eq!(Foo::<()>::name(), Foo::<i32>::name()); // WARNING: these types have the same name
394    /// ```
395    fn name() -> Cow<'static, str> {
396        let full_type_name = std::any::type_name::<Self>();
397        let type_name_without_generic = full_type_name
398            .split_once("<")
399            .map(|(s1, _)| s1)
400            .unwrap_or(full_type_name);
401        let type_name = type_name_without_generic
402            .rsplit_once("::")
403            .map(|(_, tn)| tn)
404            .unwrap_or(type_name_without_generic);
405        Cow::Borrowed(type_name)
406    }
407
408    /// Implement reference [`fastapi::openapi::schema::Schema`]s for this type.
409    ///
410    /// When [`ToSchema`] is being derived this is implemented automatically but if one needs to
411    /// manually implement [`ToSchema`] trait then this is needed for `fastapi` to know
412    /// referencing schemas that need to be present in the resulting OpenAPI spec.
413    ///
414    /// The implementation should push to `schemas` [`Vec`] all such field and variant types that
415    /// implement `ToSchema` and then call `<MyType as ToSchema>::schemas(schemas)` on that type
416    /// to forward the recursive reference collection call on that type.
417    ///
418    /// # Examples
419    ///
420    /// _**Implement `ToSchema` manually with references.**_
421    ///
422    /// ```rust
423    /// # use fastapi::{ToSchema, PartialSchema};
424    /// #
425    /// #[derive(ToSchema)]
426    /// struct Owner {
427    ///     name: String
428    /// }
429    ///
430    /// struct Pet {
431    ///     owner: Owner,
432    ///     name: String
433    /// }
434    /// impl PartialSchema for Pet {
435    ///     fn schema() -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
436    ///         fastapi::openapi::schema::Object::builder()
437    ///             .property("owner", Owner::schema())
438    ///             .property("name", String::schema())
439    ///             .into()
440    ///     }
441    /// }
442    /// impl ToSchema for Pet {
443    ///     fn schemas(schemas:
444    ///         &mut Vec<(String, fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>)>) {
445    ///         schemas.push((Owner::name().into(), Owner::schema()));
446    ///         <Owner as ToSchema>::schemas(schemas);
447    ///     }
448    /// }
449    /// ```
450    #[allow(unused)]
451    fn schemas(
452        schemas: &mut Vec<(
453            String,
454            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
455        )>,
456    ) {
457        // nothing by default
458    }
459}
460
461impl<T: ToSchema> From<T> for openapi::RefOr<openapi::schema::Schema> {
462    fn from(_: T) -> Self {
463        T::schema()
464    }
465}
466
467/// Represents _`nullable`_ type. This can be used anywhere where "nothing" needs to be evaluated.
468/// This will serialize to _`null`_ in JSON and [`openapi::schema::empty`] is used to create the
469/// [`openapi::schema::Schema`] for the type.
470pub type TupleUnit = ();
471
472impl PartialSchema for TupleUnit {
473    fn schema() -> openapi::RefOr<openapi::schema::Schema> {
474        openapi::schema::empty().into()
475    }
476}
477
478impl ToSchema for TupleUnit {
479    fn name() -> Cow<'static, str> {
480        Cow::Borrowed("TupleUnit")
481    }
482}
483
484macro_rules! impl_to_schema {
485    ( $( $ty:ident ),* ) => {
486        $(
487        impl ToSchema for $ty {
488            fn name() -> std::borrow::Cow<'static, str> {
489                std::borrow::Cow::Borrowed(stringify!( $ty ))
490            }
491        }
492        )*
493    };
494}
495
496#[rustfmt::skip]
497impl_to_schema!(
498    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool, f32, f64, String, str, char
499);
500
501impl ToSchema for &str {
502    fn name() -> Cow<'static, str> {
503        str::name()
504    }
505}
506
507#[cfg(feature = "macros")]
508#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
509impl<T: ToSchema> ToSchema for Option<T>
510where
511    Option<T>: PartialSchema,
512{
513    fn schemas(
514        schemas: &mut Vec<(
515            String,
516            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
517        )>,
518    ) {
519        T::schemas(schemas);
520    }
521}
522
523#[cfg(feature = "macros")]
524#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
525impl<T: ToSchema> ToSchema for Vec<T>
526where
527    Vec<T>: PartialSchema,
528{
529    fn schemas(
530        schemas: &mut Vec<(
531            String,
532            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
533        )>,
534    ) {
535        T::schemas(schemas);
536    }
537}
538
539#[cfg(feature = "macros")]
540#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
541impl<T: ToSchema> ToSchema for std::collections::LinkedList<T>
542where
543    std::collections::LinkedList<T>: PartialSchema,
544{
545    fn schemas(
546        schemas: &mut Vec<(
547            String,
548            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
549        )>,
550    ) {
551        T::schemas(schemas);
552    }
553}
554
555#[cfg(feature = "macros")]
556#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
557impl<T: ToSchema> ToSchema for [T]
558where
559    [T]: PartialSchema,
560{
561    fn schemas(
562        schemas: &mut Vec<(
563            String,
564            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
565        )>,
566    ) {
567        T::schemas(schemas);
568    }
569}
570
571#[cfg(feature = "macros")]
572#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
573impl<'t, T: ToSchema> ToSchema for &'t [T]
574where
575    &'t [T]: PartialSchema,
576{
577    fn schemas(
578        schemas: &mut Vec<(
579            String,
580            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
581        )>,
582    ) {
583        T::schemas(schemas);
584    }
585}
586
587#[cfg(feature = "macros")]
588#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
589impl<'t, T: ToSchema> ToSchema for &'t mut [T]
590where
591    &'t mut [T]: PartialSchema,
592{
593    fn schemas(
594        schemas: &mut Vec<(
595            String,
596            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
597        )>,
598    ) {
599        T::schemas(schemas);
600    }
601}
602
603#[cfg(feature = "macros")]
604#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
605impl<K: ToSchema, T: ToSchema> ToSchema for std::collections::HashMap<K, T>
606where
607    std::collections::HashMap<K, T>: PartialSchema,
608{
609    fn schemas(
610        schemas: &mut Vec<(
611            String,
612            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
613        )>,
614    ) {
615        K::schemas(schemas);
616        T::schemas(schemas);
617    }
618}
619
620#[cfg(feature = "macros")]
621#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
622impl<K: ToSchema, T: ToSchema> ToSchema for std::collections::BTreeMap<K, T>
623where
624    std::collections::BTreeMap<K, T>: PartialSchema,
625{
626    fn schemas(
627        schemas: &mut Vec<(
628            String,
629            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
630        )>,
631    ) {
632        K::schemas(schemas);
633        T::schemas(schemas);
634    }
635}
636
637#[cfg(feature = "macros")]
638#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
639impl<K: ToSchema> ToSchema for std::collections::HashSet<K>
640where
641    std::collections::HashSet<K>: PartialSchema,
642{
643    fn schemas(
644        schemas: &mut Vec<(
645            String,
646            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
647        )>,
648    ) {
649        K::schemas(schemas);
650    }
651}
652
653#[cfg(feature = "macros")]
654#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
655impl<K: ToSchema> ToSchema for std::collections::BTreeSet<K>
656where
657    std::collections::BTreeSet<K>: PartialSchema,
658{
659    fn schemas(
660        schemas: &mut Vec<(
661            String,
662            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
663        )>,
664    ) {
665        K::schemas(schemas);
666    }
667}
668
669#[cfg(all(feature = "macros", feature = "indexmap"))]
670#[cfg_attr(doc_cfg, doc(cfg(feature = "macros", feature = "indexmap")))]
671impl<K: ToSchema, T: ToSchema> ToSchema for indexmap::IndexMap<K, T>
672where
673    indexmap::IndexMap<K, T>: PartialSchema,
674{
675    fn schemas(
676        schemas: &mut Vec<(
677            String,
678            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
679        )>,
680    ) {
681        K::schemas(schemas);
682        T::schemas(schemas);
683    }
684}
685
686#[cfg(all(feature = "macros", feature = "indexmap"))]
687#[cfg_attr(doc_cfg, doc(cfg(feature = "macros", feature = "indexmap")))]
688impl<K: ToSchema> ToSchema for indexmap::IndexSet<K>
689where
690    indexmap::IndexSet<K>: PartialSchema,
691{
692    fn schemas(
693        schemas: &mut Vec<(
694            String,
695            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
696        )>,
697    ) {
698        K::schemas(schemas);
699    }
700}
701
702#[cfg(feature = "macros")]
703#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
704impl<T: ToSchema> ToSchema for std::boxed::Box<T>
705where
706    std::boxed::Box<T>: PartialSchema,
707{
708    fn schemas(
709        schemas: &mut Vec<(
710            String,
711            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
712        )>,
713    ) {
714        T::schemas(schemas);
715    }
716}
717
718#[cfg(feature = "macros")]
719#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
720impl<'a, T: ToSchema + Clone> ToSchema for std::borrow::Cow<'a, T>
721where
722    std::borrow::Cow<'a, T>: PartialSchema,
723{
724    fn schemas(
725        schemas: &mut Vec<(
726            String,
727            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
728        )>,
729    ) {
730        T::schemas(schemas);
731    }
732}
733
734#[cfg(feature = "macros")]
735#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
736impl<T: ToSchema> ToSchema for std::cell::RefCell<T>
737where
738    std::cell::RefCell<T>: PartialSchema,
739{
740    fn schemas(
741        schemas: &mut Vec<(
742            String,
743            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
744        )>,
745    ) {
746        T::schemas(schemas);
747    }
748}
749
750#[cfg(all(feature = "macros", feature = "rc_schema"))]
751#[cfg_attr(doc_cfg, doc(cfg(feature = "macros", feature = "rc_schema")))]
752impl<T: ToSchema> ToSchema for std::rc::Rc<T>
753where
754    std::rc::Rc<T>: PartialSchema,
755{
756    fn schemas(
757        schemas: &mut Vec<(
758            String,
759            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
760        )>,
761    ) {
762        T::schemas(schemas);
763    }
764}
765
766#[cfg(all(feature = "macros", feature = "rc_schema"))]
767#[cfg_attr(doc_cfg, doc(cfg(feature = "macros", feature = "rc_schema")))]
768impl<T: ToSchema> ToSchema for std::sync::Arc<T>
769where
770    std::sync::Arc<T>: PartialSchema,
771{
772    fn schemas(
773        schemas: &mut Vec<(
774            String,
775            fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
776        )>,
777    ) {
778        T::schemas(schemas);
779    }
780}
781
782impl PartialSchema for serde_json::Value {
783    fn schema() -> openapi::RefOr<openapi::schema::Schema> {
784        fastapi::openapi::schema::Object::builder()
785            .schema_type(fastapi::openapi::schema::SchemaType::AnyValue)
786            .into()
787    }
788}
789
790impl ToSchema for serde_json::Value {}
791
792// Create `fastapi` module so we can use `fastapi-gen` directly from `fastapi` crate.
793// ONLY for internal use!
794#[doc(hidden)]
795#[cfg(feature = "macros")]
796#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
797mod fastapi {
798    pub use super::*;
799}
800
801/// Trait used to implement only _`Schema`_ part of the OpenAPI doc.
802///
803/// This trait is by default implemented for Rust [`primitive`][primitive] types and some well known types like
804/// [`Vec`], [`Option`], [`std::collections::HashMap`] and [`BTreeMap`]. The default implementation adds `schema()`
805/// method to the implementing type allowing simple conversion of the type to the OpenAPI Schema
806/// object. Moreover this allows handy way of constructing schema objects manually if ever so
807/// wished.
808///
809/// The trait can be implemented manually easily on any type. This trait comes especially handy
810/// with [`macro@schema`] macro that can be used to generate schema for arbitrary types.
811/// ```rust
812/// # use fastapi::PartialSchema;
813/// # use fastapi::openapi::schema::{SchemaType, KnownFormat, SchemaFormat, ObjectBuilder, Schema};
814/// # use fastapi::openapi::RefOr;
815/// #
816/// struct MyType;
817///
818/// impl PartialSchema for MyType {
819///     fn schema() -> RefOr<Schema> {
820///         // ... impl schema generation here
821///         RefOr::T(Schema::Object(ObjectBuilder::new().build()))
822///     }
823/// }
824/// ```
825///
826/// # Examples
827///
828/// _**Create number schema from u64.**_
829/// ```rust
830/// # use fastapi::PartialSchema;
831/// # use fastapi::openapi::schema::{Type, KnownFormat, SchemaFormat, ObjectBuilder, Schema};
832/// # use fastapi::openapi::RefOr;
833/// #
834/// let number: RefOr<Schema> = i64::schema().into();
835///
836// // would be equal to manual implementation
837/// let number2 = RefOr::T(
838///     Schema::Object(
839///         ObjectBuilder::new()
840///             .schema_type(Type::Integer)
841///             .format(Some(SchemaFormat::KnownFormat(KnownFormat::Int64)))
842///             .build()
843///         )
844///     );
845/// # assert_json_diff::assert_json_eq!(serde_json::to_value(&number).unwrap(), serde_json::to_value(&number2).unwrap());
846/// ```
847///
848/// _**Construct a Pet object schema manually.**_
849/// ```rust
850/// # use fastapi::PartialSchema;
851/// # use fastapi::openapi::schema::ObjectBuilder;
852/// struct Pet {
853///     id: i32,
854///     name: String,
855/// }
856///
857/// let pet_schema = ObjectBuilder::new()
858///     .property("id", i32::schema())
859///     .property("name", String::schema())
860///     .required("id").required("name")
861///     .build();
862/// ```
863///
864/// [primitive]: https://doc.rust-lang.org/std/primitive/index.html
865pub trait PartialSchema {
866    /// Return ref or schema of implementing type that can then be used to
867    /// construct combined schemas.
868    fn schema() -> openapi::RefOr<openapi::schema::Schema>;
869}
870
871/// Trait for implementing OpenAPI PathItem object with path.
872///
873/// This trait is implemented via [`#[fastapi::path(...)]`][derive] attribute macro and there
874/// is no need to implement this trait manually.
875///
876/// # Examples
877///
878/// Use `#[fastapi::path(..)]` to implement Path trait
879/// ```rust
880/// # #[derive(fastapi::ToSchema)]
881/// # struct Pet {
882/// #   id: u64,
883/// #   name: String,
884/// # }
885/// #
886/// #
887/// /// Get pet by id
888/// ///
889/// /// Get pet from database by pet database id
890/// #[fastapi::path(
891///     get,
892///     path = "/pets/{id}",
893///     responses(
894///         (status = 200, description = "Pet found successfully", body = Pet),
895///         (status = 404, description = "Pet was not found")
896///     ),
897///     params(
898///         ("id" = u64, Path, description = "Pet database id to get Pet for"),
899///     )
900/// )]
901/// async fn get_pet_by_id(pet_id: u64) -> Pet {
902///     Pet {
903///         id: pet_id,
904///         name: "lightning".to_string(),
905///     }
906/// }
907/// ```
908///
909/// Example of what would manual implementation roughly look like of above `#[fastapi::path(...)]` macro.
910/// ```rust
911/// fastapi::openapi::PathsBuilder::new().path(
912///         "/pets/{id}",
913///         fastapi::openapi::PathItem::new(
914///             fastapi::openapi::HttpMethod::Get,
915///             fastapi::openapi::path::OperationBuilder::new()
916///                 .responses(
917///                     fastapi::openapi::ResponsesBuilder::new()
918///                         .response(
919///                             "200",
920///                             fastapi::openapi::ResponseBuilder::new()
921///                                 .description("Pet found successfully")
922///                                 .content("application/json",
923///                                     fastapi::openapi::Content::new(
924///                                         Some(fastapi::openapi::Ref::from_schema_name("Pet")),
925///                                     ),
926///                             ),
927///                         )
928///                         .response("404", fastapi::openapi::Response::new("Pet was not found")),
929///                 )
930///                 .operation_id(Some("get_pet_by_id"))
931///                 .deprecated(Some(fastapi::openapi::Deprecated::False))
932///                 .summary(Some("Get pet by id"))
933///                 .description(Some("Get pet by id\n\nGet pet from database by pet database id\n"))
934///                 .parameter(
935///                     fastapi::openapi::path::ParameterBuilder::new()
936///                         .name("id")
937///                         .parameter_in(fastapi::openapi::path::ParameterIn::Path)
938///                         .required(fastapi::openapi::Required::True)
939///                         .deprecated(Some(fastapi::openapi::Deprecated::False))
940///                         .description(Some("Pet database id to get Pet for"))
941///                         .schema(
942///                             Some(fastapi::openapi::ObjectBuilder::new()
943///                                 .schema_type(fastapi::openapi::schema::Type::Integer)
944///                                 .format(Some(fastapi::openapi::SchemaFormat::KnownFormat(fastapi::openapi::KnownFormat::Int64)))),
945///                         ),
946///                 )
947///                 .tag("pet_api"),
948///         ),
949///     );
950/// ```
951///
952/// [derive]: attr.path.html
953pub trait Path {
954    /// List of HTTP methods this path operation is served at.
955    fn methods() -> Vec<openapi::path::HttpMethod>;
956
957    /// The path this operation is served at.
958    fn path() -> String;
959
960    /// [`openapi::path::Operation`] describing http operation details such as request bodies,
961    /// parameters and responses.
962    fn operation() -> openapi::path::Operation;
963}
964
965/// Trait that allows OpenApi modification at runtime.
966///
967/// Implement this trait if you wish to modify the OpenApi at runtime before it is being consumed
968/// *(Before `fastapi::OpenApi::openapi()` function returns)*.
969/// This is trait can be used to add or change already generated OpenApi spec to alter the generated
970/// specification by user defined condition. For example you can add definitions that should be loaded
971/// from some configuration at runtime what may not be available during compile time.
972///
973/// See more about [`OpenApi`][derive] derive at [derive documentation][derive].
974///
975/// [derive]: derive.OpenApi.html
976/// [security_scheme]: openapi/security/enum.SecurityScheme.html
977///
978/// # Examples
979///
980/// Add custom JWT [`SecurityScheme`][security_scheme] to [`OpenApi`][`openapi::OpenApi`].
981/// ```rust
982/// # use fastapi::{OpenApi, Modify};
983/// # use fastapi::openapi::security::{SecurityScheme, HttpBuilder, HttpAuthScheme};
984/// #[derive(OpenApi)]
985/// #[openapi(modifiers(&SecurityAddon))]
986/// struct ApiDoc;
987///
988/// struct SecurityAddon;
989///
990/// impl Modify for SecurityAddon {
991///     fn modify(&self, openapi: &mut fastapi::openapi::OpenApi) {
992///          openapi.components = Some(
993///              fastapi::openapi::ComponentsBuilder::new()
994///                  .security_scheme(
995///                      "api_jwt_token",
996///                      SecurityScheme::Http(
997///                          HttpBuilder::new()
998///                              .scheme(HttpAuthScheme::Bearer)
999///                              .bearer_format("JWT")
1000///                              .build(),
1001///                      ),
1002///                  )
1003///                  .build(),
1004///          )
1005///      }
1006/// }
1007/// ```
1008///
1009/// Add [OpenAPI Server Object][server] to alter the target server url. This can be used to give context
1010/// path for api operations.
1011/// ```rust
1012/// # use fastapi::{OpenApi, Modify};
1013/// # use fastapi::openapi::Server;
1014/// #[derive(OpenApi)]
1015/// #[openapi(modifiers(&ServerAddon))]
1016/// struct ApiDoc;
1017///
1018/// struct ServerAddon;
1019///
1020/// impl Modify for ServerAddon {
1021///     fn modify(&self, openapi: &mut fastapi::openapi::OpenApi) {
1022///         openapi.servers = Some(vec![Server::new("/api")])
1023///     }
1024/// }
1025/// ```
1026///
1027/// [server]: https://spec.openapis.org/oas/latest.html#server-object
1028pub trait Modify {
1029    /// Apply mutation for [`openapi::OpenApi`] instance before it is returned by
1030    /// [`openapi::OpenApi::openapi`] method call.
1031    ///
1032    /// This function allows users to run arbitrary code to change the generated
1033    /// [`fastapi::OpenApi`] before it is served.
1034    fn modify(&self, openapi: &mut openapi::OpenApi);
1035}
1036
1037/// Trait used to convert implementing type to OpenAPI parameters.
1038///
1039/// This trait is [derivable][derive] for structs which are used to describe `path` or `query` parameters.
1040/// For more details of `#[derive(IntoParams)]` refer to [derive documentation][derive].
1041///
1042/// # Examples
1043///
1044/// Derive [`IntoParams`] implementation. This example will fail to compile because [`IntoParams`] cannot
1045/// be used alone and it need to be used together with endpoint using the params as well. See
1046/// [derive documentation][derive] for more details.
1047/// ```
1048/// use fastapi::{IntoParams};
1049///
1050/// #[derive(IntoParams)]
1051/// struct PetParams {
1052///     /// Id of pet
1053///     id: i64,
1054///     /// Name of pet
1055///     name: String,
1056/// }
1057/// ```
1058///
1059/// Roughly equal manual implementation of [`IntoParams`] trait.
1060/// ```rust
1061/// # struct PetParams {
1062/// #    /// Id of pet
1063/// #    id: i64,
1064/// #    /// Name of pet
1065/// #    name: String,
1066/// # }
1067/// impl fastapi::IntoParams for PetParams {
1068///     fn into_params(
1069///         parameter_in_provider: impl Fn() -> Option<fastapi::openapi::path::ParameterIn>
1070///     ) -> Vec<fastapi::openapi::path::Parameter> {
1071///         vec![
1072///             fastapi::openapi::path::ParameterBuilder::new()
1073///                 .name("id")
1074///                 .required(fastapi::openapi::Required::True)
1075///                 .parameter_in(parameter_in_provider().unwrap_or_default())
1076///                 .description(Some("Id of pet"))
1077///                 .schema(Some(
1078///                     fastapi::openapi::ObjectBuilder::new()
1079///                         .schema_type(fastapi::openapi::schema::Type::Integer)
1080///                         .format(Some(fastapi::openapi::SchemaFormat::KnownFormat(fastapi::openapi::KnownFormat::Int64))),
1081///                 ))
1082///                 .build(),
1083///             fastapi::openapi::path::ParameterBuilder::new()
1084///                 .name("name")
1085///                 .required(fastapi::openapi::Required::True)
1086///                 .parameter_in(parameter_in_provider().unwrap_or_default())
1087///                 .description(Some("Name of pet"))
1088///                 .schema(Some(
1089///                     fastapi::openapi::ObjectBuilder::new()
1090///                         .schema_type(fastapi::openapi::schema::Type::String),
1091///                 ))
1092///                 .build(),
1093///         ]
1094///     }
1095/// }
1096/// ```
1097/// [derive]: derive.IntoParams.html
1098pub trait IntoParams {
1099    /// Provide [`Vec`] of [`openapi::path::Parameter`]s to caller. The result is used in `fastapi-gen` library to
1100    /// provide OpenAPI parameter information for the endpoint using the parameters.
1101    fn into_params(
1102        parameter_in_provider: impl Fn() -> Option<openapi::path::ParameterIn>,
1103    ) -> Vec<openapi::path::Parameter>;
1104}
1105
1106/// This trait is implemented to document a type (like an enum) which can represent multiple
1107/// responses, to be used in operation.
1108///
1109/// # Examples
1110///
1111/// ```
1112/// use std::collections::BTreeMap;
1113/// use fastapi::{
1114///     openapi::{Response, ResponseBuilder, ResponsesBuilder, RefOr},
1115///     IntoResponses,
1116/// };
1117///
1118/// enum MyResponse {
1119///     Ok,
1120///     NotFound,
1121/// }
1122///
1123/// impl IntoResponses for MyResponse {
1124///     fn responses() -> BTreeMap<String, RefOr<Response>> {
1125///         ResponsesBuilder::new()
1126///             .response("200", ResponseBuilder::new().description("Ok"))
1127///             .response("404", ResponseBuilder::new().description("Not Found"))
1128///             .build()
1129///             .into()
1130///     }
1131/// }
1132/// ```
1133pub trait IntoResponses {
1134    /// Returns an ordered map of response codes to responses.
1135    fn responses() -> BTreeMap<String, openapi::RefOr<openapi::response::Response>>;
1136}
1137
1138#[cfg(feature = "auto_into_responses")]
1139impl<T: IntoResponses, E: IntoResponses> IntoResponses for Result<T, E> {
1140    fn responses() -> BTreeMap<String, openapi::RefOr<openapi::response::Response>> {
1141        let mut responses = T::responses();
1142        responses.append(&mut E::responses());
1143
1144        responses
1145    }
1146}
1147
1148#[cfg(feature = "auto_into_responses")]
1149impl IntoResponses for () {
1150    fn responses() -> BTreeMap<String, openapi::RefOr<openapi::response::Response>> {
1151        BTreeMap::new()
1152    }
1153}
1154
1155/// This trait is implemented to document a type which represents a single response which can be
1156/// referenced or reused as a component in multiple operations.
1157///
1158/// _`ToResponse`_ trait can also be derived with [`#[derive(ToResponse)]`][derive].
1159///
1160/// # Examples
1161///
1162/// ```
1163/// use fastapi::{
1164///     openapi::{RefOr, Response, ResponseBuilder},
1165///     ToResponse,
1166/// };
1167///
1168/// struct MyResponse;
1169///
1170/// impl<'__r> ToResponse<'__r> for MyResponse {
1171///     fn response() -> (&'__r str, RefOr<Response>) {
1172///         (
1173///             "MyResponse",
1174///             ResponseBuilder::new().description("My Response").build().into(),
1175///         )
1176///     }
1177/// }
1178/// ```
1179///
1180/// [derive]: derive.ToResponse.html
1181pub trait ToResponse<'__r> {
1182    /// Returns a tuple of response component name (to be referenced) to a response.
1183    fn response() -> (&'__r str, openapi::RefOr<openapi::response::Response>);
1184}
1185
1186/// Flexible number wrapper used by validation schema attributes to seamlessly support different
1187/// number syntaxes.
1188///
1189/// # Examples
1190///
1191/// _**Define object with two different number fields with minimum validation attribute.**_
1192///
1193/// ```rust
1194/// # use fastapi::Number;
1195/// # use fastapi::openapi::schema::{ObjectBuilder, SchemaType, Type};
1196/// let _ = ObjectBuilder::new()
1197///             .property("int_value", ObjectBuilder::new()
1198///                 .schema_type(Type::Integer).minimum(Some(1))
1199///             )
1200///             .property("float_value", ObjectBuilder::new()
1201///                 .schema_type(Type::Number).minimum(Some(-2.5))
1202///             )
1203///             .build();
1204/// ```
1205#[derive(Clone, serde::Deserialize, serde::Serialize)]
1206#[cfg_attr(feature = "debug", derive(Debug))]
1207#[serde(untagged)]
1208pub enum Number {
1209    /// Signed integer e.g. `1` or `-2`
1210    Int(isize),
1211    /// Unsigned integer value e.g. `0`. Unsigned integer cannot be below zero.
1212    UInt(usize),
1213    /// Floating point number e.g. `1.34`
1214    Float(f64),
1215}
1216
1217impl Eq for Number {}
1218
1219impl PartialEq for Number {
1220    fn eq(&self, other: &Self) -> bool {
1221        match (self, other) {
1222            (Self::Int(left), Self::Int(right)) => left == right,
1223            (Self::UInt(left), Self::UInt(right)) => left == right,
1224            (Self::Float(left), Self::Float(right)) => left == right,
1225            _ => false,
1226        }
1227    }
1228}
1229
1230macro_rules! impl_from_for_number {
1231    ( $( $ty:ident => $pat:ident $( as $as:ident )? ),* ) => {
1232        $(
1233        impl From<$ty> for Number {
1234            fn from(value: $ty) -> Self {
1235                Self::$pat(value $( as $as )?)
1236            }
1237        }
1238        )*
1239    };
1240}
1241
1242#[rustfmt::skip]
1243impl_from_for_number!(
1244    f32 => Float as f64, f64 => Float,
1245    i8 => Int as isize, i16 => Int as isize, i32 => Int as isize, i64 => Int as isize,
1246    u8 => UInt as usize, u16 => UInt as usize, u32 => UInt as usize, u64 => UInt as usize,
1247    isize => Int, usize => UInt
1248);
1249
1250/// Internal dev module used internally by fastapi-gen
1251#[doc(hidden)]
1252#[cfg(feature = "macros")]
1253#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
1254pub mod __dev {
1255    use fastapi_gen::schema;
1256
1257    use crate::{fastapi, OpenApi, PartialSchema};
1258
1259    pub trait PathConfig {
1260        fn path() -> String;
1261
1262        fn methods() -> Vec<crate::openapi::path::HttpMethod>;
1263
1264        fn tags_and_operation() -> (Vec<&'static str>, fastapi::openapi::path::Operation);
1265    }
1266
1267    pub trait Tags<'t> {
1268        fn tags() -> Vec<&'t str>;
1269    }
1270
1271    impl<T: PathConfig> fastapi::Path for T {
1272        fn path() -> String {
1273            <Self as PathConfig>::path()
1274        }
1275
1276        fn methods() -> Vec<crate::openapi::path::HttpMethod> {
1277            <Self as PathConfig>::methods()
1278        }
1279
1280        fn operation() -> crate::openapi::path::Operation {
1281            let (tags, mut operation) = <Self as PathConfig>::tags_and_operation();
1282
1283            let operation_tags = operation.tags.get_or_insert(Vec::new());
1284            operation_tags.extend(tags.iter().map(ToString::to_string));
1285
1286            operation
1287        }
1288    }
1289
1290    pub trait NestedApiConfig {
1291        fn config() -> (fastapi::openapi::OpenApi, Vec<&'static str>, &'static str);
1292    }
1293
1294    impl<T: NestedApiConfig> OpenApi for T {
1295        fn openapi() -> crate::openapi::OpenApi {
1296            let (mut api, tags, module_path) = T::config();
1297
1298            api.paths.paths.iter_mut().for_each(|(_, path_item)| {
1299                let update_tags = |operation: Option<&mut crate::openapi::path::Operation>| {
1300                    if let Some(operation) = operation {
1301                        let operation_tags = operation.tags.get_or_insert(Vec::new());
1302                        operation_tags.extend(tags.iter().map(ToString::to_string));
1303                        if operation_tags.is_empty() && !module_path.is_empty() {
1304                            operation_tags.push(module_path.to_string());
1305                        }
1306                    }
1307                };
1308
1309                update_tags(path_item.get.as_mut());
1310                update_tags(path_item.put.as_mut());
1311                update_tags(path_item.post.as_mut());
1312                update_tags(path_item.delete.as_mut());
1313                update_tags(path_item.options.as_mut());
1314                update_tags(path_item.head.as_mut());
1315                update_tags(path_item.patch.as_mut());
1316                update_tags(path_item.trace.as_mut());
1317            });
1318
1319            api
1320        }
1321    }
1322
1323    pub trait ComposeSchema {
1324        fn compose(
1325            new_generics: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1326        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>;
1327    }
1328
1329    macro_rules! impl_compose_schema {
1330        ( $( $ty:ident ),* ) => {
1331            $(
1332            impl ComposeSchema for $ty {
1333                fn compose(_: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1334                    schema!( $ty ).into()
1335                }
1336            }
1337            )*
1338        };
1339    }
1340
1341    #[rustfmt::skip]
1342    impl_compose_schema!(
1343        i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool, f32, f64, String, str, char
1344    );
1345
1346    fn schema_or_compose<T: ComposeSchema>(
1347        schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1348        index: usize,
1349    ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1350        if let Some(schema) = schemas.get(index) {
1351            schema.clone()
1352        } else {
1353            T::compose(schemas)
1354        }
1355    }
1356
1357    impl ComposeSchema for &str {
1358        fn compose(
1359            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1360        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1361            str::compose(schemas)
1362        }
1363    }
1364
1365    impl<T: ComposeSchema + ?Sized> PartialSchema for T {
1366        fn schema() -> crate::openapi::RefOr<crate::openapi::schema::Schema> {
1367            T::compose(Vec::new())
1368        }
1369    }
1370    impl<T: ComposeSchema> ComposeSchema for Option<T> {
1371        fn compose(
1372            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1373        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1374            fastapi::openapi::schema::OneOfBuilder::new()
1375                .item(
1376                    fastapi::openapi::schema::ObjectBuilder::new()
1377                        .schema_type(fastapi::openapi::schema::Type::Null),
1378                )
1379                .item(schema_or_compose::<T>(schemas, 0))
1380                .into()
1381        }
1382    }
1383
1384    impl<T: ComposeSchema> ComposeSchema for Vec<T> {
1385        fn compose(
1386            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1387        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1388            fastapi::openapi::schema::ArrayBuilder::new()
1389                .items(schema_or_compose::<T>(schemas, 0))
1390                .into()
1391        }
1392    }
1393
1394    impl<T: ComposeSchema> ComposeSchema for std::collections::LinkedList<T> {
1395        fn compose(
1396            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1397        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1398            fastapi::openapi::schema::ArrayBuilder::new()
1399                .items(schema_or_compose::<T>(schemas, 0))
1400                .into()
1401        }
1402    }
1403
1404    impl<T: ComposeSchema> ComposeSchema for [T] {
1405        fn compose(
1406            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1407        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1408            fastapi::openapi::schema::ArrayBuilder::new()
1409                .items(schema_or_compose::<T>(schemas, 0))
1410                .into()
1411        }
1412    }
1413
1414    impl<T: ComposeSchema> ComposeSchema for &[T] {
1415        fn compose(
1416            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1417        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1418            fastapi::openapi::schema::ArrayBuilder::new()
1419                .items(schema_or_compose::<T>(schemas, 0))
1420                .into()
1421        }
1422    }
1423
1424    impl<T: ComposeSchema> ComposeSchema for &mut [T] {
1425        fn compose(
1426            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1427        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1428            fastapi::openapi::schema::ArrayBuilder::new()
1429                .items(schema_or_compose::<T>(schemas, 0))
1430                .into()
1431        }
1432    }
1433
1434    impl<K: ComposeSchema, T: ComposeSchema> ComposeSchema for std::collections::HashMap<K, T> {
1435        fn compose(
1436            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1437        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1438            fastapi::openapi::ObjectBuilder::new()
1439                .property_names(Some(schema_or_compose::<K>(schemas.clone(), 0)))
1440                .additional_properties(Some(schema_or_compose::<T>(schemas, 1)))
1441                .into()
1442        }
1443    }
1444
1445    impl<K: ComposeSchema, T: ComposeSchema> ComposeSchema for std::collections::BTreeMap<K, T> {
1446        fn compose(
1447            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1448        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1449            fastapi::openapi::ObjectBuilder::new()
1450                .property_names(Some(schema_or_compose::<K>(schemas.clone(), 0)))
1451                .additional_properties(Some(schema_or_compose::<T>(schemas, 1)))
1452                .into()
1453        }
1454    }
1455
1456    impl<K: ComposeSchema> ComposeSchema for std::collections::HashSet<K> {
1457        fn compose(
1458            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1459        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1460            fastapi::openapi::schema::ArrayBuilder::new()
1461                .items(schema_or_compose::<K>(schemas, 0))
1462                .unique_items(true)
1463                .into()
1464        }
1465    }
1466
1467    impl<K: ComposeSchema> ComposeSchema for std::collections::BTreeSet<K> {
1468        fn compose(
1469            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1470        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1471            fastapi::openapi::schema::ArrayBuilder::new()
1472                .items(schema_or_compose::<K>(schemas, 0))
1473                .unique_items(true)
1474                .into()
1475        }
1476    }
1477
1478    #[cfg(feature = "indexmap")]
1479    #[cfg_attr(doc_cfg, doc(cfg(feature = "macros", feature = "indexmap")))]
1480    impl<K: ComposeSchema, T: ComposeSchema> ComposeSchema for indexmap::IndexMap<K, T> {
1481        fn compose(
1482            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1483        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1484            fastapi::openapi::ObjectBuilder::new()
1485                .property_names(Some(schema_or_compose::<K>(schemas.clone(), 0)))
1486                .additional_properties(Some(schema_or_compose::<T>(schemas, 1)))
1487                .into()
1488        }
1489    }
1490
1491    #[cfg(feature = "indexmap")]
1492    #[cfg_attr(doc_cfg, doc(cfg(feature = "macros", feature = "indexmap")))]
1493    impl<K: ComposeSchema> ComposeSchema for indexmap::IndexSet<K> {
1494        fn compose(
1495            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1496        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1497            fastapi::openapi::schema::ArrayBuilder::new()
1498                .items(schema_or_compose::<K>(schemas, 0))
1499                .unique_items(true)
1500                .into()
1501        }
1502    }
1503
1504    impl<'a, T: ComposeSchema + Clone> ComposeSchema for std::borrow::Cow<'a, T> {
1505        fn compose(
1506            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1507        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1508            schema_or_compose::<T>(schemas, 0)
1509        }
1510    }
1511
1512    impl<T: ComposeSchema> ComposeSchema for std::boxed::Box<T> {
1513        fn compose(
1514            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1515        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1516            schema_or_compose::<T>(schemas, 0)
1517        }
1518    }
1519
1520    impl<T: ComposeSchema> ComposeSchema for std::cell::RefCell<T> {
1521        fn compose(
1522            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1523        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1524            schema_or_compose::<T>(schemas, 0)
1525        }
1526    }
1527
1528    #[cfg(feature = "rc_schema")]
1529    #[cfg_attr(doc_cfg, doc(cfg(feature = "macros", feature = "rc_schema")))]
1530    impl<T: ComposeSchema> ComposeSchema for std::rc::Rc<T> {
1531        fn compose(
1532            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1533        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1534            schema_or_compose::<T>(schemas, 0)
1535        }
1536    }
1537
1538    #[cfg(feature = "rc_schema")]
1539    #[cfg_attr(doc_cfg, doc(cfg(feature = "macros", feature = "rc_schema")))]
1540    impl<T: ComposeSchema> ComposeSchema for std::sync::Arc<T> {
1541        fn compose(
1542            schemas: Vec<fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>>,
1543        ) -> fastapi::openapi::RefOr<fastapi::openapi::schema::Schema> {
1544            schema_or_compose::<T>(schemas, 0)
1545        }
1546    }
1547
1548    // For types not implementing `ToSchema`
1549    pub trait SchemaReferences {
1550        fn schemas(
1551            schemas: &mut Vec<(
1552                String,
1553                fastapi::openapi::RefOr<fastapi::openapi::schema::Schema>,
1554            )>,
1555        );
1556    }
1557}
1558
1559#[cfg(test)]
1560mod tests {
1561    use assert_json_diff::assert_json_eq;
1562    use serde_json::json;
1563
1564    use super::*;
1565
1566    #[test]
1567    fn test_toschema_name() {
1568        struct Foo;
1569        impl ToSchema for Foo {}
1570        impl PartialSchema for Foo {
1571            fn schema() -> openapi::RefOr<openapi::schema::Schema> {
1572                Default::default()
1573            }
1574        }
1575        assert_eq!(Foo::name(), Cow::Borrowed("Foo"));
1576
1577        struct FooGeneric<T: ToSchema, U: ToSchema>(T, U);
1578        impl<T: ToSchema, U: ToSchema> ToSchema for FooGeneric<T, U> {}
1579        impl<T: ToSchema, U: ToSchema> PartialSchema for FooGeneric<T, U> {
1580            fn schema() -> openapi::RefOr<openapi::schema::Schema> {
1581                Default::default()
1582            }
1583        }
1584        assert_eq!(
1585            FooGeneric::<Foo, String>::name(),
1586            Cow::Borrowed("FooGeneric")
1587        );
1588        assert_eq!(
1589            FooGeneric::<Foo, String>::name(),
1590            FooGeneric::<(), ()>::name(),
1591        );
1592    }
1593
1594    #[cfg(not(feature = "non_strict_integers"))]
1595    #[test]
1596    fn test_partial_schema_strict_integers() {
1597        use assert_json_diff::{assert_json_matches, CompareMode, Config, NumericMode};
1598
1599        for (name, schema, value) in [
1600            (
1601                "i8",
1602                i8::schema(),
1603                json!({"type": "integer", "format": "int32"}),
1604            ),
1605            (
1606                "i16",
1607                i16::schema(),
1608                json!({"type": "integer", "format": "int32"}),
1609            ),
1610            (
1611                "i32",
1612                i32::schema(),
1613                json!({"type": "integer", "format": "int32"}),
1614            ),
1615            (
1616                "i64",
1617                i64::schema(),
1618                json!({"type": "integer", "format": "int64"}),
1619            ),
1620            ("i128", i128::schema(), json!({"type": "integer"})),
1621            ("isize", isize::schema(), json!({"type": "integer"})),
1622            (
1623                "u8",
1624                u8::schema(),
1625                json!({"type": "integer", "format": "int32", "minimum": 0.0}),
1626            ),
1627            (
1628                "u16",
1629                u16::schema(),
1630                json!({"type": "integer", "format": "int32", "minimum": 0.0}),
1631            ),
1632            (
1633                "u32",
1634                u32::schema(),
1635                json!({"type": "integer", "format": "int32", "minimum": 0.0}),
1636            ),
1637            (
1638                "u64",
1639                u64::schema(),
1640                json!({"type": "integer", "format": "int64", "minimum": 0.0}),
1641            ),
1642        ] {
1643            println!(
1644                "{name}: {json}",
1645                json = serde_json::to_string(&schema).unwrap()
1646            );
1647            let schema = serde_json::to_value(schema).unwrap();
1648
1649            let config = Config::new(CompareMode::Strict).numeric_mode(NumericMode::AssumeFloat);
1650            assert_json_matches!(schema, value, config);
1651        }
1652    }
1653
1654    #[cfg(feature = "non_strict_integers")]
1655    #[test]
1656    fn test_partial_schema_non_strict_integers() {
1657        for (name, schema, value) in [
1658            (
1659                "i8",
1660                i8::schema(),
1661                json!({"type": "integer", "format": "int8"}),
1662            ),
1663            (
1664                "i16",
1665                i16::schema(),
1666                json!({"type": "integer", "format": "int16"}),
1667            ),
1668            (
1669                "i32",
1670                i32::schema(),
1671                json!({"type": "integer", "format": "int32"}),
1672            ),
1673            (
1674                "i64",
1675                i64::schema(),
1676                json!({"type": "integer", "format": "int64"}),
1677            ),
1678            ("i128", i128::schema(), json!({"type": "integer"})),
1679            ("isize", isize::schema(), json!({"type": "integer"})),
1680            (
1681                "u8",
1682                u8::schema(),
1683                json!({"type": "integer", "format": "uint8", "minimum": 0}),
1684            ),
1685            (
1686                "u16",
1687                u16::schema(),
1688                json!({"type": "integer", "format": "uint16", "minimum": 0}),
1689            ),
1690            (
1691                "u32",
1692                u32::schema(),
1693                json!({"type": "integer", "format": "uint32", "minimum": 0}),
1694            ),
1695            (
1696                "u64",
1697                u64::schema(),
1698                json!({"type": "integer", "format": "uint64", "minimum": 0}),
1699            ),
1700        ] {
1701            println!(
1702                "{name}: {json}",
1703                json = serde_json::to_string(&schema).unwrap()
1704            );
1705            let schema = serde_json::to_value(schema).unwrap();
1706            assert_json_eq!(schema, value);
1707        }
1708    }
1709
1710    #[test]
1711    fn test_partial_schema() {
1712        for (name, schema, value) in [
1713            ("bool", bool::schema(), json!({"type": "boolean"})),
1714            ("str", str::schema(), json!({"type": "string"})),
1715            ("String", String::schema(), json!({"type": "string"})),
1716            ("char", char::schema(), json!({"type": "string"})),
1717            (
1718                "f32",
1719                f32::schema(),
1720                json!({"type": "number", "format": "float"}),
1721            ),
1722            (
1723                "f64",
1724                f64::schema(),
1725                json!({"type": "number", "format": "double"}),
1726            ),
1727        ] {
1728            println!(
1729                "{name}: {json}",
1730                json = serde_json::to_string(&schema).unwrap()
1731            );
1732            let schema = serde_json::to_value(schema).unwrap();
1733            assert_json_eq!(schema, value);
1734        }
1735    }
1736}