ipify_client/
ipify.rs

1use std::convert::From;
2use std::fmt;
3use std::str::FromStr;
4
5use futures;
6use hyper::{self, Client};
7use hyper::rt::{self, Future, Stream};
8
9use serde_json;
10
11/// Representation of an IPV4 address.
12#[derive(PartialEq, Debug)]
13pub struct IP(u8, u8, u8, u8);
14
15impl FromStr for IP {
16    type Err = IPParseError;
17
18    fn from_str(s: &str) -> Result<Self, Self::Err> {
19        let mut octets = Vec::new();
20        for octet_str in s.split('.') {
21            match u8::from_str(octet_str) {
22                Ok(octet) => octets.push(octet),
23                Err(_) => return Err(IPParseError::OctetOutOfRange),
24            }
25        }
26
27        if octets.len() != 4 {
28            return Err(IPParseError::WrongNumOctets);
29        }
30
31        Ok(IP(octets[0], octets[1], octets[2], octets[3]))
32    }
33}
34
35impl fmt::Display for IP {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        write!(f, "{}.{}.{}.{}", self.0, self.1, self.2, self.3)
38    }
39}
40
41#[derive(Debug, PartialEq)]
42pub enum IPParseError {
43    OctetOutOfRange,
44    WrongNumOctets,
45}
46
47/// Error states which can occur when resolving an IP.
48#[derive(Debug)]
49pub enum IpifyError {
50    /// Indicates an error in the HTTP request to ipify.org
51    RequestError,
52    /// Indicates an error in parsing the response from ipify.org
53    ParseError,
54}
55
56impl From<hyper::Error> for IpifyError {
57    fn from(_: hyper::Error) -> IpifyError {
58        IpifyError::RequestError
59    }
60}
61
62impl From<IPParseError> for IpifyError {
63    fn from(_: IPParseError) -> IpifyError {
64        IpifyError::ParseError
65    }
66}
67
68impl From<::std::str::Utf8Error> for IpifyError {
69    fn from(_: ::std::str::Utf8Error) -> IpifyError {
70        IpifyError::ParseError
71    }
72}
73
74#[derive(Serialize, Deserialize, Debug, PartialEq)]
75struct IpifyResponse {
76    origin: String,
77}
78
79impl IpifyResponse {
80    fn from_str(json: &str) -> Self {
81        serde_json::from_str(json).unwrap()
82    }
83}
84
85/// Resolves the IP address of the local machine by calling out to ipify.org.
86///
87/// Returns a future which resolves to this machine's IP address.
88pub fn get_ip() -> impl Future<Item=IP, Error=IpifyError> {
89    let uri = "http://httpbin.org/ip".parse().unwrap();
90    let client = Client::new();
91    client
92        .get(uri)
93        .map_err(|e|{ e.into() })
94        .and_then(|res| {
95            res
96                .into_body()
97                .concat2()
98                .map_err(|e|{ e.into() })
99        })
100        .and_then(|body_bytes| {
101            ::std::str::from_utf8(&body_bytes)
102                .map(|bytes| { bytes.to_owned() })
103                .map_err(|e| { e.into() })
104        })
105        .and_then(|body| {
106            let response = IpifyResponse::from_str(&body);
107            IP::from_str(&response.origin)
108                .map_err(|e|{ e.into() })
109        })
110}
111
112
113#[cfg(test)]
114mod test {
115    use super::*;
116
117    #[test]
118    fn test_deserialize() {
119        let actual = IpifyResponse::from_str("{\"origin\": \"fartturdbutt\"}");
120        let expected = IpifyResponse{ origin: String::from("fartturdbutt") };
121        assert!(actual == expected);
122    }
123
124    #[test]
125    fn test_parse_ip() {
126        let expected = Ok(IP(127, 0, 0, 1));
127        let actual = IP::from_str("127.0.0.1");
128
129        assert!(expected == actual);
130    }
131
132    #[test]
133    fn test_fail_parse_ip() {
134        let expected = Err(IPParseError::OctetOutOfRange);
135        let actual = IP::from_str("256.0.0.1");
136        assert!(expected == actual);
137
138        let expected = Err(IPParseError::WrongNumOctets);
139        let actual = IP::from_str("127.0.0.1.1");
140        assert!(expected == actual);
141
142        let expected = Err(IPParseError::OctetOutOfRange);
143        let actual = IP::from_str("256.0.0.1.1");
144        assert!(expected == actual);
145    }
146
147    #[test]
148    fn test_ipify() {
149        let f = futures::future::ok::<(), ()>(())
150            .map(|_| {
151                let future = get_ip();
152                future.wait().expect("failed to successfully resolve future");
153            });
154
155        rt::run(f);
156    }
157}