use crate::error::Result;
use bytes::Bytes;
use http::{HeaderMap, Method};
use std::sync::Arc;
pub type RequestInterceptor = Arc<dyn Fn(&mut RequestData) + Send + Sync>;
pub type ResponseInterceptor = Arc<dyn Fn(&ResponseData) + Send + Sync>;
#[derive(Debug)]
pub struct RequestData {
pub method: Method,
pub url: String,
pub headers: HeaderMap,
pub body: Option<Bytes>,
}
impl RequestData {
pub fn new(method: Method, url: String, headers: HeaderMap, body: Option<Bytes>) -> Self {
Self {
method,
url,
headers,
body,
}
}
pub fn set_header(&mut self, name: &str, value: &str) -> Result<()> {
use http::header::{HeaderName, HeaderValue};
let name = HeaderName::from_bytes(name.as_bytes())
.map_err(|_| crate::error::Error::InvalidHeader {
name: name.to_string(),
value: value.to_string(),
})?;
let value = HeaderValue::from_str(value)
.map_err(|_| crate::error::Error::InvalidHeader {
name: name.to_string(),
value: value.to_string(),
})?;
self.headers.insert(name, value);
Ok(())
}
pub fn remove_header(&mut self, name: &str) {
if let Ok(name) = http::HeaderName::from_bytes(name.as_bytes()) {
self.headers.remove(&name);
}
}
pub fn get_header(&self, name: &str) -> Option<&str> {
let name = http::HeaderName::from_bytes(name.as_bytes()).ok()?;
self.headers.get(&name)?.to_str().ok()
}
}
#[derive(Debug)]
pub struct ResponseData {
pub status: u16,
pub headers: HeaderMap,
pub body_size: usize,
pub duration_ms: u64,
}
impl ResponseData {
pub fn new(status: u16, headers: HeaderMap, body_size: usize, duration_ms: u64) -> Self {
Self {
status,
headers,
body_size,
duration_ms,
}
}
pub fn get_header(&self, name: &str) -> Option<&str> {
let name = http::HeaderName::from_bytes(name.as_bytes()).ok()?;
self.headers.get(&name)?.to_str().ok()
}
pub fn is_success(&self) -> bool {
self.status >= 200 && self.status < 300
}
pub fn is_redirect(&self) -> bool {
self.status >= 300 && self.status < 400
}
pub fn is_client_error(&self) -> bool {
self.status >= 400 && self.status < 500
}
pub fn is_server_error(&self) -> bool {
self.status >= 500 && self.status < 600
}
}
#[derive(Clone, Default)]
pub struct Interceptors {
pub(crate) request: Vec<RequestInterceptor>,
pub(crate) response: Vec<ResponseInterceptor>,
}
impl Interceptors {
pub fn new() -> Self {
Self {
request: Vec::new(),
response: Vec::new(),
}
}
pub fn add_request<F>(&mut self, interceptor: F)
where
F: Fn(&mut RequestData) + Send + Sync + 'static,
{
self.request.push(Arc::new(interceptor));
}
pub fn add_response<F>(&mut self, interceptor: F)
where
F: Fn(&ResponseData) + Send + Sync + 'static,
{
self.response.push(Arc::new(interceptor));
}
pub(crate) fn apply_request(&self, data: &mut RequestData) {
for interceptor in &self.request {
interceptor(data);
}
}
pub(crate) fn apply_response(&self, data: &ResponseData) {
for interceptor in &self.response {
interceptor(data);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_data_set_header() {
let mut data = RequestData::new(
Method::GET,
"https://example.com".to_string(),
HeaderMap::new(),
None,
);
data.set_header("User-Agent", "avx-http/0.2.0").unwrap();
assert_eq!(data.get_header("User-Agent"), Some("avx-http/0.2.0"));
}
#[test]
fn test_request_data_remove_header() {
let mut headers = HeaderMap::new();
headers.insert(
http::header::USER_AGENT,
http::HeaderValue::from_static("test"),
);
let mut data = RequestData::new(
Method::GET,
"https://example.com".to_string(),
headers,
None,
);
assert!(data.get_header("User-Agent").is_some());
data.remove_header("User-Agent");
assert!(data.get_header("User-Agent").is_none());
}
#[test]
fn test_response_data_status_checks() {
let data = ResponseData::new(200, HeaderMap::new(), 0, 0);
assert!(data.is_success());
assert!(!data.is_client_error());
let data = ResponseData::new(404, HeaderMap::new(), 0, 0);
assert!(data.is_client_error());
assert!(!data.is_success());
let data = ResponseData::new(500, HeaderMap::new(), 0, 0);
assert!(data.is_server_error());
assert!(!data.is_success());
}
#[test]
fn test_interceptors_add() {
let mut interceptors = Interceptors::new();
interceptors.add_request(|data| {
let _ = data.set_header("X-Test", "value");
});
interceptors.add_response(|data| {
println!("Response status: {}", data.status);
});
assert_eq!(interceptors.request.len(), 1);
assert_eq!(interceptors.response.len(), 1);
}
#[test]
fn test_interceptors_apply_request() {
let mut interceptors = Interceptors::new();
interceptors.add_request(|data| {
let _ = data.set_header("X-Custom", "test");
});
let mut data = RequestData::new(
Method::GET,
"https://example.com".to_string(),
HeaderMap::new(),
None,
);
interceptors.apply_request(&mut data);
assert_eq!(data.get_header("X-Custom"), Some("test"));
}
}