1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! json_request exports a single function, [`request`](fn.request.html) for making JSON calls to an HTTP server.
//!
//! This crate relies on [hyper][] for proving an HTTP client, and [rustc_serialize][] for automatic
//! encoding/decoding of JSON requests/responses.
//!
//! [hyper]: http://hyper.rs/hyper/hyper/index.html
//! [rustc_serialize]: https://doc.rust-lang.org/rustc-serialize/rustc_serialize/index.html
extern crate hyper;
extern crate rustc_serialize;

use rustc_serialize::{json, Encodable, Decodable};

pub use hyper::method::Method;
use hyper::Client;
use hyper::header;
use hyper::status::StatusClass;

use std::io::{Read, self};
use std::error::Error as StdError;

// ---------------------------------- ERROR HANDLING STUFF -----------------------------------------
/// Error wrapper
#[derive(Debug)]
pub enum Error {
    /// Error in HTTP library
    HttpClient(hyper::Error),
    /// Error encoding JSON object for request
    JsonEncoder(json::EncoderError),
    /// Error decoding JSON object from response
    JsonDecoder(json::DecoderError),
    /// Error reading body from hyper response
    IoError(io::Error),
}

impl From<hyper::Error> for Error {
    fn from(err: hyper::Error) -> Error {
        Error::HttpClient(err)
    }
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::IoError(err)
    }
}

impl From<json::EncoderError> for Error {
    fn from(err: json::EncoderError) -> Error {
        Error::JsonEncoder(err)
    }
}

impl From<json::DecoderError> for Error {
    fn from(err: json::DecoderError) -> Error {
        Error::JsonDecoder(err)
    }
}

pub type Result<T> = std::result::Result<T, Error>;

impl ::std::error::Error for Error {
    fn cause(&self) -> Option<&::std::error::Error> {
        match *self {
            Error::HttpClient(ref err) => Some(err),
            Error::JsonDecoder(ref err) => Some(err),
            Error::JsonEncoder(ref err) => Some(err),
            Error::IoError(ref err) => Some(err),
        }
    }

    fn description(&self) -> &str {
        match *self {
            Error::HttpClient(ref err) => err.description(),
            Error::JsonDecoder(ref err) => err.description(),
            Error::JsonEncoder(ref err) => err.description(),
            Error::IoError(ref err) => err.description(),
        }
    }
}

impl ::std::fmt::Display for Error {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        f.write_str(self.description())
    }
}
// -------------------------------- END ERROR HANDLING STUFF ---------------------------------------

/// Make an HTTP request
///
/// The third parameter of request, data, will be automatically serialized as JSON and set as the
/// request body before making the request. The wrapped result will be decoded from any 2xx JSON
/// response. For the automatic encoding to work, the `data` type must implement
/// `rustc_serialize::Encodable`, and the result type must implement `rustc_serialize::Decodable`.
/// This can usually be achieved with `#[derive(RustcEncodable)]` and `#[derive(RustcDecodable)]`.
///
/// # Example
///
/// ```no_run
/// extern crate rustc_serialize;
/// extern crate json_request;
///
/// use json_request::{request, Method};
///
/// #[derive(Debug, RustcEncodable)]
/// struct RequestData {
///     ping: bool
/// }
///
/// #[derive(Debug, RustcDecodable)]
/// struct ResponseData {
///     pong: bool
/// }
///
/// # fn main() {
/// let data = RequestData { ping: true };
/// let res = request(Method::Post, "http://example.com/ping", Some(data));
/// // Request returns a Result<Option<D>>; hence, two unwrap calls
/// let pong: ResponseData = res.unwrap().unwrap();
/// # }
/// ```
///
/// Alternatively, if you don't want to specify the binding type, pass the type parameter to
/// `request`.
///
/// ```no_run
/// # extern crate rustc_serialize;
/// # extern crate json_request;
/// # use json_request::{request, Method};
/// # #[derive(Debug, RustcEncodable)]
/// # struct RequestData {
/// #     ping: bool
/// # }
/// # #[derive(Debug, RustcDecodable)]
/// # struct ResponseData {
/// #     pong: bool
/// # }
/// # fn main() {
/// # let data = RequestData { ping: true };
/// let res = request::<_, ResponseData>(Method::Post, "http://example.com/ping", Some(data));
/// let pong = res.unwrap().unwrap();
/// # }
/// ```
pub fn request<S, D>(method: Method, url: &str, data: Option<S>) -> Result<Option<D>>
where S: Encodable, D: Decodable {
    let mut body = String::new();

    let client = Client::new();
    println!("url: {}", url);

    let mut res = match data {
        Some(inner) => {
            let payload = try!(json::encode(&inner));
            let builder = client.request(method, url)
                                .header(header::Connection::close())
                                .body(&payload[..]);

            try!(builder.send())
        },
        None => {
            let builder = client.request(method, url)
                                .header(header::Connection::close());

            try!(builder.send())
        }
    };

    Ok(match res.status.class() {
        StatusClass::Success => {
            try!(res.read_to_string(&mut body));
            Some(try!(json::decode::<D>(&body[..])))
        },
        _ => None
    })
}