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