diff --git a/src/agent.rs b/src/agent.rs index 7c8b900..a2225db 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -44,25 +44,22 @@ pub(crate) struct AgentConfig { /// can keep a state. /// /// ``` +/// # fn main() -> Result<(), ureq::Error> { +/// # ureq::is_test(true); /// let mut agent = ureq::agent(); /// -/// let auth = agent -/// .post("/login") -/// .call(); // blocks. -/// -/// if auth.is_err() { -/// println!("Noes!"); -/// } +/// agent +/// .post("http://example.com/login") +/// .call()?; /// /// let secret = agent -/// .get("/my-protected-page") -/// .call(); // blocks and waits for request. +/// .get("http://example.com/my-protected-page") +/// .call()? +/// .into_string()?; /// -/// if secret.is_err() { -/// println!("Wot?!"); -/// } else { -/// println!("Secret is: {}", secret.unwrap().into_string().unwrap()); -/// } +/// println!("Secret is: {}", secret); +/// # Ok(()) +/// # } /// ``` /// /// Agent uses an inner Arc, so cloning an Agent results in an instance @@ -99,12 +96,15 @@ impl Agent { /// Request by providing the HTTP verb such as `GET`, `POST`... /// /// ``` + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); /// let agent = ureq::agent(); /// - /// let r = agent - /// .request("GET", "/my_page") - /// .call(); - /// println!("{:?}", r); + /// let resp = agent + /// .request("GET", "http://httpbin.org/status/200") + /// .call()?; + /// # Ok(()) + /// # } /// ``` pub fn request(&self, method: &str, path: &str) -> Request { Request::new(self.clone(), method.into(), path.into()) @@ -143,15 +143,18 @@ impl Agent { /// use std::io::Write; /// use std::fs::File; /// - /// let mut file = File::create("cookies.json").unwrap(); - /// + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); /// let agent = ureq::agent(); /// /// // Cookies set by www.google.com are stored in agent. - /// agent.get("https://www.google.com/").call().unwrap(); + /// agent.get("https://www.google.com/").call()?; /// /// // Saves (persistent) cookies + /// let mut file = File::create("cookies.json")?; /// agent.cookie_store().save_json(&mut file).unwrap(); + /// # Ok(()) + /// # } /// ``` #[cfg(feature = "cookies")] pub fn cookie_store(&self) -> CookieStoreGuard<'_> { @@ -209,10 +212,14 @@ impl AgentBuilder { /// /// Example: /// ``` - /// let proxy = ureq::Proxy::new("user:password@cool.proxy:9090").unwrap(); + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let proxy = ureq::Proxy::new("user:password@cool.proxy:9090")?; /// let agent = ureq::AgentBuilder::new() /// .proxy(proxy) /// .build(); + /// # Ok(()) + /// # } /// ``` pub fn proxy(mut self, proxy: Proxy) -> Self { self.config.proxy = Some(proxy); @@ -224,7 +231,9 @@ impl AgentBuilder { /// connection pooling. /// /// ``` - /// let agent = ureq::AgentBuilder::new().max_idle_connections(200).build(); + /// let agent = ureq::AgentBuilder::new() + /// .max_idle_connections(200) + /// .build(); /// ``` pub fn max_idle_connections(mut self, max: usize) -> Self { self.max_idle_connections = max; @@ -236,7 +245,9 @@ impl AgentBuilder { /// would disable connection pooling. /// /// ``` - /// let agent = ureq::AgentBuilder::new().max_idle_connections_per_host(200).build(); + /// let agent = ureq::AgentBuilder::new() + /// .max_idle_connections_per_host(200) + /// .build(); /// ``` pub fn max_idle_connections_per_host(mut self, max: usize) -> Self { self.max_idle_connections_per_host = max; @@ -274,10 +285,15 @@ impl AgentBuilder { /// The default is 30 seconds. /// /// ``` + /// use std::time::Duration; + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); /// let agent = ureq::builder() - /// .timeout_connect(std::time::Duration::from_secs(1)) + /// .timeout_connect(Duration::from_secs(1)) /// .build(); - /// let r = agent.get("/my_page").call(); + /// let result = agent.get("http://httpbin.org/delay/20").call(); + /// # Ok(()) + /// # } /// ``` pub fn timeout_connect(mut self, timeout: Duration) -> Self { self.config.timeout_connect = Some(timeout); @@ -288,13 +304,18 @@ impl AgentBuilder { /// If both this and `.timeout()` are both set, `.timeout()` /// takes precedence. /// - /// The default is `0`, which means it can block forever. + /// The default is no timeout. In other words, requests may block forever on reads by default. /// /// ``` + /// use std::time::Duration; + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); /// let agent = ureq::builder() - /// .timeout_read(std::time::Duration::from_secs(1)) + /// .timeout_read(Duration::from_secs(1)) /// .build(); - /// let r = agent.get("/my_page").call(); + /// let result = agent.get("http://httpbin.org/delay/20").call(); + /// # Ok(()) + /// # } /// ``` pub fn timeout_read(mut self, timeout: Duration) -> Self { self.config.timeout_read = Some(timeout); @@ -305,13 +326,18 @@ impl AgentBuilder { /// If both this and `.timeout()` are both set, `.timeout()` /// takes precedence. /// - /// The default is `0`, which means it can block forever. + /// The default is no timeout. In other words, requests may block forever on writes by default. /// /// ``` + /// use std::time::Duration; + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); /// let agent = ureq::builder() - /// .timeout_write(std::time::Duration::from_secs(1)) + /// .timeout_read(Duration::from_secs(1)) /// .build(); - /// let r = agent.get("/my_page").call(); + /// let result = agent.get("http://httpbin.org/delay/20").call(); + /// # Ok(()) + /// # } /// ``` pub fn timeout_write(mut self, timeout: Duration) -> Self { self.config.timeout_write = Some(timeout); @@ -327,11 +353,15 @@ impl AgentBuilder { /// not `.timeout_connect()`. /// /// ``` + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); /// // wait max 1 second for whole request to complete. /// let agent = ureq::builder() /// .timeout(std::time::Duration::from_secs(1)) /// .build(); - /// let r = agent.get("/my_page").call(); + /// let result = agent.get("http://httpbin.org/delay/20").call(); + /// # Ok(()) + /// # } /// ``` pub fn timeout(mut self, timeout: Duration) -> Self { self.config.timeout = Some(timeout); @@ -346,12 +376,15 @@ impl AgentBuilder { /// If the redirect count hits this limit (and it's > 0), TooManyRedirects is returned. /// /// ``` - /// let r = ureq::builder() - /// .redirects(10) + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let result = ureq::builder() + /// .redirects(1) /// .build() - /// .get("/my_page") + /// .get("http://httpbin.org/redirect/3") /// .call(); - /// println!("{:?}", r); + /// # Ok(()) + /// # } /// ``` pub fn redirects(mut self, n: u32) -> Self { self.config.redirects = n; @@ -362,11 +395,15 @@ impl AgentBuilder { /// /// Example: /// ``` - /// let tls_config = std::sync::Arc::new(rustls::ClientConfig::new()); + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// use std::sync::Arc; + /// let tls_config = Arc::new(rustls::ClientConfig::new()); /// let agent = ureq::builder() /// .tls_config(tls_config.clone()) /// .build(); - /// let req = agent.post("https://cool.server"); + /// # Ok(()) + /// # } /// ``` #[cfg(feature = "tls")] pub fn tls_config(mut self, tls_config: Arc) -> Self { @@ -382,11 +419,12 @@ impl AgentBuilder { /// /// Example /// ```no_run + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); /// use cookie_store::CookieStore; /// use std::fs::File; /// use std::io::BufReader; - /// - /// let file = File::open("cookies.json").unwrap(); + /// let file = File::open("cookies.json")?; /// let read = BufReader::new(file); /// /// // Read persisted cookies from cookies.json @@ -396,7 +434,8 @@ impl AgentBuilder { /// let agent = ureq::builder() /// .cookie_store(my_store) /// .build(); - ///; + /// # Ok(()) + /// # } /// ``` #[cfg(feature = "cookies")] pub fn cookie_store(mut self, cookie_store: CookieStore) -> Self { diff --git a/src/request.rs b/src/request.rs index 4bb2015..260b420 100644 --- a/src/request.rs +++ b/src/request.rs @@ -18,11 +18,13 @@ pub type Result = std::result::Result; /// Request instances are builders that creates a request. /// /// ``` -/// let mut request = ureq::get("https://www.google.com/"); -/// -/// let response = request -/// .query("foo", "bar baz") // add ?foo=bar%20baz -/// .call(); // run the request +/// # fn main() -> Result<(), ureq::Error> { +/// # ureq::is_test(true); +/// let response = ureq::get("http://example.com/form") +/// .query("foo", "bar baz") // add ?foo=bar+baz +/// .call()?; // run the request +/// # Ok(()) +/// # } /// ``` #[derive(Clone)] pub struct Request { @@ -61,13 +63,16 @@ impl Request { /// Use `.timeout_connect()` and `.timeout_read()` to avoid blocking forever. /// /// ``` - /// let r = ureq::builder() - /// .timeout_connect(std::time::Duration::from_secs(10)) // max 10 seconds + /// use std::time::Duration; + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::builder() + /// .timeout_connect(Duration::from_secs(10)) /// .build() - /// .get("/my_page") - /// .call(); - /// - /// println!("{:?}", r); + /// .get("http://example.com/") + /// .call()?; + /// # Ok(()) + /// # } /// ``` pub fn call(self) -> Result { self.do_call(Payload::Empty) @@ -104,8 +109,11 @@ impl Request { /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); - /// let r = ureq::post("http://example.com/form") - /// .send_json(ureq::json!({ "name": "martin", "rust": true }))?; + /// let resp = ureq::post("http://httpbin.org/post") + /// .send_json(ureq::json!({ + /// "name": "martin", + /// "rust": true, + /// }))?; /// # Ok(()) /// # } /// ``` @@ -122,10 +130,12 @@ impl Request { /// The `Content-Length` header is implicitly set to the length of the serialized value. /// /// ``` - /// let body = b"Hello world!"; - /// let r = ureq::post("/my_page") - /// .send_bytes(body); - /// println!("{:?}", r); + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::put("http://httpbin.org/put") + /// .send_bytes(&[0; 1000])?; + /// # Ok(()) + /// # } /// ``` pub fn send_bytes(self, data: &[u8]) -> Result { self.do_call(Payload::Bytes(data)) @@ -147,10 +157,13 @@ impl Request { /// ``` /// // this example requires features = ["charset"] /// - /// let r = ureq::post("/my_page") + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::post("http://httpbin.org/post") /// .set("Content-Type", "text/plain; charset=iso-8859-1") - /// .send_string("Hällo Wörld!"); - /// println!("{:?}", r); + /// .send_string("Hällo Wörld!")?; + /// # Ok(()) + /// # } /// ``` pub fn send_string(self, data: &str) -> Result { let charset = @@ -164,14 +177,15 @@ impl Request { /// The `Content-Length` header is implicitly set to the length of the serialized value. /// /// ``` - /// #[macro_use] - /// extern crate ureq; - /// - /// fn main() { - /// let r = ureq::post("/my_page") - /// .send_form(&[("foo", "bar"),("foo2", "bar2")]); - /// println!("{:?}", r); - /// } + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::post("http://httpbin.org/post") + /// .send_form(&[ + /// ("foo", "bar"), + /// ("foo2", "bar2"), + /// ])?; + /// # Ok(()) + /// # } /// ``` pub fn send_form(mut self, data: &[(&str, &str)]) -> Result { if self.header("Content-Type").is_none() { @@ -194,12 +208,13 @@ impl Request { /// /// ``` /// use std::io::Cursor; - /// - /// let read = Cursor::new(vec![0x20; 100_000]); - /// - /// let resp = ureq::post("http://localhost/example-upload") - /// .set("Content-Type", "text/plain") - /// .send(read); + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let read = Cursor::new(vec![0x20; 100]); + /// let resp = ureq::post("http://httpbin.org/post") + /// .send(read)?; + /// # Ok(()) + /// # } /// ``` pub fn send(self, reader: impl Read) -> Result { self.do_call(Payload::Reader(Box::new(reader))) @@ -208,16 +223,14 @@ impl Request { /// Set a header field. /// /// ``` - /// let r = ureq::get("/my_page") - /// .set("X-API-Key", "foobar") + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::get("http://httpbin.org/bytes/1000") /// .set("Accept", "text/plain") - /// .call(); - /// - /// if r.is_ok() { - /// println!("yay got {}", r.unwrap().into_string().unwrap()); - /// } else { - /// println!("Oh no error!"); - /// } + /// .set("Range", "bytes=500-999") + /// .call()?; + /// # Ok(()) + /// # } /// ``` pub fn set(mut self, header: &str, value: &str) -> Self { header::add_header(&mut self.headers, Header::new(header, value)); @@ -282,12 +295,14 @@ impl Request { /// For example, to set `?format=json&dest=/login` /// /// ``` - /// let r = ureq::get("/my_page") + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::get("http://httpbin.org/response-headers") /// .query("format", "json") /// .query("dest", "/login") - /// .call(); - /// - /// println!("{:?}", r); + /// .call()?; + /// # Ok(()) + /// # } /// ``` pub fn query(mut self, param: &str, value: &str) -> Self { self.query_params @@ -302,10 +317,11 @@ impl Request { /// Example: /// ``` /// # fn main() -> Result<(), ureq::Error> { - /// let result = ureq::get("http://httpbin.org/status/500") + /// # ureq::is_test(true); + /// let response = ureq::get("http://httpbin.org/status/500") /// .error_for_status(false) - /// .call(); - /// assert!(result.is_ok()); + /// .call()?; + /// assert_eq!(response.status(), 500); /// # Ok(()) /// # } /// ``` diff --git a/src/response.rs b/src/response.rs index 8a492bd..de28a05 100644 --- a/src/response.rs +++ b/src/response.rs @@ -30,13 +30,17 @@ pub const DEFAULT_CHARACTER_SET: &str = "utf-8"; /// [`into_string()`](#method.into_string) consumes the response. /// /// ``` -/// let response = ureq::get("http://example.com/").call().unwrap(); +/// # fn main() -> Result<(), ureq::Error> { +/// # ureq::is_test(true); +/// let response = ureq::get("http://example.com/").call()?; /// /// // socket is still open and the response body has not been read. /// -/// let text = response.into_string().unwrap(); +/// let text = response.into_string()?; /// /// // response is consumed, and body has been read. +/// # Ok(()) +/// # } /// ``` pub struct Response { url: Option, @@ -74,9 +78,13 @@ impl Response { /// Example: /// /// ``` - /// let resp = ureq::Response::new(401, "Authorization Required", "Please log in").unwrap(); + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::Response::new(401, "Authorization Required", "Please log in")?; /// /// assert_eq!(resp.status(), 401); + /// # Ok(()) + /// # } /// ``` pub fn new(status: u16, status_text: &str, body: &str) -> Result { let r = format!("HTTP/1.1 {} {}\r\n\r\n{}\n", status, status_text, body); @@ -170,10 +178,12 @@ impl Response { /// Example: /// /// ``` - /// # #[cfg(feature = "tls")] { - /// let resp = ureq::get("https://www.google.com/").call().unwrap(); - /// assert_eq!("text/html; charset=ISO-8859-1", resp.header("content-type").unwrap()); + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::get("http://example.com/").call()?; + /// assert!(matches!(resp.header("content-type"), Some("text/html; charset=ISO-8859-1"))); /// assert_eq!("text/html", resp.content_type()); + /// # Ok(()) /// # } /// ``` pub fn content_type(&self) -> &str { @@ -192,10 +202,12 @@ impl Response { /// Example: /// /// ``` - /// # #[cfg(feature = "tls")] { - /// let resp = ureq::get("https://www.google.com/").call().unwrap(); - /// assert_eq!("text/html; charset=ISO-8859-1", resp.header("content-type").unwrap()); + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::get("http://example.com/").call()?; + /// assert!(matches!(resp.header("content-type"), Some("text/html; charset=ISO-8859-1"))); /// assert_eq!("ISO-8859-1", resp.charset()); + /// # Ok(()) /// # } /// ``` pub fn charset(&self) -> &str { @@ -213,22 +225,22 @@ impl Response { /// Example: /// /// ``` - /// # #[cfg(feature = "tls")] { /// use std::io::Read; - /// - /// let resp = - /// ureq::get("https://ureq.s3.eu-central-1.amazonaws.com/hello_world.json") - /// .call().unwrap(); + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let resp = ureq::get("http://httpbin.org/bytes/100") + /// .call()?; /// /// assert!(resp.has("Content-Length")); /// let len = resp.header("Content-Length") /// .and_then(|s| s.parse::().ok()).unwrap(); /// - /// let mut reader = resp.into_reader(); - /// let mut bytes = vec![]; - /// reader.read_to_end(&mut bytes); + /// let mut bytes: Vec = Vec::with_capacity(len); + /// resp.into_reader() + /// .read_to_end(&mut bytes)?; /// /// assert_eq!(bytes.len(), len); + /// # Ok(()) /// # } /// ``` pub fn into_reader(self) -> impl Read + Send { @@ -293,14 +305,14 @@ impl Response { /// Example: /// /// ``` - /// # #[cfg(feature = "tls")] { - /// let resp = - /// ureq::get("https://ureq.s3.eu-central-1.amazonaws.com/hello_world.json") - /// .call().unwrap(); + /// # fn main() -> Result<(), ureq::Error> { + /// # ureq::is_test(true); + /// let text = ureq::get("http://httpbin.org/get/success") + /// .call()? + /// .into_string()?; /// - /// let text = resp.into_string().unwrap(); - /// - /// assert!(text.contains("hello")); + /// assert!(text.contains("success")); + /// # Ok(()) /// # } /// ``` /// @@ -376,7 +388,7 @@ impl Response { /// Example: /// /// ``` - /// # use serde::Deserialize; + /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Hello { diff --git a/src/testserver.rs b/src/testserver.rs index 326fc54..1b36f75 100644 --- a/src/testserver.rs +++ b/src/testserver.rs @@ -14,15 +14,28 @@ use crate::{Agent, AgentBuilder}; // that all hostnames resolve to a TestServer on localhost. pub(crate) fn test_agent() -> Agent { let testserver = TestServer::new(|mut stream: TcpStream| -> io::Result<()> { - read_headers(&stream); - stream.write_all(b"HTTP/1.1 200 OK\r\n")?; - stream.write_all(b"Transfer-Encoding: chunked\r\n")?; - stream.write_all(b"Content-Type: text/html; charset=ISO-8859-1\r\n")?; - stream.write_all(b"\r\n")?; - stream.write_all(b"7\r\n")?; - stream.write_all(b"success\r\n")?; - stream.write_all(b"0\r\n")?; - stream.write_all(b"\r\n")?; + let headers = read_headers(&stream); + if headers.path() == "/status/500" { + stream.write_all(b"HTTP/1.1 500 Server Internal Error\r\n\r\n")?; + } else if headers.path() == "/bytes/100" { + stream.write_all(b"HTTP/1.1 200 OK\r\n")?; + stream.write_all(b"Content-Length: 100\r\n")?; + stream.write_all(b"\r\n")?; + stream.write_all(&[0; 100])?; + } else if headers.path() == "/redirect/3" { + stream.write_all(b"HTTP/1.1 302 Found\r\n")?; + stream.write_all(b"Location: /redirect/3\r\n")?; + stream.write_all(b"\r\n")?; + } else { + stream.write_all(b"HTTP/1.1 200 OK\r\n")?; + stream.write_all(b"Transfer-Encoding: chunked\r\n")?; + stream.write_all(b"Content-Type: text/html; charset=ISO-8859-1\r\n")?; + stream.write_all(b"\r\n")?; + stream.write_all(b"7\r\n")?; + stream.write_all(b"success\r\n")?; + stream.write_all(b"0\r\n")?; + stream.write_all(b"\r\n")?; + } Ok(()) }); // Slightly tricky thing here: we want to make sure the TestServer lives @@ -53,7 +66,6 @@ pub struct TestHeaders(Vec); #[allow(dead_code)] impl TestHeaders { // Return the path for a request, e.g. /foo from "GET /foo HTTP/1.1" - #[cfg(feature = "cookies")] pub fn path(&self) -> &str { if self.0.len() == 0 { ""