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 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}