use std::sync::Arc;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum SecurityScheme {
#[serde(rename = "apiKey")]
ApiKey(APIKeySecurityScheme),
#[serde(rename = "http")]
Http(HTTPAuthSecurityScheme),
#[serde(rename = "oauth2")]
OAuth2(Box<super::oauth_flow::OAuth2SecurityScheme>),
#[serde(rename = "openIdConnect")]
OpenIdConnect(OpenIdConnectSecurityScheme),
#[serde(rename = "mutualTls")]
MutualTls(MutualTlsSecurityScheme),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenIdConnectSecurityScheme {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<Arc<str>>,
pub open_id_connect_url: Url,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MutualTlsSecurityScheme {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<Arc<str>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct APIKeySecurityScheme {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<Arc<str>>,
#[serde(rename = "in")]
pub in_: APIKeyLocation,
pub name: Arc<str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum APIKeyLocation {
Query,
Header,
Cookie,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HTTPAuthSecurityScheme {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<Arc<str>>,
pub scheme: Arc<str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bearer_format: Option<Arc<str>>,
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::{
APIKeyLocation, APIKeySecurityScheme, HTTPAuthSecurityScheme, MutualTlsSecurityScheme,
OpenIdConnectSecurityScheme, SecurityScheme,
};
type TestError = Box<dyn std::error::Error>;
#[test]
fn api_key_serialises_with_openapi_wire_form() -> Result<(), TestError> {
let scheme = SecurityScheme::ApiKey(APIKeySecurityScheme {
description: None,
in_: APIKeyLocation::Header,
name: Arc::from("X-API-Key"),
});
let value = serde_json::to_value(&scheme)?;
let Some(obj) = value.as_object() else {
return Err("a SecurityScheme must serialise to a JSON object"
.to_owned()
.into());
};
assert_eq!(
obj.get("type").and_then(serde_json::Value::as_str),
Some("apiKey"),
"the type field must be the OpenAPI literal \"apiKey\""
);
assert_eq!(
obj.get("in").and_then(serde_json::Value::as_str),
Some("header"),
"the in field must be the OpenAPI name for location"
);
assert_eq!(
obj.get("name").and_then(serde_json::Value::as_str),
Some("X-API-Key")
);
assert!(
!obj.contains_key("description"),
"an absent description must not serialise as null"
);
Ok(())
}
#[test]
fn api_key_round_trips_through_serde() -> Result<(), TestError> {
let scheme = SecurityScheme::ApiKey(APIKeySecurityScheme {
description: Some(Arc::from("API key for the public read endpoint.")),
in_: APIKeyLocation::Query,
name: Arc::from("api_key"),
});
let json = serde_json::to_string(&scheme)?;
let parsed: SecurityScheme = serde_json::from_str(&json)?;
assert_eq!(parsed, scheme);
Ok(())
}
#[test]
fn api_key_location_serialises_as_openapi_literal() {
let query = serde_json::to_value(APIKeyLocation::Query).ok();
let header = serde_json::to_value(APIKeyLocation::Header).ok();
let cookie = serde_json::to_value(APIKeyLocation::Cookie).ok();
assert_eq!(query.as_ref().and_then(|v| v.as_str()), Some("query"));
assert_eq!(header.as_ref().and_then(|v| v.as_str()), Some("header"));
assert_eq!(cookie.as_ref().and_then(|v| v.as_str()), Some("cookie"));
}
#[test]
fn http_bearer_serialises_with_openapi_wire_form() -> Result<(), TestError> {
let scheme = SecurityScheme::Http(HTTPAuthSecurityScheme {
description: None,
scheme: Arc::from("Bearer"),
bearer_format: Some(Arc::from("JWT")),
});
let value = serde_json::to_value(&scheme)?;
let Some(obj) = value.as_object() else {
return Err("a SecurityScheme must serialise to a JSON object"
.to_owned()
.into());
};
assert_eq!(
obj.get("type").and_then(serde_json::Value::as_str),
Some("http")
);
assert_eq!(
obj.get("scheme").and_then(serde_json::Value::as_str),
Some("Bearer")
);
assert_eq!(
obj.get("bearerFormat").and_then(serde_json::Value::as_str),
Some("JWT")
);
Ok(())
}
#[test]
fn http_basic_round_trips_through_serde() -> Result<(), TestError> {
let scheme = SecurityScheme::Http(HTTPAuthSecurityScheme {
description: Some(Arc::from("HTTP Basic auth.")),
scheme: Arc::from("Basic"),
bearer_format: None,
});
let json = serde_json::to_string(&scheme)?;
let parsed: SecurityScheme = serde_json::from_str(&json)?;
assert_eq!(parsed, scheme);
Ok(())
}
#[test]
fn open_id_connect_serialises_with_openapi_wire_form() -> Result<(), TestError> {
let scheme = SecurityScheme::OpenIdConnect(OpenIdConnectSecurityScheme {
description: Some(Arc::from("Login with the corporate OIDC provider.")),
open_id_connect_url: url::Url::parse(
"https://auth.example.com/.well-known/openid-configuration",
)?,
});
let value = serde_json::to_value(&scheme)?;
let Some(obj) = value.as_object() else {
return Err("a SecurityScheme must serialise to a JSON object"
.to_owned()
.into());
};
assert_eq!(
obj.get("type").and_then(serde_json::Value::as_str),
Some("openIdConnect"),
"the type field must be the OpenAPI literal \"openIdConnect\""
);
assert_eq!(
obj.get("openIdConnectUrl")
.and_then(serde_json::Value::as_str),
Some("https://auth.example.com/.well-known/openid-configuration")
);
Ok(())
}
#[test]
fn open_id_connect_round_trips_through_serde() -> Result<(), TestError> {
let scheme = SecurityScheme::OpenIdConnect(OpenIdConnectSecurityScheme {
description: None,
open_id_connect_url: url::Url::parse(
"https://auth.example.com/.well-known/openid-configuration",
)?,
});
let json = serde_json::to_string(&scheme)?;
let parsed: SecurityScheme = serde_json::from_str(&json)?;
assert_eq!(parsed, scheme);
Ok(())
}
#[test]
fn mutual_tls_serialises_with_openapi_wire_form() -> Result<(), TestError> {
let scheme = SecurityScheme::MutualTls(MutualTlsSecurityScheme {
description: Some(Arc::from(
"Client certs validated against the corporate CA.",
)),
});
let value = serde_json::to_value(&scheme)?;
let Some(obj) = value.as_object() else {
return Err("a SecurityScheme must serialise to a JSON object"
.to_owned()
.into());
};
assert_eq!(
obj.get("type").and_then(serde_json::Value::as_str),
Some("mutualTls"),
"the type field must be the OpenAPI literal \"mutualTls\""
);
assert_eq!(
obj.get("description").and_then(serde_json::Value::as_str),
Some("Client certs validated against the corporate CA.")
);
Ok(())
}
#[test]
fn mutual_tls_round_trips_through_serde() -> Result<(), TestError> {
let scheme = SecurityScheme::MutualTls(MutualTlsSecurityScheme { description: None });
let json = serde_json::to_string(&scheme)?;
let parsed: SecurityScheme = serde_json::from_str(&json)?;
assert_eq!(parsed, scheme);
Ok(())
}
}