Switch to Result-based API. (#132)

Gets rid of synthetic_error, and makes the various send_* methods return `Result<Response, Error>`.
Introduces a new error type "HTTP", which represents an error due to status codes 4xx or 5xx.
The HTTP error type contains a boxed Response, so users can read the actual response if they want.
Adds an `error_for_status` setting to disable the functionality of treating 4xx and 5xx as errors.
Adds .unwrap() to a lot of tests.

Fixes #128.
This commit is contained in:
Jacob Hoffman-Andrews
2020-10-17 00:40:48 -07:00
committed by GitHub
parent 257d4e54dd
commit e36c1c2aa1
19 changed files with 222 additions and 344 deletions

View File

@@ -1,6 +1,5 @@
use std::fmt;
use std::io::Read;
use std::result::Result;
use std::sync::{Arc, Mutex};
use std::time;
@@ -19,6 +18,8 @@ use crate::Response;
#[cfg(feature = "json")]
use super::SerdeValue;
pub type Result<T> = std::result::Result<T, Error>;
/// Request instances are builders that creates a request.
///
/// ```
@@ -37,6 +38,7 @@ pub struct Request {
url: String,
// from request itself
return_error_for_status: bool,
pub(crate) headers: Vec<Header>,
pub(crate) query: QString,
pub(crate) timeout_connect: Option<time::Duration>,
@@ -76,6 +78,7 @@ impl Request {
url,
headers: agent.headers.clone(),
redirects: 5,
return_error_for_status: true,
..Default::default()
}
}
@@ -104,18 +107,22 @@ impl Request {
///
/// println!("{:?}", r);
/// ```
pub fn call(&mut self) -> Response {
pub fn call(&mut self) -> Result<Response> {
self.do_call(Payload::Empty)
}
fn do_call(&mut self, payload: Payload) -> Response {
self.to_url()
.and_then(|url| {
let reader = payload.into_read();
let unit = Unit::new(&self, &url, true, &reader);
unit::connect(&self, unit, true, 0, reader, false)
})
.unwrap_or_else(|e| e.into())
fn do_call(&self, payload: Payload) -> Result<Response> {
let response = self.to_url().and_then(|url| {
let reader = payload.into_read();
let unit = Unit::new(&self, &url, true, &reader);
unit::connect(&self, unit, true, 0, reader, false)
})?;
if response.error() && self.return_error_for_status {
Err(Error::HTTP(response.into()))
} else {
Ok(response)
}
}
/// Send data a json value.
@@ -135,7 +142,7 @@ impl Request {
/// }
/// ```
#[cfg(feature = "json")]
pub fn send_json(&mut self, data: SerdeValue) -> Response {
pub fn send_json(&mut self, data: SerdeValue) -> Result<Response> {
if self.header("Content-Type").is_none() {
self.set("Content-Type", "application/json");
}
@@ -152,7 +159,7 @@ impl Request {
/// .send_bytes(body);
/// println!("{:?}", r);
/// ```
pub fn send_bytes(&mut self, data: &[u8]) -> Response {
pub fn send_bytes(&mut self, data: &[u8]) -> Result<Response> {
self.do_call(Payload::Bytes(data.to_owned()))
}
@@ -177,7 +184,7 @@ impl Request {
/// .send_string("Hällo Wörld!");
/// println!("{:?}", r);
/// ```
pub fn send_string(&mut self, data: &str) -> Response {
pub fn send_string(&mut self, data: &str) -> Result<Response> {
let text = data.into();
let charset =
crate::response::charset_from_content_type(self.header("content-type")).to_string();
@@ -199,7 +206,7 @@ impl Request {
/// println!("{:?}", r);
/// }
/// ```
pub fn send_form(&mut self, data: &[(&str, &str)]) -> Response {
pub fn send_form(&mut self, data: &[(&str, &str)]) -> Result<Response> {
if self.header("Content-Type").is_none() {
self.set("Content-Type", "application/x-www-form-urlencoded");
}
@@ -227,7 +234,7 @@ impl Request {
/// .set("Content-Type", "text/plain")
/// .send(read);
/// ```
pub fn send(&mut self, reader: impl Read + 'static) -> Response {
pub fn send(&mut self, reader: impl Read + 'static) -> Result<Response> {
self.do_call(Payload::Reader(Box::new(reader)))
}
@@ -239,8 +246,8 @@ impl Request {
/// .set("Accept", "text/plain")
/// .call();
///
/// if r.ok() {
/// println!("yay got {}", r.into_string().unwrap());
/// if r.is_ok() {
/// println!("yay got {}", r.unwrap().into_string().unwrap());
/// } else {
/// println!("Oh no error!");
/// }
@@ -446,8 +453,7 @@ impl Request {
/// Defaults to `5`. Set to `0` to avoid redirects and instead
/// get a response object with the 3xx status code.
///
/// If the redirect count hits this limit (and it's > 0), a synthetic 500 error
/// response is produced.
/// If the redirect count hits this limit (and it's > 0), TooManyRedirects is returned.
///
/// ```
/// let r = ureq::get("/my_page")
@@ -460,6 +466,25 @@ impl Request {
self
}
/// By default, if a response's status is anything but a 2xx or 3xx,
/// send() and related methods will return an Error. If you want
/// to handle such responses as non-errors, set this to false.
///
/// Example:
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// let result = ureq::get("http://httpbin.org/status/500")
/// .error_for_status(false)
/// .call();
/// assert!(result.is_ok());
/// # Ok(())
/// # }
/// ```
pub fn error_for_status(&mut self, value: bool) -> &mut Request {
self.return_error_for_status = value;
self
}
/// Get the method this request is using.
///
/// Example:
@@ -499,7 +524,7 @@ impl Request {
/// .build();
/// assert_eq!(req2.get_host().unwrap(), "localhost");
/// ```
pub fn get_host(&self) -> Result<String, Error> {
pub fn get_host(&self) -> Result<String> {
match self.to_url() {
Ok(u) => match u.host_str() {
Some(host) => Ok(host.to_string()),
@@ -517,7 +542,7 @@ impl Request {
/// .build();
/// assert_eq!(req.get_scheme().unwrap(), "https");
/// ```
pub fn get_scheme(&self) -> Result<String, Error> {
pub fn get_scheme(&self) -> Result<String> {
self.to_url().map(|u| u.scheme().to_string())
}
@@ -530,7 +555,7 @@ impl Request {
/// .build();
/// assert_eq!(req.get_query().unwrap(), "?foo=bar&format=json");
/// ```
pub fn get_query(&self) -> Result<String, Error> {
pub fn get_query(&self) -> Result<String> {
self.to_url()
.map(|u| unit::combine_query(&u, &self.query, true))
}
@@ -543,11 +568,11 @@ impl Request {
/// .build();
/// assert_eq!(req.get_path().unwrap(), "/innit");
/// ```
pub fn get_path(&self) -> Result<String, Error> {
pub fn get_path(&self) -> Result<String> {
self.to_url().map(|u| u.path().to_string())
}
fn to_url(&self) -> Result<Url, Error> {
fn to_url(&self) -> Result<Url> {
Url::parse(&self.url).map_err(|e| Error::BadUrl(format!("{}", e)))
}