api/
lib.rs

1#[cfg(feature = "use-hyper")]
2extern crate hyper;
3
4/// *api* is a library that abstracts a HTTP API
5/// and separates the client from the API definition.
6/// This allows you to change the underlying HTTP
7/// client easily.
8use std::io;
9use std::collections::BTreeMap;
10
11
12/// Type for the request/response headers.
13pub type Headers = BTreeMap<String, Vec<String>>;
14/// Type for the URL query.
15pub type Query<'s> = Vec<(String, String)>;
16
17/// Enum with all the standard HTTP methods. It also has
18/// a variant `Custom` to support non-standard methods.
19pub enum Method {
20    Get,
21    Head,
22    Post,
23    Put,
24    Delete,
25    Patch,
26    Options,
27    Trace,
28    Connect,
29    Custom(String),
30}
31
32impl ToString for Method {
33    /// Returns a string representing the HTTP method.
34    fn to_string(&self) -> String {
35        match *self {
36            Method::Get => "GET".to_string(),
37            Method::Head => "HEAD".to_string(),
38            Method::Post => "POST".to_string(),
39            Method::Put => "PUT".to_string(),
40            Method::Delete => "DELETE".to_string(),
41            Method::Patch => "PATCH".to_string(),
42            Method::Options => "OPTIONS".to_string(),
43            Method::Trace => "TRACE".to_string(),
44            Method::Connect => "CONNECT".to_string(),
45            Method::Custom(ref s) => s.clone(),
46        }
47    }
48}
49
50#[cfg(feature = "use-hyper")]
51impl From<hyper::method::Method> for Method {
52    fn from(m: hyper::method::Method) -> Method {
53        match m {
54            hyper::method::Method::Get => Method::Get,
55            hyper::method::Method::Head => Method::Head,
56            hyper::method::Method::Post => Method::Post,
57            hyper::method::Method::Put => Method::Put,
58            hyper::method::Method::Delete => Method::Delete,
59            hyper::method::Method::Patch => Method::Patch,
60            hyper::method::Method::Options => Method::Options,
61            hyper::method::Method::Trace => Method::Trace,
62            hyper::method::Method::Connect => Method::Connect,
63            hyper::method::Method::Extension(ref s) => Method::Custom(s.clone()),
64        }
65    }
66}
67
68#[cfg(feature = "use-hyper")]
69impl Into<hyper::method::Method> for Method {
70    fn into(self) -> hyper::method::Method {
71        match self {
72            Method::Get => hyper::method::Method::Get,
73            Method::Head => hyper::method::Method::Head,
74            Method::Post => hyper::method::Method::Post,
75            Method::Put => hyper::method::Method::Put,
76            Method::Delete => hyper::method::Method::Delete,
77            Method::Patch => hyper::method::Method::Patch,
78            Method::Options => hyper::method::Method::Options,
79            Method::Trace => hyper::method::Method::Trace,
80            Method::Connect => hyper::method::Method::Connect,
81            Method::Custom(s) => hyper::method::Method::Extension(s),
82        }
83    }
84}
85
86
87/// It represents the Server response received
88/// by the client after sending a HTTP request.
89pub trait HttpResponse {
90    type Body: io::Read;
91
92    /// Response's status code. It should be a integer
93    /// between 100 and 599.
94    fn status(&self) -> u16;
95
96    /// Reason-phrase that describes the status code.
97    /// i.e. 200 OK, 404 Not Found
98    fn reason(&self) -> &str;
99
100    /// Response's header. It contains metadata for the response.
101    /// e.g. `Content-Type` informs the client about the body MIME
102    /// and how to decode it.
103    fn headers(&self) -> Headers;
104
105    /// Response's body contain the data sent back from the server.
106    fn body(&mut self) -> &mut Self::Body;
107
108    /// Return `true` if the status code is 1xx, otherwise return `false`.
109    fn is_1xx(&self) -> bool {
110        self.status() / 100 == 1
111    }
112
113    /// Return `true` if the status code is 2xx, otherwise return `false`.
114    fn is_2xx(&self) -> bool {
115        self.status() / 100 == 2
116    }
117
118    /// Return `true` if the status code is 3xx, otherwise return `false`.
119    fn is_3xx(&self) -> bool {
120        self.status() / 100 == 3
121    }
122
123    /// Return `true` if the status code is 4xx, otherwise return `false`.
124    fn is_4xx(&self) -> bool {
125        self.status() / 100 == 4
126    }
127
128    /// Return `true` if the status code is 5xx, otherwise return `false`.
129    fn is_5xx(&self) -> bool {
130        self.status() / 100 == 5
131    }
132}
133
134
135#[cfg(feature = "use-hyper")]
136impl HttpResponse for hyper::client::Response {
137    type Body = hyper::client::Response;
138
139    fn status(&self) -> u16 {
140        self.status.to_u16()
141    }
142
143    fn reason(&self) -> &str {
144        self.status_raw().1.as_ref()
145    }
146
147    fn headers(&self) -> Headers {
148        Headers::new()
149    }
150
151    fn body(&mut self) -> &mut hyper::client::Response {
152        return self
153    }
154}
155
156/// `Api` represents a HTTP API exposing all the request parameters
157/// and a function to parse the HTTP response.
158pub trait Api {
159    type Reply;
160    type Body: io::Read;
161    type Error;
162
163    /// Return the HTTP method used by this API.
164    fn method(&self) -> Method;
165
166    /// Return the URL path for this API request.
167    fn path(&self) -> String;
168
169    /// Return the URL query for this API request.
170    fn query(&self) -> Query;
171
172    /// Return the headers for this HTTP request.
173    fn headers(&self) -> Headers;
174
175    /// Return the body of this HTTP request. If the request
176    /// doesn't expect any body (i.e. GET), it should return
177    /// `std::io::Empty`.
178    fn body(&self) -> Self::Body;
179
180    /// Parse the HTTP response, received from the actual client,
181    /// into the type `Reply`.
182    fn parse<Resp>(&self, &mut Resp) -> Result<Self::Reply, Self::Error> where Resp: HttpResponse;
183}
184
185
186#[derive(Debug)]
187pub enum SendError<S, A> {
188    Client(S),
189    Api(A)
190}
191
192pub trait Client<A: Api, E> {
193    fn send(&mut self, url: &str, req: A) -> Result<A::Reply, SendError<E, A::Error>>;
194}
195
196
197#[cfg(feature = "use-hyper")]
198impl<A: Api> Client<A, hyper::Error> for hyper::Client {
199    /// Send an HTTP request for the given API using an `hyper` client.
200    /// The path will be added do `url` that is supposed to be the base URL
201    /// for the API.
202    fn send(&mut self, url: &str, req: A)
203        -> Result<A::Reply, SendError<hyper::Error, A::Error>>
204    {
205        let mut url = hyper::Url::parse(url)
206            .map_err(|e| SendError::Client(hyper::Error::Uri(e)))?
207            .join(req.path().as_ref())
208            .map_err(|e| SendError::Client(hyper::Error::Uri(e)))?;
209        let mut body = req.body();
210        let body = hyper::client::Body::ChunkedBody(&mut body);
211
212        {
213            let mut query = url.query_pairs_mut();
214            for (name, value) in req.query() {
215                query.append_pair(name.as_str(), value.as_str());
216            }
217        }
218
219        let mut headers = hyper::header::Headers::new();
220        for (name, value) in req.headers() {
221            headers.set_raw(
222                name,
223                value.iter().map(|v| v.clone().into_bytes()).collect()
224            );
225        }
226
227        let mut resp = self.request(req.method().into(), url)
228            .headers(headers)
229            .body(body)
230            .send()
231            .map_err(|e| SendError::Client(e))?;
232
233        req.parse(&mut resp)
234            .map_err(|e| SendError::Api(e))
235    }
236}
237