ip_api_io/lib.rs
1//! Official Rust client for the [ip-api.io](https://ip-api.io) IP intelligence
2//! and email validation API: IP geolocation, email validation, fraud detection
3//! and risk scoring, VPN/proxy/Tor detection, ASN lookup, WHOIS, DNS and
4//! domain age.
5//!
6//! Get a free API key at <https://ip-api.io>.
7//!
8//! ```no_run
9//! # async fn run() -> Result<(), ip_api_io::Error> {
10//! let client = ip_api_io::Client::with_api_key("YOUR_API_KEY");
11//!
12//! let info = client.lookup_ip("8.8.8.8").await?;
13//! println!("{:?} vpn={}", info.location.country, info.suspicious_factors.is_vpn);
14//!
15//! let risk = client.risk_score_ip("8.8.8.8").await?;
16//! println!("{} {}", risk.score, risk.risk_level);
17//! # Ok(())
18//! # }
19//! ```
20//!
21//! A synchronous client is available behind the `blocking` feature as
22//! [`blocking::Client`].
23
24#[cfg(feature = "blocking")]
25pub mod blocking;
26mod client;
27mod error;
28pub mod models;
29
30pub use client::{Client, ClientBuilder};
31pub use error::Error;
32pub use models::*;
33
34/// Client library version.
35pub const VERSION: &str = env!("CARGO_PKG_VERSION");
36
37pub(crate) const DEFAULT_BASE_URL: &str = "https://ip-api.io";
38pub(crate) const USER_AGENT: &str = concat!("ip-api-io-rust/", env!("CARGO_PKG_VERSION"));
39
40/// Documented per-request limit for batch endpoints.
41pub const MAX_BATCH_SIZE: usize = 100;
42
43pub(crate) fn check_batch(items: &[&str], name: &str) -> Result<(), Error> {
44 if items.is_empty() {
45 return Err(Error::InvalidArgument(format!("{name} must not be empty")));
46 }
47 if items.len() > MAX_BATCH_SIZE {
48 return Err(Error::InvalidArgument(format!(
49 "{name} must contain at most {MAX_BATCH_SIZE} items"
50 )));
51 }
52 Ok(())
53}
54
55/// RFC 3986 percent-encoding for a single path segment (zero-dependency).
56pub(crate) fn encode_segment(segment: &str) -> String {
57 let mut encoded = String::with_capacity(segment.len());
58 for byte in segment.bytes() {
59 match byte {
60 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
61 encoded.push(byte as char)
62 }
63 _ => encoded.push_str(&format!("%{byte:02X}")),
64 }
65 }
66 encoded
67}