use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ApiKeyLocation {
Header,
Query,
Cookie,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OAuthGrantType {
AuthorizationCode,
ClientCredentials,
Implicit,
Password,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct OAuthFlow {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub authorization_url: Option<String>,
pub token_url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub refresh_url: Option<String>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub scopes: IndexMap<String, String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct OAuthFlows {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub authorization_code: Option<OAuthFlow>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client_credentials: Option<OAuthFlow>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub implicit: Option<OAuthFlow>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub password: Option<OAuthFlow>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "scheme_type", rename_all = "snake_case")]
pub enum AuthScheme {
ApiKey {
location: ApiKeyLocation,
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
},
Http {
scheme: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
bearer_format: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
},
OAuth2 {
flows: OAuthFlows,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
},
OpenIdConnect {
open_id_connect_url: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
scopes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
},
Custom {
tag: String,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
properties: serde_json::Value,
},
}
impl AuthScheme {
#[must_use]
pub fn kind(&self) -> &'static str {
match self {
Self::ApiKey { .. } => "api_key",
Self::Http { .. } => "http",
Self::OAuth2 { .. } => "oauth2",
Self::OpenIdConnect { .. } => "oidc",
Self::Custom { .. } => "custom",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn api_key_scheme_round_trip() {
let s = AuthScheme::ApiKey {
location: ApiKeyLocation::Header,
name: "X-API-Key".into(),
description: None,
};
let j = serde_json::to_string(&s).unwrap();
assert!(j.contains("\"api_key\""));
assert!(j.contains("\"header\""));
let back: AuthScheme = serde_json::from_str(&j).unwrap();
assert_eq!(s, back);
}
#[test]
fn oauth2_scheme_with_authorization_code_flow() {
let mut scopes = IndexMap::new();
scopes.insert("read".to_string(), "Read scope".to_string());
let s = AuthScheme::OAuth2 {
flows: OAuthFlows {
authorization_code: Some(OAuthFlow {
authorization_url: Some("https://provider/authorize".into()),
token_url: "https://provider/token".into(),
refresh_url: None,
scopes,
}),
..OAuthFlows::default()
},
description: None,
};
let back: AuthScheme = serde_json::from_str(&serde_json::to_string(&s).unwrap()).unwrap();
assert_eq!(s, back);
assert_eq!(s.kind(), "oauth2");
}
}