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}