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