From cd4a1722fa085a174ea0ba903e59f7780909af9b Mon Sep 17 00:00:00 2001 From: Martin Algesten Date: Fri, 22 Jun 2018 14:03:25 +0200 Subject: [PATCH] reduce dependency tree by features --- Cargo.toml | 12 ++++++++--- README.md | 16 +++++++++++++++ src/agent.rs | 9 +++++---- src/lib.rs | 29 ++++++++++++++++++++++----- src/request.rs | 43 +++++++++++++++++++++++++++++----------- src/response.rs | 46 ++++++++++++++++++++++++++++++++----------- src/stream.rs | 24 +++++++++++++++------- src/test/body_send.rs | 2 ++ src/test/simple.rs | 1 + 9 files changed, 140 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3f1478c..168a974 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,15 +9,21 @@ readme = "README.md" keywords = ["web", "request", "http", "rest", "client"] categories = ["web-programming::http-client"] +[features] +default = ["tls"] +json = ["serde_json"] +charset = ["encoding"] +tls = ["native-tls"] + [dependencies] ascii = "0.9" base64 = "0.9" chunked_transfer = "0.3" cookie = { version = "0.10", features = ["percent-encode"] } dns-lookup = "0.9.1" -encoding = "0.2" lazy_static = "1" qstring = "0.6" -native-tls = "0.1" -serde_json = "1" url = "1.6" +native-tls = { version = "0.1", optional = true } +serde_json = { version = "1", optional = true } +encoding = { version = "0.2", optional = true } diff --git a/README.md b/README.md index c3470f8..571e3e1 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ ## Usage ```rust +// requires feature: `ureq = { version = "*", features = ["json"] }` #[macro_use] extern crate ureq; @@ -25,6 +26,21 @@ fn main() { } ``` +## Features + +To enable a minimal dependency tree, some features are off by default. +You can control them when including `ureq` as a dependency. + +``` + ureq = { version = "*", features = ["json", "charset"] } +``` + +* `tls` enables https. This is enabled by default. +* `json` enables `response.into_json()` and `request.send_json()` serde json. +* `charset` enables interpreting the charset part of + `Content-Type: text/plain; charset=iso-8859-1`. Without this, the library + defaults to rust's built in `utf-8`. + ## Motivation * Minimal dependency tree diff --git a/src/agent.rs b/src/agent.rs index fd45bcf..a3594ae 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -89,7 +89,7 @@ impl Agent { /// ``` /// let agent = ureq::agent() /// .set("X-API-Key", "foobar") - /// .set("Accept", "application/json") + /// .set("Accept", "text/plain") /// .build(); /// /// let r = agent @@ -97,7 +97,7 @@ impl Agent { /// .call(); /// /// if r.ok() { - /// println!("yay got {}", r.into_json().unwrap()); + /// println!("yay got {}", r.into_string().unwrap()); /// } else { /// println!("Oh no error!"); /// } @@ -123,7 +123,7 @@ impl Agent { /// let agent = ureq::agent() /// .set_map(map! { /// "X-API-Key" => "foobar", - /// "Accept" => "application/json" + /// "Accept" => "text/plain" /// }) /// .build(); /// @@ -132,7 +132,7 @@ impl Agent { /// .call(); /// /// if r.ok() { - /// println!("yay got {}", r.into_json().unwrap()); + /// println!("yay got {}", r.into_string().unwrap()); /// } /// } /// ``` @@ -402,6 +402,7 @@ mod tests { } #[test] + #[cfg(feature = "json")] fn parse_simple_json() { let s = format!("HTTP/1.1 200 OK\r\n\r\n{{\"hello\":\"world\"}}"); let resp = s.parse::().unwrap(); diff --git a/src/lib.rs b/src/lib.rs index e865c95..c8d3151 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ //! * Obvious API //! //! ``` +//! // requires feature: `ureq = { version = "*", features = ["json"] }` //! #[macro_use] //! extern crate ureq; //! @@ -37,7 +38,14 @@ //! * [`.call()`](struct.Request.html#method.call) without a request body. //! * [`.send()`](struct.Request.html#method.send) with a request body as `Read`. //! * [`.send_string()`](struct.Request.html#method.send_string) body as string. -//! * [`.send_json()`](struct.Request.html#method.send_json) body as serde json. +//! +//! # JSON +//! +//! By enabling the `ureq = { version = "*", features = ["json"] }` feature, +//! the library supports serde json. +//! +//! * [`request.send_json()`](struct.Request.html#method.send_json) send body as serde json. +//! * [`response.into_json()`](struct.Response.html#method.into_json) transform response to json. //! //! # Agents //! @@ -70,12 +78,15 @@ //! //! # Character encoding //! +//! By enabling the `ureq = { version = "*", features = ["charset"] }` feature, +//! the library supports sending/receiving other character sets than `utf-8`. +//! //! For [`response.into_string()`](struct.Response.html#method.into_string) we read the //! header `Content-Type: text/plain; charset=iso-8859-1` and if it contains a charset //! specification, we try to decode the body using that encoding. In the absence of, or failing //! to interpret the charset, we fall back on `utf-8`. //! -//! Similarly when using [`.send_string()`](struct.Request.html#method.send_string), to +//! Similarly when using [`request.send_string()`](struct.Request.html#method.send_string), //! we first check if the user has set a `; charset=` and attempt //! to encode the request body using that. //! @@ -84,18 +95,24 @@ extern crate base64; extern crate chunked_transfer; extern crate cookie; extern crate dns_lookup; -extern crate encoding; #[macro_use] extern crate lazy_static; -extern crate native_tls; extern crate qstring; -extern crate serde_json; extern crate url; +#[cfg(feature = "charset")] +extern crate encoding; +#[cfg(feature = "tls")] +extern crate native_tls; +#[cfg(feature = "json")] +extern crate serde_json; + mod agent; mod error; mod header; mod macros; + +#[cfg(feature = "json")] mod serde_macros; #[cfg(test)] @@ -107,6 +124,7 @@ pub use header::Header; // re-export pub use cookie::Cookie; +#[cfg(feature = "json")] pub use serde_json::{to_value as serde_to_value, Map as SerdeMap, Value as SerdeValue}; /// Agents are used to keep state between requests. @@ -214,6 +232,7 @@ mod tests { } #[test] + #[cfg(feature = "tls")] fn connect_https_google() { let resp = get("https://www.google.com/").call(); assert_eq!( diff --git a/src/request.rs b/src/request.rs index 0b0fe4e..a32817f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,10 +1,13 @@ -use super::SerdeValue; use qstring::QString; -use serde_json; use std::io::empty; use std::io::Cursor; use std::sync::Arc; +#[cfg(feature = "json")] +use super::SerdeValue; +#[cfg(feature = "json")] +use serde_json; + lazy_static! { static ref URL_BASE: Url = { Url::parse("http://localhost/").expect("Failed to parse URL_BASE") }; } @@ -48,7 +51,7 @@ impl ::std::fmt::Debug for Request { enum Payload { Empty, Text(String, String), - JSON(SerdeValue), + #[cfg(feature = "json")] JSON(SerdeValue), Reader(Box), } @@ -73,15 +76,21 @@ impl Payload { fn into_read(self) -> SizedReader { match self { Payload::Empty => SizedReader::new(None, Box::new(empty())), - Payload::Text(text, charset) => { - let encoding = encoding_from_whatwg_label(&charset) - .or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET)) - .unwrap(); - let bytes = encoding.encode(&text, EncoderTrap::Replace).unwrap(); + Payload::Text(text, _charset) => { + #[cfg(feature = "charset")] + let bytes = { + let encoding = encoding_from_whatwg_label(&_charset) + .or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET)) + .unwrap(); + encoding.encode(&text, EncoderTrap::Replace).unwrap() + }; + #[cfg(not(feature = "charset"))] + let bytes = text.into_bytes(); let len = bytes.len(); let cursor = Cursor::new(bytes); SizedReader::new(Some(len), Box::new(cursor)) } + #[cfg(feature = "json")] Payload::JSON(v) => { let bytes = serde_json::to_vec(&v).expect("Bad JSON in payload"); let len = bytes.len(); @@ -166,6 +175,8 @@ impl Request { /// Send data a json value. /// + /// Requires feature `ureq = { version = "*", features = ["json"] }` + /// /// The `Content-Length` header is implicitly set to the length of the serialized value. /// /// ``` @@ -178,6 +189,7 @@ impl Request { /// println!("{:?}", r); /// } /// ``` + #[cfg(feature = "json")] pub fn send_json(&mut self, data: SerdeValue) -> Response { self.do_call(Payload::JSON(data)) } @@ -185,12 +197,19 @@ impl Request { /// Send data as a string. /// /// The `Content-Length` header is implicitly set to the length of the serialized value. + /// Defaults to `utf-8` + /// + /// ## Charset support + /// + /// Requires feature `ureq = { version = "*", features = ["charset"] }` /// /// If a `Content-Type` header is present and it contains a charset specification, we /// attempt to encode the string using that character set. If it fails, we fall back /// on utf-8. /// /// ``` + /// // this example requires features = ["charset"] + /// /// let r = ureq::post("/my_page") /// .set("Content-Type", "text/plain; charset=iso-8859-1") /// .send_string("Hällo Wörld!"); @@ -231,11 +250,11 @@ impl Request { /// ``` /// let r = ureq::get("/my_page") /// .set("X-API-Key", "foobar") - /// .set("Accept", "application/json") + /// .set("Accept", "text/plain") /// .call(); /// /// if r.ok() { - /// println!("yay got {}", r.into_json().unwrap()); + /// println!("yay got {}", r.into_string().unwrap()); /// } else { /// println!("Oh no error!"); /// } @@ -308,12 +327,12 @@ impl Request { /// let r = ureq::get("/my_page") /// .set_map(map! { /// "X-API-Key" => "foobar", - /// "Accept" => "application/json" + /// "Accept" => "text/plain" /// }) /// .call(); /// /// if r.ok() { - /// println!("yay got {}", r.into_json().unwrap()); + /// println!("yay got {}", r.into_string().unwrap()); /// } /// } /// ``` diff --git a/src/response.rs b/src/response.rs index 83937c5..452160a 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,12 +1,15 @@ use ascii::AsciiString; use chunked_transfer; -use encoding::label::encoding_from_whatwg_label; -use encoding::{DecoderTrap, EncoderTrap}; use std::io::Error as IoError; use std::io::ErrorKind; use std::io::Read; use std::io::Result as IoResult; +#[cfg(feature = "charset")] +use encoding::label::encoding_from_whatwg_label; +#[cfg(feature = "charset")] +use encoding::{DecoderTrap, EncoderTrap}; + use error::Error; const DEFAULT_CONTENT_TYPE: &'static str = "text/plain"; @@ -254,8 +257,8 @@ impl Response { } } - /// Turn this response into a String of the response body. Attempts to respect the - /// character encoding of the `Content-Type` header and falls back to `utf-8`. + /// Turn this response into a String of the response body. By default uses `utf-8`, + /// but can work with charset, see below. /// /// This is potentially memory inefficient for large bodies since the /// implementation first reads the reader to end into a `Vec` and then @@ -272,17 +275,38 @@ impl Response { /// /// assert!(text.contains("hello")); /// ``` + /// + /// ## Charset support + /// + /// Requires feature `ureq = { version = "*", features = ["charset"] }` + /// + /// Attempts to respect the character encoding of the `Content-Type` header and + /// falls back to `utf-8`. + /// + /// I.e. `Content-Length: text/plain; charset=iso-8859-1` would be decoded in latin-1. + /// pub fn into_string(self) -> IoResult { - let encoding = encoding_from_whatwg_label(self.charset()) - .or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET)) - .unwrap(); - let mut buf: Vec = vec![]; - self.into_reader().read_to_end(&mut buf)?; - Ok(encoding.decode(&buf, DecoderTrap::Replace).unwrap()) + #[cfg(feature = "charset")] + { + let encoding = encoding_from_whatwg_label(self.charset()) + .or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET)) + .unwrap(); + let mut buf: Vec = vec![]; + self.into_reader().read_to_end(&mut buf)?; + Ok(encoding.decode(&buf, DecoderTrap::Replace).unwrap()) + } + #[cfg(not(feature = "charset"))] + { + let mut buf: Vec = vec![]; + self.into_reader().read_to_end(&mut buf)?; + Ok(String::from_utf8_lossy(&buf).to_string()) + } } /// Turn this response into a (serde) JSON value of the response body. /// + /// Requires feature `ureq = { version = "*", features = ["json"] }` + /// /// Example: /// /// ``` @@ -294,6 +318,7 @@ impl Response { /// /// assert_eq!(json["hello"], "world"); /// ``` + #[cfg(feature = "json")] pub fn into_json(self) -> IoResult { let reader = self.into_reader(); serde_json::from_reader(reader).map_err(|e| { @@ -480,4 +505,3 @@ fn charset_from_content_type(header: Option<&str>) -> &str { }) .unwrap_or(DEFAULT_CHARACTER_SET) } - diff --git a/src/stream.rs b/src/stream.rs index 551906e..436b397 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,13 +1,17 @@ use dns_lookup; -use native_tls::TlsConnector; -use native_tls::TlsStream; use std::net::IpAddr; use std::net::SocketAddr; use std::net::TcpStream; use std::time::Duration; +#[cfg(feature = "tls")] +use native_tls::TlsConnector; +#[cfg(feature = "tls")] +use native_tls::TlsStream; + pub enum Stream { Http(TcpStream), + #[cfg(feature = "tls")] Https(TlsStream), Cursor(Cursor>), #[cfg(test)] @@ -65,6 +69,7 @@ fn connect_http(request: &Request, url: &Url) -> Result { connect_host(request, hostname, port).map(|tcp| Stream::Http(tcp)) } +#[cfg(feature = "tls")] fn connect_https(request: &Request, url: &Url) -> Result { // let hostname = url.host_str().unwrap(); @@ -110,13 +115,18 @@ fn connect_host(request: &Request, hostname: &str, port: u16) -> Result Result { - Err(Error::UnknownScheme(url.scheme().to_string())) -} - #[cfg(test)] fn connect_test(request: &Request, url: &Url) -> Result { use test; test::resolve_handler(request, url) } + +#[cfg(not(test))] +fn connect_test(_request: &Request, url: &Url) -> Result { + Err(Error::UnknownScheme(url.scheme().to_string())) +} + +#[cfg(not(feature = "tls"))] +fn connect_https(request: &Request, url: &Url) -> Result { + Err(Error::UnknownScheme(url.scheme().to_string())) +} diff --git a/src/test/body_send.rs b/src/test/body_send.rs index 64770a4..bbc7426 100644 --- a/src/test/body_send.rs +++ b/src/test/body_send.rs @@ -27,6 +27,7 @@ fn user_set_content_length_on_str() { } #[test] +#[cfg(feature = "json")] fn content_length_on_json() { test::set_handler("/content_length_on_json", |_req, _url| { test::make_response(200, "OK", vec![], vec![]) @@ -57,6 +58,7 @@ fn content_length_and_chunked() { } #[test] +#[cfg(feature = "charset")] fn str_with_encoding() { test::set_handler("/str_with_encoding", |_req, _url| { test::make_response(200, "OK", vec![], vec![]) diff --git a/src/test/simple.rs b/src/test/simple.rs index 9151343..e55de81 100644 --- a/src/test/simple.rs +++ b/src/test/simple.rs @@ -59,6 +59,7 @@ fn body_as_text() { } #[test] +#[cfg(feature = "json")] fn body_as_json() { test::set_handler("/body_as_json", |_req, _url| { test::make_response(