#[derive(Clone, Debug)]
pub enum CertificationMode {
Skip,
ResponseOnly(ResponseOnlyConfig),
Full(FullConfig),
}
impl CertificationMode {
pub fn skip() -> Self {
Self::Skip
}
pub fn response_only() -> Self {
Self::ResponseOnly(ResponseOnlyConfig::default())
}
pub fn authenticated() -> Self {
Self::Full(
FullConfig::builder()
.with_request_headers(&["authorization"])
.with_response_headers(&["content-type"])
.build(),
)
}
}
impl Default for CertificationMode {
fn default() -> Self {
Self::response_only()
}
}
#[derive(Clone, Debug)]
pub struct ResponseOnlyConfig {
pub include_headers: Vec<String>,
pub exclude_headers: Vec<String>,
}
impl Default for ResponseOnlyConfig {
fn default() -> Self {
Self {
include_headers: vec!["*".to_string()],
exclude_headers: vec![
"date".to_string(),
"ic-certificate".to_string(),
"ic-certificate-expression".to_string(),
],
}
}
}
#[derive(Clone, Debug)]
#[derive(Default)]
pub struct FullConfig {
pub request_headers: Vec<String>,
pub query_params: Vec<String>,
pub response: ResponseOnlyConfig,
}
impl FullConfig {
pub fn builder() -> FullConfigBuilder {
FullConfigBuilder::default()
}
}
#[derive(Default)]
pub struct FullConfigBuilder {
request_headers: Vec<String>,
query_params: Vec<String>,
include_response_headers: Vec<String>,
exclude_response_headers: Vec<String>,
}
impl FullConfigBuilder {
pub fn with_request_headers(mut self, headers: &[&str]) -> Self {
self.request_headers = headers.iter().map(|s| s.to_lowercase()).collect();
self
}
pub fn with_query_params(mut self, params: &[&str]) -> Self {
self.query_params = params.iter().map(|s| s.to_string()).collect();
self
}
pub fn with_response_headers(mut self, headers: &[&str]) -> Self {
self.include_response_headers = headers.iter().map(|s| s.to_lowercase()).collect();
self
}
pub fn excluding_response_headers(mut self, headers: &[&str]) -> Self {
self.exclude_response_headers = headers.iter().map(|s| s.to_lowercase()).collect();
self
}
pub fn build(self) -> FullConfig {
FullConfig {
request_headers: self.request_headers,
query_params: self.query_params,
response: ResponseOnlyConfig {
include_headers: if self.include_response_headers.is_empty() {
vec!["*".to_string()]
} else {
self.include_response_headers
},
exclude_headers: self.exclude_response_headers,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_response_only() {
let mode = CertificationMode::default();
assert!(matches!(mode, CertificationMode::ResponseOnly(_)));
}
#[test]
fn skip_produces_skip() {
let mode = CertificationMode::skip();
assert!(matches!(mode, CertificationMode::Skip));
}
#[test]
fn response_only_has_correct_default_config() {
let mode = CertificationMode::response_only();
match mode {
CertificationMode::ResponseOnly(config) => {
assert_eq!(config.include_headers, vec!["*"]);
assert_eq!(
config.exclude_headers,
vec!["date", "ic-certificate", "ic-certificate-expression"]
);
}
_ => panic!("expected ResponseOnly"),
}
}
#[test]
fn authenticated_has_authorization_in_request_headers() {
let mode = CertificationMode::authenticated();
match mode {
CertificationMode::Full(config) => {
assert_eq!(config.request_headers, vec!["authorization"]);
assert_eq!(config.response.include_headers, vec!["content-type"]);
}
_ => panic!("expected Full"),
}
}
#[test]
fn builder_with_all_options() {
let config = FullConfig::builder()
.with_request_headers(&["Authorization", "Accept"])
.with_query_params(&["page", "limit"])
.with_response_headers(&["Content-Type", "ETag"])
.excluding_response_headers(&["Set-Cookie"])
.build();
assert_eq!(config.request_headers, vec!["authorization", "accept"]);
assert_eq!(config.query_params, vec!["page", "limit"]);
assert_eq!(
config.response.include_headers,
vec!["content-type", "etag"]
);
assert_eq!(config.response.exclude_headers, vec!["set-cookie"]);
}
#[test]
fn builder_with_partial_options() {
let config = FullConfig::builder()
.with_request_headers(&["authorization"])
.build();
assert_eq!(config.request_headers, vec!["authorization"]);
assert!(config.query_params.is_empty());
assert_eq!(config.response.include_headers, vec!["*"]);
assert!(config.response.exclude_headers.is_empty());
}
#[test]
fn builder_with_no_options() {
let config = FullConfig::builder().build();
assert!(config.request_headers.is_empty());
assert!(config.query_params.is_empty());
assert_eq!(config.response.include_headers, vec!["*"]);
assert!(config.response.exclude_headers.is_empty());
}
#[test]
fn header_normalization_to_lowercase() {
let config = FullConfig::builder()
.with_request_headers(&["AUTHORIZATION", "Accept-Encoding"])
.with_response_headers(&["Content-Type", "X-CUSTOM-HEADER"])
.excluding_response_headers(&["SET-COOKIE"])
.build();
assert_eq!(
config.request_headers,
vec!["authorization", "accept-encoding"]
);
assert_eq!(
config.response.include_headers,
vec!["content-type", "x-custom-header"]
);
assert_eq!(config.response.exclude_headers, vec!["set-cookie"]);
}
#[test]
fn full_config_default() {
let config = FullConfig::default();
assert!(config.request_headers.is_empty());
assert!(config.query_params.is_empty());
assert_eq!(config.response.include_headers, vec!["*"]);
assert_eq!(
config.response.exclude_headers,
vec!["date", "ic-certificate", "ic-certificate-expression"]
);
}
#[test]
fn response_only_config_default() {
let config = ResponseOnlyConfig::default();
assert_eq!(config.include_headers, vec!["*"]);
assert_eq!(
config.exclude_headers,
vec!["date", "ic-certificate", "ic-certificate-expression"]
);
}
#[test]
fn certification_mode_clone_and_debug() {
let mode = CertificationMode::authenticated();
let cloned = mode.clone();
let _debug = format!("{:?}", cloned);
}
}