1use std::net::IpAddr;
4
5#[cfg(feature = "maxmind")]
6#[cfg_attr(docsrs, doc(cfg(feature = "maxmind")))]
7mod maxmind;
8#[cfg(feature = "maxmind")]
9#[cfg_attr(docsrs, doc(cfg(feature = "maxmind")))]
10pub use maxmind::{MaxMindDatabase, MaxMindError, MaxMindIntel};
11
12#[derive(Clone, Debug, Default, PartialEq, Eq)]
14#[non_exhaustive]
15pub struct IpInfo {
16 pub country: Option<String>,
18 pub asn: Option<u32>,
20}
21
22impl IpInfo {
23 pub fn new(country: Option<String>, asn: Option<u32>) -> Self {
25 Self { country, asn }
26 }
27}
28
29pub trait IpIntel: Send + Sync + 'static {
34 fn lookup(&self, ip: IpAddr) -> IpInfo;
36}
37
38#[derive(Clone, Copy, Debug, Default)]
40pub struct NoIpIntel;
41
42impl IpIntel for NoIpIntel {
43 fn lookup(&self, _ip: IpAddr) -> IpInfo {
44 IpInfo::default()
45 }
46}
47
48impl<F> IpIntel for F
49where
50 F: Fn(IpAddr) -> IpInfo + Send + Sync + 'static,
51{
52 fn lookup(&self, ip: IpAddr) -> IpInfo {
53 self(ip)
54 }
55}
56
57impl<T: IpIntel + ?Sized> IpIntel for std::sync::Arc<T> {
60 fn lookup(&self, ip: IpAddr) -> IpInfo {
61 (**self).lookup(ip)
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn closures_are_sources() {
71 let intel = |_ip: IpAddr| IpInfo::new(Some("BE".into()), Some(5432));
72 assert_eq!(intel.lookup("1.2.3.4".parse().unwrap()).asn, Some(5432));
73 assert_eq!(NoIpIntel.lookup("1.2.3.4".parse().unwrap()), IpInfo::default());
74 }
75
76 #[test]
77 fn shared_sources_are_sources() {
78 fn source(intel: impl IpIntel) -> impl IpIntel {
79 intel
80 }
81 #[derive(Debug)]
82 struct Fixed;
83 impl IpIntel for Fixed {
84 fn lookup(&self, _ip: IpAddr) -> IpInfo {
85 IpInfo::new(Some("BE".into()), None)
86 }
87 }
88 let shared = std::sync::Arc::new(Fixed);
89 let intel = source(std::sync::Arc::clone(&shared));
90 assert_eq!(intel.lookup("1.2.3.4".parse().unwrap()).country.as_deref(), Some("BE"));
91 }
92}