Skip to content

Commit

Permalink
Add missing extensions for OpenAPI types (#1104)
Browse files Browse the repository at this point in the history
This commit adds `extensions` field for OpenAPI types currently missing
the field. This allows defining the `x-*` extension attributes on types
supporting it.

Closes #643
  • Loading branch information
juhaku authored Oct 7, 2024
1 parent 8bbc5d9 commit 3d6b1f9
Show file tree
Hide file tree
Showing 14 changed files with 193 additions and 7 deletions.
1 change: 1 addition & 0 deletions utoipa/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ to look into changes introduced to **`utoipa-gen`**.

### Added

* Add missing `extensions` for OpenAPI types (https://github.com/juhaku/utoipa/pull/1104)
* Add a default impl of ToSchema::name() (https://github.com/juhaku/utoipa/pull/1096)
* Add support for `property_names` for object (https://github.com/juhaku/utoipa/pull/1084)
* Add changelogs for crates (https://github.com/juhaku/utoipa/pull/1075)
Expand Down
12 changes: 10 additions & 2 deletions utoipa/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ builder! {
/// Available paths and operations for the API.
///
/// See more details at <https://spec.openapis.org/oas/latest.html#paths-object>.
#[serde(flatten)]
pub paths: Paths,

/// Holds various reusable schemas for the OpenAPI document.
Expand Down Expand Up @@ -208,6 +207,11 @@ impl OpenApi {
}
}
self.paths.paths.extend(other.paths.paths);

if let Some(other_paths_extensions) = other.paths.extensions {
let paths_extensions = self.paths.extensions.get_or_insert(Extensions::default());
paths_extensions.merge(other_paths_extensions);
}
};

if let Some(other_components) = &mut other.components {
Expand Down Expand Up @@ -852,6 +856,7 @@ mod tests {
.response("200", Response::new("Get user success 1")),
),
)
.extensions(Some(Extensions::from_iter([("x-v1-api", true)])))
.build(),
);

Expand Down Expand Up @@ -891,6 +896,7 @@ mod tests {
.response("200", Response::new("Post user success 2")),
),
)
.extensions(Some(Extensions::from_iter([("x-random", "Value")])))
.build(),
)
.components(Some(
Expand Down Expand Up @@ -950,7 +956,9 @@ mod tests {
}
}
}
}
},
"x-random": "Value",
"x-v1-api": true,
},
"components": {
"schemas": {
Expand Down
14 changes: 14 additions & 0 deletions utoipa/src/openapi/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ use serde_json::Value;

use super::builder;
use super::example::Example;
use super::extensions::Extensions;
use super::{encoding::Encoding, set_value, RefOr, Schema};

builder! {
ContentBuilder;


/// Content holds request body content or response content.
///
/// [`Content`] implements OpenAPI spec [Media Type Object][media_type]
///
/// [media_type]: <https://spec.openapis.org/oas/latest.html#media-type-object>
#[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[non_exhaustive]
Expand Down Expand Up @@ -43,6 +48,10 @@ builder! {
/// multipart or `application/x-www-form-urlencoded`.
#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
pub encoding: BTreeMap<String, Encoding>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}
}

Expand Down Expand Up @@ -107,4 +116,9 @@ impl ContentBuilder {
self.encoding.insert(property_name.into(), encoding.into());
self
}

/// Add openapi extensions (x-something) of the API.
pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
set_value!(self extensions extensions)
}
}
10 changes: 10 additions & 0 deletions utoipa/src/openapi/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use super::extensions::Extensions;
use super::{builder, path::ParameterStyle, set_value, Header};

builder! {
Expand Down Expand Up @@ -51,6 +52,10 @@ builder! {
/// `application/x-www-form-urlencoded`.
#[serde(skip_serializing_if = "Option::is_none")]
pub allow_reserved: Option<bool>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}
}

Expand Down Expand Up @@ -81,4 +86,9 @@ impl EncodingBuilder {
pub fn allow_reserved(mut self, allow_reserved: Option<bool>) -> Self {
set_value!(self allow_reserved allow_reserved)
}

/// Add openapi extensions (x-something) of the API.
pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
set_value!(self extensions extensions)
}
}
7 changes: 7 additions & 0 deletions utoipa/src/openapi/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ builder! {
}
}

impl Extensions {
/// Merge other [`Extensions`] into _`self`_.
pub fn merge(&mut self, other: Extensions) {
self.extensions.extend(other.extensions);
}
}

impl Deref for Extensions {
type Target = HashMap<String, serde_json::Value>;

Expand Down
10 changes: 10 additions & 0 deletions utoipa/src/openapi/external_docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! [external_docs]: https://spec.openapis.org/oas/latest.html#xml-object
use serde::{Deserialize, Serialize};

use super::extensions::Extensions;
use super::{builder, set_value};

builder! {
Expand All @@ -18,6 +19,10 @@ builder! {
pub url: String,
/// Additional description supporting markdown syntax of the external documentation.
pub description: Option<String>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}
}

Expand Down Expand Up @@ -50,4 +55,9 @@ impl ExternalDocsBuilder {
pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
set_value!(self description description.map(|description| description.into()))
}

/// Add openapi extensions (x-something) of the API.
pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
set_value!(self extensions extensions)
}
}
18 changes: 18 additions & 0 deletions utoipa/src/openapi/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ builder! {
/// Email of the contact person or the organization of the API.
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}
}

