use crate::common::helpers::{
Context, PushError, ValidateWithContext, validate_optional_url, validate_required_string,
};
use crate::v2::spec::Spec;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt::{Display, Formatter};
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "type")]
pub enum SecurityScheme {
#[serde(rename = "basic")]
Basic(BasicSecurityScheme),
#[serde(rename = "apiKey")]
ApiKey(ApiKeySecurityScheme),
#[serde(rename = "oauth2")]
OAuth2(OAuth2SecurityScheme),
}
impl Display for SecurityScheme {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SecurityScheme::Basic(_) => write!(f, "basic"),
SecurityScheme::ApiKey(_) => write!(f, "aoiKey"),
SecurityScheme::OAuth2(_) => write!(f, "oauth2"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct BasicSecurityScheme {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct ApiKeySecurityScheme {
pub name: String,
#[serde(rename = "in")]
pub location: SecuritySchemeApiKeyLocation,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub enum SecuritySchemeApiKeyLocation {
#[default]
#[serde(rename = "query")]
Query,
#[serde(rename = "header")]
Header,
}
impl Display for SecuritySchemeApiKeyLocation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SecuritySchemeApiKeyLocation::Query => write!(f, "query"),
SecuritySchemeApiKeyLocation::Header => write!(f, "header"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct OAuth2SecurityScheme {
flow: SecuritySchemeOAuth2Flow,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "authorizationUrl")]
authorization_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "tokenUrl")]
token_url: Option<String>,
scopes: BTreeMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub enum SecuritySchemeOAuth2Flow {
#[default]
#[serde(rename = "implicit")]
Implicit,
#[serde(rename = "password")]
Password,
#[serde(rename = "application")]
Application,
#[serde(rename = "accessCode")]
AccessCode,
}
impl Display for SecuritySchemeOAuth2Flow {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SecuritySchemeOAuth2Flow::Implicit => write!(f, "implicit"),
SecuritySchemeOAuth2Flow::Password => write!(f, "password"),
SecuritySchemeOAuth2Flow::Application => write!(f, "application"),
SecuritySchemeOAuth2Flow::AccessCode => write!(f, "accessCode"),
}
}
}
impl ValidateWithContext<Spec> for SecurityScheme {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
match self {
SecurityScheme::Basic(basic) => basic.validate_with_context(ctx, path),
SecurityScheme::ApiKey(api_key) => api_key.validate_with_context(ctx, path),
SecurityScheme::OAuth2(oauth2) => oauth2.validate_with_context(ctx, path),
}
}
}
impl ValidateWithContext<Spec> for BasicSecurityScheme {
fn validate_with_context(&self, _ctx: &mut Context<Spec>, _path: String) {}
}
impl ValidateWithContext<Spec> for ApiKeySecurityScheme {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
validate_required_string(&self.name, ctx, format!("{path}.name"));
}
}
impl ValidateWithContext<Spec> for OAuth2SecurityScheme {
fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
if self.scopes.is_empty() {
ctx.error(path.clone(), ".scopes: must not be empty");
}
if self.authorization_url.is_none()
&& (self.flow == SecuritySchemeOAuth2Flow::Implicit
|| self.flow == SecuritySchemeOAuth2Flow::AccessCode)
{
ctx.error(
path,
format_args!(
".authorizationUrl: must be present for flow `{}`",
self.flow,
),
);
} else {
validate_optional_url(
&self.authorization_url,
ctx,
format!("{path}.authorizationUrl"),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_security_scheme_basic_deserialize() {
assert_eq!(
serde_json::from_value::<SecurityScheme>(json!({
"type": "basic",
"description": "A short description for security scheme.",
}))
.unwrap(),
SecurityScheme::Basic(BasicSecurityScheme {
description: Some(String::from("A short description for security scheme.")),
}),
"deserialize",
);
}
#[test]
fn test_security_scheme_basic_serialize() {
assert_eq!(
serde_json::to_value(SecurityScheme::Basic(BasicSecurityScheme {
description: Some(String::from("A short description for security scheme.")),
}))
.unwrap(),
json!({
"type": "basic",
"description": "A short description for security scheme.",
}),
"serialize",
);
}
#[test]
fn test_security_scheme_api_key_deserialize() {
assert_eq!(
serde_json::from_value::<SecurityScheme>(json!({
"type": "apiKey",
"name": "api_key",
"in": "header",
"description": "A short description for security scheme.",
}))
.unwrap(),
SecurityScheme::ApiKey(ApiKeySecurityScheme {
name: String::from("api_key"),
location: SecuritySchemeApiKeyLocation::Header,
description: Some(String::from("A short description for security scheme.")),
}),
"deserialize in = header",
);
assert_eq!(
serde_json::from_value::<SecurityScheme>(json!({
"type": "apiKey",
"name": "api_key",
"in": "query",
"description": "A short description for security scheme.",
}))
.unwrap(),
SecurityScheme::ApiKey(ApiKeySecurityScheme {
name: String::from("api_key"),
location: SecuritySchemeApiKeyLocation::Query,
description: Some(String::from("A short description for security scheme.")),
}),
"deserialize in = query",
);
}
#[test]
fn test_security_scheme_api_key_serialize() {
assert_eq!(
serde_json::to_value(SecurityScheme::ApiKey(ApiKeySecurityScheme {
name: String::from("api_key"),
location: SecuritySchemeApiKeyLocation::Header,
description: Some(String::from("A short description for security scheme.")),
}))
.unwrap(),
json!({
"type": "apiKey",
"name": "api_key",
"in": "header",
"description": "A short description for security scheme.",
}),
"serialize location = header",
);
assert_eq!(
serde_json::to_value(SecurityScheme::ApiKey(ApiKeySecurityScheme {
name: String::from("api_key"),
location: SecuritySchemeApiKeyLocation::Query,
description: Some(String::from("A short description for security scheme.")),
}))
.unwrap(),
json!({
"type": "apiKey",
"name": "api_key",
"in": "query",
"description": "A short description for security scheme.",
}),
"serialize location = query",
);
}
#[test]
fn test_security_scheme_oauth2_deserialize() {
assert_eq!(
serde_json::from_value::<SecurityScheme>(json!({
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets",
},
"description": "A short description for security scheme.",
}))
.unwrap(),
SecurityScheme::OAuth2(OAuth2SecurityScheme {
flow: SecuritySchemeOAuth2Flow::Implicit,
authorization_url: Some(String::from("https://example.com/api/oauth/dialog")),
token_url: None,
scopes: BTreeMap::from_iter(vec![
(
String::from("write:pets"),
String::from("modify pets in your account"),
),
(String::from("read:pets"), String::from("read your pets"),),
]),
description: Some(String::from("A short description for security scheme.")),
}),
"deserialize flow = implicit",
);
assert_eq!(
serde_json::from_value::<SecurityScheme>(json!({
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets",
},
"description": "A short description for security scheme.",
}))
.unwrap(),
SecurityScheme::OAuth2(OAuth2SecurityScheme {
flow: SecuritySchemeOAuth2Flow::AccessCode,
authorization_url: Some(String::from("https://example.com/api/oauth/dialog")),
token_url: None,
scopes: BTreeMap::from_iter(vec![
(
String::from("write:pets"),
String::from("modify pets in your account"),
),
(String::from("read:pets"), String::from("read your pets"),),
]),
description: Some(String::from("A short description for security scheme.")),
}),
"deserialize flow = accessCode",
);
assert_eq!(
serde_json::from_value::<SecurityScheme>(json!({
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets",
},
"description": "A short description for security scheme.",
}))
.unwrap(),
SecurityScheme::OAuth2(OAuth2SecurityScheme {
flow: SecuritySchemeOAuth2Flow::Password,
authorization_url: None,
token_url: Some(String::from("https://example.com/api/oauth/dialog")),
scopes: BTreeMap::from_iter(vec![
(
String::from("write:pets"),
String::from("modify pets in your account"),
),
(String::from("read:pets"), String::from("read your pets"),),
]),
description: Some(String::from("A short description for security scheme.")),
}),
"deserialize flow = password",
);
assert_eq!(
serde_json::from_value::<SecurityScheme>(json!({
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets",
},
"description": "A short description for security scheme.",
}))
.unwrap(),
SecurityScheme::OAuth2(OAuth2SecurityScheme {
flow: SecuritySchemeOAuth2Flow::Application,
authorization_url: None,
token_url: Some(String::from("https://example.com/api/oauth/dialog")),
scopes: BTreeMap::from_iter(vec![
(
String::from("write:pets"),
String::from("modify pets in your account"),
),
(String::from("read:pets"), String::from("read your pets"),),
]),
description: Some(String::from("A short description for security scheme.")),
}),
"deserialize flow = application",
);
}
#[test]
fn test_security_scheme_oauth2_serialize() {
assert_eq!(
serde_json::to_value(SecurityScheme::OAuth2(OAuth2SecurityScheme {
flow: SecuritySchemeOAuth2Flow::Implicit,
authorization_url: Some(String::from("https://example.com/api/oauth/dialog")),
token_url: None,
scopes: BTreeMap::from_iter(vec![
(
String::from("write:pets"),
String::from("modify pets in your account"),
),
(String::from("read:pets"), String::from("read your pets"),),
]),
description: Some(String::from("A short description for security scheme.")),
}))
.unwrap(),
json!({
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets",
},
"description": "A short description for security scheme.",
}),
"serialize flow = implicit",
);
assert_eq!(
serde_json::to_value(SecurityScheme::OAuth2(OAuth2SecurityScheme {
flow: SecuritySchemeOAuth2Flow::AccessCode,
authorization_url: Some(String::from("https://example.com/api/oauth/dialog")),
token_url: None,
scopes: BTreeMap::from_iter(vec![
(
String::from("write:pets"),
String::from("modify pets in your account"),
),
(String::from("read:pets"), String::from("read your pets"),),
]),
description: Some(String::from("A short description for security scheme.")),
}))
.unwrap(),
json!({
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets",
},
"description": "A short description for security scheme.",
}),
"serialize flow = accessCode",
);
assert_eq!(
serde_json::to_value(SecurityScheme::OAuth2(OAuth2SecurityScheme {
flow: SecuritySchemeOAuth2Flow::Password,
authorization_url: None,
token_url: Some(String::from("https://example.com/api/oauth/dialog")),
scopes: BTreeMap::from_iter(vec![
(
String::from("write:pets"),
String::from("modify pets in your account"),
),
(String::from("read:pets"), String::from("read your pets"),),
]),
description: Some(String::from("A short description for security scheme.")),
}))
.unwrap(),
json!({
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets",
},
"description": "A short description for security scheme.",
}),
"serialize flow = password",
);
assert_eq!(
serde_json::to_value(SecurityScheme::OAuth2(OAuth2SecurityScheme {
flow: SecuritySchemeOAuth2Flow::Application,
authorization_url: None,
token_url: Some(String::from("https://example.com/api/oauth/dialog")),
scopes: BTreeMap::from_iter(vec![
(
String::from("write:pets"),
String::from("modify pets in your account"),
),
(String::from("read:pets"), String::from("read your pets"),),
]),
description: Some(String::from("A short description for security scheme.")),
}))
.unwrap(),
json!({
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets",
},
"description": "A short description for security scheme.",
}),
"serialize flow = application",
);
}
}