1use reqwest::Client;
33use serde::{Deserialize, Serialize};
34use std::time::Duration;
35
36const BASE_URL: &str = "https://myip.foo";
37const IPV4_URL: &str = "https://ipv4.myip.foo";
38const IPV6_URL: &str = "https://ipv6.myip.foo";
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct Location {
44 pub country: String,
45 pub city: String,
46 pub region: String,
47 pub postal_code: String,
48 pub timezone: String,
49 pub latitude: String,
50 pub longitude: String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct Network {
56 pub asn: i64,
57 pub isp: String,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Cloudflare {
63 pub colo: String,
64 pub ray: String,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct IpData {
71 pub ip: String,
72 #[serde(rename = "type")]
73 pub ip_type: String,
74 pub hostname: Option<String>,
75 pub connection_type: Option<String>,
76 pub location: Location,
77 pub network: Network,
78 pub cloudflare: Cloudflare,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct DualStackData {
84 pub ipv4: Option<String>,
85 pub ipv6: Option<String>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ConnectionTypeData {
91 pub ip: String,
92 #[serde(rename = "type")]
93 pub connection_type: String,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98struct IpResponse {
99 ip: String,
100}
101
102#[derive(Debug)]
104pub enum Error {
105 Request(reqwest::Error),
106 Parse(serde_json::Error),
107}
108
109impl std::fmt::Display for Error {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 match self {
112 Error::Request(e) => write!(f, "Request error: {}", e),
113 Error::Parse(e) => write!(f, "Parse error: {}", e),
114 }
115 }
116}
117
118impl std::error::Error for Error {}
119
120impl From<reqwest::Error> for Error {
121 fn from(err: reqwest::Error) -> Self {
122 Error::Request(err)
123 }
124}
125
126impl From<serde_json::Error> for Error {
127 fn from(err: serde_json::Error) -> Self {
128 Error::Parse(err)
129 }
130}
131
132pub type Result<T> = std::result::Result<T, Error>;
134
135fn create_client() -> reqwest::Result<Client> {
136 Client::builder()
137 .user_agent("myip-foo/1.0.0")
138 .timeout(Duration::from_secs(10))
139 .build()
140}
141
142pub async fn get_ip() -> Result<String> {
155 let client = create_client()?;
156 let response = client
157 .get(format!("{}/plain", BASE_URL))
158 .send()
159 .await?
160 .text()
161 .await?;
162 Ok(response.trim().to_string())
163}
164
165pub async fn get_ip_data() -> Result<IpData> {
180 let client = create_client()?;
181 let response = client
182 .get(format!("{}/api", BASE_URL))
183 .send()
184 .await?
185 .json()
186 .await?;
187 Ok(response)
188}
189
190pub async fn get_dual_stack() -> Result<DualStackData> {
210 let client = Client::builder()
211 .user_agent("myip-foo/1.0.0")
212 .timeout(Duration::from_secs(5))
213 .build()?;
214
215 let ipv4 = match client.get(format!("{}/ip", IPV4_URL)).send().await {
216 Ok(resp) => match resp.json::<IpResponse>().await {
217 Ok(data) => Some(data.ip),
218 Err(_) => None,
219 },
220 Err(_) => None,
221 };
222
223 let ipv6 = match client.get(format!("{}/ip", IPV6_URL)).send().await {
224 Ok(resp) => match resp.json::<IpResponse>().await {
225 Ok(data) => Some(data.ip),
226 Err(_) => None,
227 },
228 Err(_) => None,
229 };
230
231 Ok(DualStackData { ipv4, ipv6 })
232}
233
234pub async fn get_connection_type() -> Result<ConnectionTypeData> {
247 let client = create_client()?;
248 let response = client
249 .get(format!("{}/api/connection-type", BASE_URL))
250 .send()
251 .await?
252 .json()
253 .await?;
254 Ok(response)
255}
256
257pub async fn get_headers() -> Result<std::collections::HashMap<String, String>> {
272 let client = create_client()?;
273 let response = client
274 .get(format!("{}/headers", BASE_URL))
275 .send()
276 .await?
277 .json()
278 .await?;
279 Ok(response)
280}
281
282pub async fn get_user_agent() -> Result<String> {
295 let client = create_client()?;
296 let response: serde_json::Value = client
297 .get(format!("{}/user-agent", BASE_URL))
298 .send()
299 .await?
300 .json()
301 .await?;
302
303 Ok(response["userAgent"]
304 .as_str()
305 .unwrap_or("")
306 .to_string())
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[tokio::test]
314 async fn test_get_ip() {
315 let result = get_ip().await;
316 assert!(result.is_ok());
317 let ip = result.unwrap();
318 assert!(!ip.is_empty());
319 }
320}