use std::net::IpAddr;
#[cfg(feature = "maxmind")]
#[cfg_attr(docsrs, doc(cfg(feature = "maxmind")))]
mod maxmind;
#[cfg(feature = "maxmind")]
#[cfg_attr(docsrs, doc(cfg(feature = "maxmind")))]
pub use maxmind::{MaxMindDatabase, MaxMindError, MaxMindIntel};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct IpInfo {
pub country: Option<String>,
pub asn: Option<u32>,
}
impl IpInfo {
pub fn new(country: Option<String>, asn: Option<u32>) -> Self {
Self { country, asn }
}
}
pub trait IpIntel: Send + Sync + 'static {
fn lookup(&self, ip: IpAddr) -> IpInfo;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NoIpIntel;
impl IpIntel for NoIpIntel {
fn lookup(&self, _ip: IpAddr) -> IpInfo {
IpInfo::default()
}
}
impl<F> IpIntel for F
where
F: Fn(IpAddr) -> IpInfo + Send + Sync + 'static,
{
fn lookup(&self, ip: IpAddr) -> IpInfo {
self(ip)
}
}
impl<T: IpIntel + ?Sized> IpIntel for std::sync::Arc<T> {
fn lookup(&self, ip: IpAddr) -> IpInfo {
(**self).lookup(ip)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn closures_are_sources() {
let intel = |_ip: IpAddr| IpInfo::new(Some("BE".into()), Some(5432));
assert_eq!(intel.lookup("1.2.3.4".parse().unwrap()).asn, Some(5432));
assert_eq!(NoIpIntel.lookup("1.2.3.4".parse().unwrap()), IpInfo::default());
}
#[test]
fn shared_sources_are_sources() {
fn source(intel: impl IpIntel) -> impl IpIntel {
intel
}
#[derive(Debug)]
struct Fixed;
impl IpIntel for Fixed {
fn lookup(&self, _ip: IpAddr) -> IpInfo {
IpInfo::new(Some("BE".into()), None)
}
}
let shared = std::sync::Arc::new(Fixed);
let intel = source(std::sync::Arc::clone(&shared));
assert_eq!(intel.lookup("1.2.3.4".parse().unwrap()).country.as_deref(), Some("BE"));
}
}