isitbad_api/
lib.rs

1mod util;
2
3#[cfg(feature = "async")]
4mod apiasync;
5
6use util::structs::{IpInformation, FailedResponse};
7use serde_json::Value;
8
9pub fn get_ip_info(ip: String) -> Result<IpInformation, FailedResponse> {
10    let response = match ureq::get("https://funkemunky.cc/vpn")
11        .query("ip", ip.as_str())
12        .call() {
13        Ok(response) => response,
14        Err(_) => {
15            return Err(FailedResponse {
16                success: false,
17                reason: "Failed to fetch API request for unknown reason.".to_string(),
18            });
19        }
20    };
21
22    let status = response.status();
23
24    // Checking if the response is HTTP OK (200)
25    if status != 200 {
26        return Err(FailedResponse {
27            success: false,
28            reason: format!("Failed to fetch API request with status {status}."),
29        });
30    }
31
32    let json: Value = match response.into_json() {
33        Ok(json) => json,
34        Err(_) => {
35            return Err(FailedResponse {
36                success: false,
37                reason: "Failed to parse JSON response.".to_string(),
38            });
39        }
40    };
41
42    let is_success = match json.get("success") {
43        Some(success) => success.as_bool().unwrap(),
44        None => {
45            return Err(FailedResponse {
46                success: false,
47                reason: "Failed to parse JSON response for status".to_string(),
48            });
49        }
50    };
51
52    if !is_success {
53        let failed_response: FailedResponse = serde_json::from_value(json).unwrap();
54
55        return Err(failed_response);
56    }
57
58    let ip_info: IpInformation = match serde_json::from_value(json.clone()) {
59        Ok(ip_info) => ip_info,
60        Err(_) => {
61            return Err(FailedResponse {
62                success: false,
63                reason: format!("Failed to turn successful JSON response into \
64                IpInformation object. Raw Response: {}", json.to_string()),
65            });
66        }
67    };
68    Ok(ip_info)
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_legitimate_query() {
77        let response = match get_ip_info("192.168.1.1".to_string()) {
78            Ok(response) => response,
79            Err(err) => {
80                panic!("Failed to fetch IP information: {}", err.reason);
81            }
82        };
83
84        if !response.ip.eq("192.168.1.1") {
85            panic!("API returned a response with a different IP than we requested.");
86        }
87
88        if !response.city.eq("unknown") {
89            panic!("API returned a response with a city that is not unknown, \
90            which is not expected.");
91        }
92    }
93
94    #[test]
95    fn test_illegitimate_query() {
96        let response = match get_ip_info("notanip".to_string()) {
97            Ok(response) => {
98                panic!("API returned a successful response when it should have failed. Response: {response:?}");
99            },
100            Err(err) => {
101                err
102            }
103        };
104
105        if response.success {
106            panic!("API returned a successful response with a FailedResponse struct when it should have been success=false. Response: {response:?}");
107        }
108    }
109
110    #[cfg(feature = "async")]
111    #[test]
112    fn test_async_query() {
113        let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
114
115        let response = rt.block_on(apiasync::alib::get_ip_info_async("192.168.1.1".to_string())).unwrap();
116
117        if !response.ip.eq("192.168.1.1") {
118            panic!("API returned a response with a different IP than we requested.");
119        }
120
121        if !response.city.eq("unknown") {
122            panic!("API returned a response with a city that is not unknown, \
123            which is not expected.");
124        }
125    }
126}