use std::collections::BTreeMap;
use std::sync::Arc;
pub const ON_BLOCK_EXCLUDED_CHECK_NAMES: [&str; 3] =
["custom_request", "custom_validators", "https_enforcement"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockPayload {
pub check_name: String,
pub reason: String,
pub trigger_info: String,
pub passive_mode: bool,
pub client_ip: String,
pub path: String,
pub method: String,
pub status_code: Option<u16>,
}
pub type OnBlockHook = Arc<dyn Fn(&BlockPayload) + Send + Sync>;
pub type OnErrorFn = Arc<dyn Fn(&str, &str, &BTreeMap<String, String>) + Send + Sync>;
#[derive(Debug, Clone, Default)]
pub struct RequestBits {
pub method: String,
pub url_path: String,
pub client_ip: String,
pub origin: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ResponseBits {
pub status: u16,
pub body: Option<String>,
pub headers: BTreeMap<String, String>,
}
pub type ResponseModifierFn = Arc<dyn Fn(&mut ResponseBits) + Send + Sync>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn payload_and_bits_are_constructible_and_default() {
let payload = BlockPayload {
check_name: String::from("rate_limit"),
reason: String::from("Rate limit exceeded"),
trigger_info: String::new(),
passive_mode: false,
client_ip: String::from("192.0.2.1"),
path: String::from("/login"),
method: String::from("GET"),
status_code: Some(429),
};
assert_eq!(payload.status_code, Some(429));
let bits = ResponseBits::default();
assert_eq!(bits.status, 0);
assert!(bits.body.is_none());
assert!(bits.headers.is_empty());
let bits = RequestBits::default();
assert!(bits.client_ip.is_empty());
assert!(bits.origin.is_none());
}
#[test]
fn hooks_receive_their_views() {
let seen = Arc::new(std::sync::Mutex::new(Vec::new()));
let sink = Arc::clone(&seen);
let hook: OnBlockHook = Arc::new(move |payload: &BlockPayload| {
sink.lock().expect("sink").push(payload.check_name.clone());
});
let payload = BlockPayload {
check_name: String::from("ip_security"),
reason: String::new(),
trigger_info: String::new(),
passive_mode: true,
client_ip: String::new(),
path: String::new(),
method: String::new(),
status_code: None,
};
hook(&payload);
assert_eq!(seen.lock().expect("sink").as_slice(), ["ip_security"]);
let errors = Arc::new(std::sync::Mutex::new(Vec::new()));
let sink = Arc::clone(&errors);
let on_error: OnErrorFn = Arc::new(move |stage: &str, message: &str, _| {
sink.lock()
.expect("sink")
.push((stage.to_owned(), message.to_owned()));
});
let mut metadata = BTreeMap::new();
metadata.insert(String::from("k"), String::from("v"));
on_error("cloud_fetch", "boom", &metadata);
assert_eq!(
errors.lock().expect("sink").as_slice(),
[("cloud_fetch".to_owned(), "boom".to_owned())]
);
let modifier: ResponseModifierFn = Arc::new(|bits: &mut ResponseBits| {
bits.headers
.insert(String::from("x-modified"), String::from("1"));
});
let mut bits = ResponseBits::default();
modifier(&mut bits);
assert_eq!(
bits.headers.get("x-modified").map(String::as_str),
Some("1")
);
}
}