diff --git a/Cargo.toml b/Cargo.toml index ecf8c12..74b1f78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ hyper = { version = "0.9" , default-features = false } log = "0.3" serde = "0.8" serde_json = "0.8" +serde_urlencoded = "0.3" url = "1.0" [dependencies.native-tls] diff --git a/src/client.rs b/src/client.rs index 33f4123..7573000 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,6 +9,7 @@ use hyper::{Url}; use serde::Serialize; use serde_json; +use serde_urlencoded; use ::body::{self, Body}; @@ -94,7 +95,7 @@ pub struct RequestBuilder<'a> { _version: HttpVersion, headers: Headers, - body: Option, + body: Option<::Result>, } impl<'a> RequestBuilder<'a> { @@ -122,7 +123,30 @@ impl<'a> RequestBuilder<'a> { /// Set the request body. pub fn body>(mut self, body: T) -> RequestBuilder<'a> { - self.body = Some(body.into()); + self.body = Some(Ok(body.into())); + self + } + + /// Send a form body. + /// + /// Sets the body to the url encoded serialization of the passed value, + /// and also sets the `Content-Type: application/www-form-url-encoded` + /// header. + /// + /// ```no_run + /// # use std::collections::HashMap; + /// let mut params = HashMap::new(); + /// params.insert("lang", "rust"); + /// + /// let client = reqwest::Client::new().unwrap(); + /// let res = client.post("http://httpbin.org") + /// .form(¶ms) + /// .send(); + /// ``` + pub fn form(mut self, form: &T) -> RequestBuilder<'a> { + let body = serde_urlencoded::to_string(form).map_err(::Error::from); + self.headers.set(ContentType::form_url_encoded()); + self.body = Some(body.map(|b| b.into())); self } @@ -136,14 +160,15 @@ impl<'a> RequestBuilder<'a> { /// let mut map = HashMap::new(); /// map.insert("lang", "rust"); /// - /// let res = reqwest::post("http://www.rust-lang.org") - /// .json(map) + /// let client = reqwest::Client::new().unwrap(); + /// let res = client.post("http://httpbin.org") + /// .json(&map) /// .send(); /// ``` - pub fn json(mut self, json: T) -> RequestBuilder<'a> { - let body = serde_json::to_vec(&json).expect("serde to_vec cannot fail"); + pub fn json(mut self, json: &T) -> RequestBuilder<'a> { + let body = serde_json::to_vec(json).expect("serde to_vec cannot fail"); self.headers.set(ContentType::json()); - self.body = Some(body.into()); + self.body = Some(Ok(body.into())); self } @@ -157,7 +182,10 @@ impl<'a> RequestBuilder<'a> { let mut method = self.method; let mut url = try!(self.url); let mut headers = self.headers; - let mut body = self.body; + let mut body = match self.body { + Some(b) => Some(try!(b)), + None => None, + }; let mut redirect_count = 0; diff --git a/src/error.rs b/src/error.rs index 00167b3..e78c13c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,11 @@ use std::fmt; pub enum Error { /// An HTTP error from the `hyper` crate. Http(::hyper::Error), + /// An error trying to serialize a value. + /// + /// This may be serializing a value that is illegal in JSON or + /// form-url-encoded bodies. + Serialize(Box), /// A request tried to redirect too many times. TooManyRedirects, #[doc(hidden)] @@ -16,6 +21,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Http(ref e) => fmt::Display::fmt(e, f), + Error::Serialize(ref e) => fmt::Display::fmt(e, f), Error::TooManyRedirects => { f.pad("Too many redirects") }, @@ -28,6 +34,7 @@ impl StdError for Error { fn description(&self) -> &str { match *self { Error::Http(ref e) => e.description(), + Error::Serialize(ref e) => e.description(), Error::TooManyRedirects => "Too many redirects", Error::__DontMatchMe => unreachable!() } @@ -36,6 +43,7 @@ impl StdError for Error { fn cause(&self) -> Option<&StdError> { match *self { Error::Http(ref e) => Some(e), + Error::Serialize(ref e) => Some(&**e), Error::TooManyRedirects => None, Error::__DontMatchMe => unreachable!() } @@ -54,7 +62,11 @@ impl From<::url::ParseError> for Error { } } - +impl From<::serde_urlencoded::ser::Error> for Error { + fn from(err: ::serde_urlencoded::ser::Error) -> Error { + Error::Serialize(Box::new(err)) + } +} /// A `Result` alias where the `Err` case is `reqwest::Error`. pub type Result = ::std::result::Result; diff --git a/src/lib.rs b/src/lib.rs index c83f3bc..1edab19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,9 +9,9 @@ //! to do for them. //! //! - Uses system-native TLS -//! - Plain bodies, JSON, urlencoded, multipart -//! - Customizable redirect policy -//! - Cookies +//! - Plain bodies, JSON, urlencoded, (TODO: multipart) +//! - (TODO: Customizable redirect policy) +//! - (TODO: Cookies) //! //! The `reqwest::Client` is synchronous, making it a great fit for //! applications that only require a few HTTP requests, and wish to handle @@ -32,12 +32,69 @@ //! //! If you plan to perform multiple requests, it is best to create a [`Client`][client] //! and reuse it, taking advantage of keep-alive connection pooling. +//! +//! ## Making POST requests (or setting request bodies) +//! +//! There are several ways you can set the body of a request. The basic one is +//! by using the `body()` method of a [`RequestBuilder`][builder]. This lets you set the +//! exact raw bytes of what the body should be. It accepts various types, +//! including `String`, `Vec`, and `File`. If you wish to pass a custom +//! Reader, you can use the `reqwest::Body::new()` constructor. +//! +//! ```no_run +//! let client = reqwest::Client::new().unwrap(); +//! let res = client.post("http://httpbin.org/post") +//! .body("the exact body that is sent") +//! .send(); +//! ``` +//! +//! ### Forms +//! +//! It's very common to want to send form data in a request body. This can be +//! done with any type that can be serialized into form data. +//! +//! This can be an array of tuples, or a `HashMap`, or a custom type that +//! implements [`Serialize`][serde]. +//! +//! ```no_run +//! // This will POST a body of `foo=bar&baz=quux` +//! let params = [("foo", "bar"), ("baz", "quux")]; +//! let client = reqwest::Client::new().unwrap(); +//! let res = client.post("http://httpbin.org/post") +//! .form(¶ms) +//! .send(); +//! ``` +//! +//! ### JSON +//! +//! There is also a `json` method helper on the [`RequestBuilder`][builder] that works in +//! a similar fashion the `form` method. It can take any value that can be +//! serialized into JSON. +//! +//! ```no_run +//! # use std::collections::HashMap; +//! // This will POST a body of `{"lang":"rust","body":"json"}` +//! let mut map = HashMap::new(); +//! map.insert("lang", "rust"); +//! map.insert("body", "json"); +//! +//! let client = reqwest::Client::new().unwrap(); +//! let res = client.post("http://httpbin.org/post") +//! .json(&map) +//! .send(); +//! ``` +//! +//! [hyper]: http://hyper.rs +//! [client]: ./struct.Client.html +//! [builder]: ./client/struct.RequestBuilder.html +//! [serde]: http://serde.rs extern crate hyper; #[macro_use] extern crate log; #[cfg(feature = "tls")] extern crate native_tls; extern crate serde; extern crate serde_json; +extern crate serde_urlencoded; extern crate url; pub use hyper::header;