use std::collections::BTreeMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SecurityRequirement {
#[serde(default)]
pub schemes: BTreeMap<Arc<str>, StringList>,
}
impl SecurityRequirement {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_scheme(
mut self,
name: impl Into<Arc<str>>,
scopes: impl IntoIterator<Item = impl Into<Arc<str>>>,
) -> Self {
self.schemes.insert(
name.into(),
StringList {
list: scopes.into_iter().map(Into::into).collect(),
},
);
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct StringList {
pub list: Vec<Arc<str>>,
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::{SecurityRequirement, StringList};
type TestError = Box<dyn std::error::Error>;
#[test]
fn empty_requirement_serialises_as_openapi_no_auth() -> Result<(), TestError> {
let req = SecurityRequirement::new();
let value = serde_json::to_value(&req)?;
let Some(obj) = value.as_object() else {
return Err("a SecurityRequirement must serialise to a JSON object"
.to_owned()
.into());
};
let Some(schemes) = obj.get("schemes").and_then(serde_json::Value::as_object) else {
return Err("schemes must be a JSON object".to_owned().into());
};
assert!(schemes.is_empty());
Ok(())
}
#[test]
fn requirement_with_api_key_serialises_with_proto_shape() -> Result<(), TestError> {
let req = SecurityRequirement::new().with_scheme("apiKey", std::iter::empty::<Arc<str>>());
let value = serde_json::to_value(&req)?;
let Some(obj) = value.as_object() else {
return Err("a SecurityRequirement must serialise to a JSON object"
.to_owned()
.into());
};
let Some(schemes) = obj.get("schemes").and_then(serde_json::Value::as_object) else {
return Err("schemes must be a JSON object".to_owned().into());
};
let Some(api_key) = schemes.get("apiKey").and_then(serde_json::Value::as_object) else {
return Err("apiKey must be a JSON object".to_owned().into());
};
let list = api_key.get("list").and_then(serde_json::Value::as_array);
let list = list.map(std::vec::Vec::as_slice).unwrap_or_default();
assert!(list.is_empty());
Ok(())
}
#[test]
fn requirement_with_oauth_scopes_round_trips() -> Result<(), TestError> {
let req = SecurityRequirement::new()
.with_scheme("oauth2", [Arc::from("read"), Arc::from("write")]);
let json = serde_json::to_string(&req)?;
let parsed: SecurityRequirement = serde_json::from_str(&json)?;
assert_eq!(parsed, req);
let value = serde_json::to_value(&req)?;
let Some(obj) = value.as_object() else {
return Err("a SecurityRequirement must serialise to a JSON object"
.to_owned()
.into());
};
let Some(schemes) = obj.get("schemes").and_then(serde_json::Value::as_object) else {
return Err("schemes must be a JSON object".to_owned().into());
};
let Some(oauth2) = schemes.get("oauth2").and_then(serde_json::Value::as_object) else {
return Err("oauth2 must be a JSON object".to_owned().into());
};
let list = oauth2
.get("list")
.and_then(serde_json::Value::as_array)
.map(std::vec::Vec::as_slice)
.unwrap_or_default();
let scope_strs: Vec<&str> = list.iter().filter_map(serde_json::Value::as_str).collect();
assert_eq!(scope_strs, ["read", "write"]);
Ok(())
}
#[test]
fn string_list_round_trips() -> Result<(), TestError> {
let list = StringList {
list: vec![Arc::from("a"), Arc::from("b")],
};
let json = serde_json::to_string(&list)?;
let parsed: StringList = serde_json::from_str(&json)?;
assert_eq!(parsed, list);
Ok(())
}
}