Expand All @@ -183,6 +187,11 @@ impl ContactBuilder {
pub fn email<S: Into<String>>(mut self, email: Option<S>) -> Self {
set_value!(self email email.map(|email| email.into()))
}

/// Add openapi extensions (x-something) of the API.
pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
set_value!(self extensions extensions)
}
}

builder! {
Expand All @@ -202,6 +211,10 @@ builder! {
/// Optional url pointing to the license.
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}
}

Expand All @@ -227,6 +240,11 @@ impl LicenseBuilder {
pub fn url<S: Into<String>>(mut self, url: Option<S>) -> Self {
set_value!(self url url.map(|url| url.into()))
}

/// Add openapi extensions (x-something) of the API.
pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
set_value!(self extensions extensions)
}
}

#[cfg(test)]
Expand Down
12 changes: 12 additions & 0 deletions utoipa/src/openapi/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use super::extensions::Extensions;
use super::{builder, Server};

builder! {
Expand Down Expand Up @@ -67,6 +68,10 @@ builder! {
/// [server]: ../server/struct.Server.html
#[serde(skip_serializing_if = "Option::is_none")]
pub server: Option<Server>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}
}

Expand Down Expand Up @@ -128,4 +133,11 @@ impl LinkBuilder {

self
}

/// Add openapi extensions (x-something) of the API.
pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
self.extensions = extensions;

self
}
}
1 change: 1 addition & 0 deletions utoipa/src/openapi/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ builder! {
pub struct Paths {
/// Map of relative paths with [`PathItem`]s holding [`Operation`]s matching
/// api endpoints.
#[serde(flatten)]
pub paths: PathsMap<String, PathItem>,

/// Optional extensions "x-something".
Expand Down
10 changes: 10 additions & 0 deletions utoipa/src/openapi/request_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use super::extensions::Extensions;
use super::{builder, set_value, Content, Required};

builder! {
Expand All @@ -28,6 +29,10 @@ builder! {
/// Determines whether request body is required in the request or not.
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Required>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}
}

Expand Down Expand Up @@ -55,6 +60,11 @@ impl RequestBodyBuilder {

self
}

/// Add openapi extensions (x-something) of the API.
pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
set_value!(self extensions extensions)
}
}

/// Trait with convenience functions for documenting request bodies.
Expand Down
10 changes: 10 additions & 0 deletions utoipa/src/openapi/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ builder! {
/// Map containing status code as a key with represented response as a value.
#[serde(flatten)]
pub responses: BTreeMap<String, RefOr<Response>>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}
}

Expand Down Expand Up @@ -72,6 +76,11 @@ impl ResponsesBuilder {
self.responses.extend(I::responses());
self
}

/// Add openapi extensions (x-something) of the API.
pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
set_value!(self extensions extensions)
}
}

impl From<Responses> for BTreeMap<String, RefOr<Response>> {
Expand All @@ -91,6 +100,7 @@ where
iter.into_iter()
.map(|(code, response)| (code.into(), response.into())),
),
..Default::default()
}
}
}
Expand Down
23 changes: 22 additions & 1 deletion utoipa/src/openapi/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ builder! {
/// [security_scheme]: https://spec.openapis.org/oas/latest.html#security-scheme-object
#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
pub security_schemes: BTreeMap<String, SecurityScheme>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}
}

Expand Down Expand Up @@ -254,6 +258,11 @@ impl ComponentsBuilder {

self
}

/// Add openapi extensions (x-something) of the API.
pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
set_value!(self extensions extensions)
}
}

/// Is super type for [OpenAPI Schema Object][schemas]. Schema is reusable resource what can be
Expand Down Expand Up @@ -312,6 +321,10 @@ pub struct Discriminator {
/// validation.
#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
pub mapping: BTreeMap<String, String>,

/// Optional extensions "x-something".
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub extensions: Option<Extensions>,
}

impl Discriminator {
Expand All @@ -328,6 +341,7 @@ impl Discriminator {
Self {
property_name: property_name.into(),
mapping: BTreeMap::new(),
..Default::default()
}
}

Expand Down Expand Up @@ -363,6 +377,7 @@ impl Discriminator {
.into_iter()
.map(|(key, val)| (key.into(), val.into())),
),
..Default::default()
}
}
}
Expand Down Expand Up @@ -2373,7 +2388,13 @@ mod tests {
"200",
ResponseBuilder::new().description("Okay").build(),
)])
.security_scheme("TLS", SecurityScheme::MutualTls { description: None })
.security_scheme(
"TLS",
SecurityScheme::MutualTls {
description: None,
extensions: None,
},
)
.build();

let serialized_components = serde_json::to_string(&components).unwrap();
Expand Down
Loading

0 comments on commit 3d6b1f9

Please sign in to comment.