use std::{
fmt::{self, Display},
net::IpAddr,
sync::Arc,
};
use http::Request;
use super::error::RateLimitError;
pub trait KeyExtractor: Clone {
type Key: Display;
fn extract<T>(&self, request: &Request<T>) -> Result<Self::Key, RateLimitError>;
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct IpKeyExtractor;
impl IpKeyExtractor {
pub const fn new() -> Self {
Self
}
}
impl KeyExtractor for IpKeyExtractor {
type Key = IpAddr;
fn extract<T>(&self, request: &Request<T>) -> Result<Self::Key, RateLimitError> {
http_extract::extract_socket_ip(request).ok_or_else(|| {
RateLimitError::Key(
String::from("socket_ip_unavailable"),
String::from("request extensions do not contain a socket ip address"),
)
})
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ClientIpKeyExtractor;
impl ClientIpKeyExtractor {
pub const fn new() -> Self {
Self
}
}
impl KeyExtractor for ClientIpKeyExtractor {
type Key = IpAddr;
fn extract<T>(&self, request: &Request<T>) -> Result<Self::Key, RateLimitError> {
http_extract::extract_proxy_client_ip(request)
.map_err(|error| RateLimitError::Key(String::from("invalid_client_ip"), error.to_string()))?
.ok_or_else(|| {
RateLimitError::Key(
String::from("client_ip_unavailable"),
String::from("request does not contain a client or socket IP address"),
)
})
}
}
#[derive(Clone)]
pub struct TrustedProxyClientIpKeyExtractor {
is_trusted_proxy: Arc<dyn Fn(IpAddr) -> bool + Send + Sync>,
}
impl fmt::Debug for TrustedProxyClientIpKeyExtractor {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("TrustedProxyClientIpKeyExtractor")
.finish_non_exhaustive()
}
}
impl TrustedProxyClientIpKeyExtractor {
pub fn new<F>(is_trusted_proxy: F) -> Self
where
F: Fn(IpAddr) -> bool + Send + Sync + 'static,
{
Self {
is_trusted_proxy: Arc::new(is_trusted_proxy),
}
}
}
impl KeyExtractor for TrustedProxyClientIpKeyExtractor {
type Key = IpAddr;
fn extract<T>(&self, request: &Request<T>) -> Result<Self::Key, RateLimitError> {
let peer = http_extract::extract_socket_ip(request).ok_or_else(|| {
RateLimitError::Key(
String::from("socket_ip_unavailable"),
String::from("request extensions do not contain a socket ip address"),
)
})?;
if !(self.is_trusted_proxy)(peer) {
return Ok(peer);
}
http_extract::extract_client_ip(request.headers())
.map(|client_ip| client_ip.unwrap_or(peer))
.map_err(|error| RateLimitError::Key(String::from("invalid_client_ip"), error.to_string()))
}
}