Skip to content

Commit e36c1c2

Browse files
authored
Switch to Result-based API. (fortanix#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 fortanix#128.
1 parent 257d4e5 commit e36c1c2

19 files changed

+222
-344
lines changed

README.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,12 @@ let resp = ureq::post("https://myapi.example.com/ingest")
1717
.send_json(serde_json::json!({
1818
"name": "martin",
1919
"rust": true
20-
}));
20+
}))?;
2121

2222
// .ok() tells if response is 200-299.
2323
if resp.ok() {
2424
println!("success: {}", resp.into_string()?);
2525
} else {
26-
// This can include errors like failure to parse URL or
27-
// connect timeout. They are treated as synthetic
28-
// HTTP-level error statuses.
2926
println!("error {}: {}", resp.status(), resp.into_string()?);
3027
}
3128
```

examples/smoke-test/main.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ impl From<io::Error> for Oops {
1717
}
1818
}
1919

20-
impl From<&ureq::Error> for Oops {
21-
fn from(e: &ureq::Error) -> Oops {
20+
impl From<ureq::Error> for Oops {
21+
fn from(e: ureq::Error) -> Oops {
2222
Oops(e.to_string())
2323
}
2424
}
@@ -44,10 +44,7 @@ fn get(agent: &ureq::Agent, url: &String) -> Result<Vec<u8>> {
4444
.get(url)
4545
.timeout_connect(std::time::Duration::from_secs(5))
4646
.timeout(Duration::from_secs(20))
47-
.call();
48-
if let Some(err) = response.synthetic_error() {
49-
return Err(err.into());
50-
}
47+
.call()?;
5148
let mut reader = response.into_reader();
5249
let mut bytes = vec![];
5350
reader.read_to_end(&mut bytes)?;

src/agent.rs

+8-10
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ use crate::resolve::ArcResolver;
2727
/// .auth("martin", "rubbermashgum")
2828
/// .call(); // blocks. puts auth cookies in agent.
2929
///
30-
/// if !auth.ok() {
30+
/// if auth.is_err() {
3131
/// println!("Noes!");
3232
/// }
3333
///
3434
/// let secret = agent
3535
/// .get("/my-protected-page")
3636
/// .call(); // blocks and waits for request.
3737
///
38-
/// if !secret.ok() {
38+
/// if secret.is_err() {
3939
/// println!("Wot?!");
40+
/// } else {
41+
/// println!("Secret is: {}", secret.unwrap().into_string().unwrap());
4042
/// }
41-
///
42-
/// println!("Secret is: {}", secret.into_string().unwrap());
4343
/// ```
4444
#[derive(Debug, Default, Clone)]
4545
pub struct Agent {
@@ -110,8 +110,8 @@ impl Agent {
110110
/// .get("/my-page")
111111
/// .call();
112112
///
113-
/// if r.ok() {
114-
/// println!("yay got {}", r.into_string().unwrap());
113+
/// if let Ok(resp) = r {
114+
/// println!("yay got {}", resp.into_string().unwrap());
115115
/// } else {
116116
/// println!("Oh no error!");
117117
/// }
@@ -376,8 +376,7 @@ mod tests {
376376
let agent = crate::agent();
377377
let url = "https://ureq.s3.eu-central-1.amazonaws.com/sherlock.txt";
378378
// req 1
379-
let resp = agent.get(url).call();
380-
assert!(resp.ok());
379+
let resp = agent.get(url).call().unwrap();
381380
let mut reader = resp.into_reader();
382381
let mut buf = vec![];
383382
// reading the entire content will return the connection to the pool
@@ -390,8 +389,7 @@ mod tests {
390389
assert_eq!(poolsize(&agent), 1);
391390

392391
// req 2 should be done with a reused connection
393-
let resp = agent.get(url).call();
394-
assert!(resp.ok());
392+
let resp = agent.get(url).call().unwrap();
395393
assert_eq!(poolsize(&agent), 0);
396394
let mut reader = resp.into_reader();
397395
let mut buf = vec![];

src/error.rs

+30-70
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1+
use crate::response::Response;
12
use std::fmt;
23
use std::io::{self, ErrorKind};
34

4-
/// Errors that are translated to ["synthetic" responses](struct.Response.html#method.synthetic).
55
#[derive(Debug)]
66
pub enum Error {
7-
/// The url could not be understood. Synthetic error `400`.
7+
/// The url could not be understood.
88
BadUrl(String),
9-
/// The url scheme could not be understood. Synthetic error `400`.
9+
/// The url scheme could not be understood.
1010
UnknownScheme(String),
11-
/// DNS lookup failed. Synthetic error `400`.
11+
/// DNS lookup failed.
1212
DnsFailed(String),
13-
/// Connection to server failed. Synthetic error `500`.
13+
/// Connection to server failed.
1414
ConnectionFailed(String),
15-
/// Too many redirects. Synthetic error `500`.
15+
/// Too many redirects.
1616
TooManyRedirects,
17-
/// A status line we don't understand `HTTP/1.1 200 OK`. Synthetic error `500`.
17+
/// A status line we don't understand `HTTP/1.1 200 OK`.
1818
BadStatus,
19-
/// A header line that couldn't be parsed. Synthetic error `500`.
19+
/// A header line that couldn't be parsed.
2020
BadHeader,
21-
/// Some unspecified `std::io::Error`. Synthetic error `500`.
21+
/// Some unspecified `std::io::Error`.
2222
Io(io::Error),
2323
/// Proxy information was not properly formatted
2424
BadProxy,
@@ -28,6 +28,10 @@ pub enum Error {
2828
ProxyConnect,
2929
/// Incorrect credentials for proxy
3030
InvalidProxyCreds,
31+
/// HTTP status code indicating an error (e.g. 4xx, 5xx)
32+
/// Read the inner response body for details and to return
33+
/// the connection to the pool.
34+
HTTP(Box<Response>),
3135
/// TLS Error
3236
#[cfg(feature = "native-tls")]
3337
TlsError(native_tls::Error),
@@ -42,66 +46,6 @@ impl Error {
4246
_ => false,
4347
}
4448
}
45-
46-
/// For synthetic responses, this is the error code.
47-
pub fn status(&self) -> u16 {
48-
match self {
49-
Error::BadUrl(_) => 400,
50-
Error::UnknownScheme(_) => 400,
51-
Error::DnsFailed(_) => 400,
52-
Error::ConnectionFailed(_) => 500,
53-
Error::TooManyRedirects => 500,
54-
Error::BadStatus => 500,
55-
Error::BadHeader => 500,
56-
Error::Io(_) => 500,
57-
Error::BadProxy => 500,
58-
Error::BadProxyCreds => 500,
59-
Error::ProxyConnect => 500,
60-
Error::InvalidProxyCreds => 500,
61-
#[cfg(feature = "native-tls")]
62-
Error::TlsError(_) => 500,
63-
}
64-
}
65-
66-
/// For synthetic responses, this is the status text.
67-
pub fn status_text(&self) -> &str {
68-
match self {
69-
Error::BadUrl(_) => "Bad URL",
70-
Error::UnknownScheme(_) => "Unknown Scheme",
71-
Error::DnsFailed(_) => "Dns Failed",
72-
Error::ConnectionFailed(_) => "Connection Failed",
73-
Error::TooManyRedirects => "Too Many Redirects",
74-
Error::BadStatus => "Bad Status",
75-
Error::BadHeader => "Bad Header",
76-
Error::Io(_) => "Network Error",
77-
Error::BadProxy => "Malformed proxy",
78-
Error::BadProxyCreds => "Failed to parse proxy credentials",
79-
Error::ProxyConnect => "Proxy failed to connect",
80-
Error::InvalidProxyCreds => "Provided proxy credentials are incorrect",
81-
#[cfg(feature = "native-tls")]
82-
Error::TlsError(_) => "TLS Error",
83-
}
84-
}
85-
86-
/// For synthetic responses, this is the body text.
87-
pub fn body_text(&self) -> String {
88-
match self {
89-
Error::BadUrl(url) => format!("Bad URL: {}", url),
90-
Error::UnknownScheme(scheme) => format!("Unknown Scheme: {}", scheme),
91-
Error::DnsFailed(err) => format!("Dns Failed: {}", err),
92-
Error::ConnectionFailed(err) => format!("Connection Failed: {}", err),
93-
Error::TooManyRedirects => "Too Many Redirects".to_string(),
94-
Error::BadStatus => "Bad Status".to_string(),
95-
Error::BadHeader => "Bad Header".to_string(),
96-
Error::Io(ioe) => format!("Network Error: {}", ioe),
97-
Error::BadProxy => "Malformed proxy".to_string(),
98-
Error::BadProxyCreds => "Failed to parse proxy credentials".to_string(),
99-
Error::ProxyConnect => "Proxy failed to connect".to_string(),
100-
Error::InvalidProxyCreds => "Provided proxy credentials are incorrect".to_string(),
101-
#[cfg(feature = "native-tls")]
102-
Error::TlsError(err) => format!("TLS Error: {}", err),
103-
}
104-
}
10549
}
10650

10751
impl From<io::Error> for Error {
@@ -112,7 +56,23 @@ impl From<io::Error> for Error {
11256

11357
impl fmt::Display for Error {
11458
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115-
write!(f, "{}", self.body_text())
59+
match self {
60+
Error::BadUrl(url) => write!(f, "Bad URL: {}", url),
61+
Error::UnknownScheme(scheme) => write!(f, "Unknown Scheme: {}", scheme),
62+
Error::DnsFailed(err) => write!(f, "Dns Failed: {}", err),
63+
Error::ConnectionFailed(err) => write!(f, "Connection Failed: {}", err),
64+
Error::TooManyRedirects => write!(f, "Too Many Redirects"),
65+
Error::BadStatus => write!(f, "Bad Status"),
66+
Error::BadHeader => write!(f, "Bad Header"),
67+
Error::Io(ioe) => write!(f, "Network Error: {}", ioe),
68+
Error::BadProxy => write!(f, "Malformed proxy"),
69+
Error::BadProxyCreds => write!(f, "Failed to parse proxy credentials"),
70+
Error::ProxyConnect => write!(f, "Proxy failed to connect"),
71+
Error::InvalidProxyCreds => write!(f, "Provided proxy credentials are incorrect"),
72+
Error::HTTP(response) => write!(f, "HTTP status {}", response.status()),
73+
#[cfg(feature = "native-tls")]
74+
Error::TlsError(err) => write!(f, "TLS Error: {}", err),
75+
}
11676
}
11777
}
11878

src/lib.rs

+7-24
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,11 @@
2424
//! "rust": true
2525
//! }));
2626
//!
27-
//! // .ok() tells if response is 200-299.
28-
//! if resp.ok() {
27+
//! if let Ok(resp) = resp {
2928
//! println!("success: {}", resp.into_string()?);
3029
//! } else {
3130
//! // This can include errors like failure to parse URL or connect timeout.
32-
//! // They are treated as synthetic HTTP-level error statuses.
33-
//! println!("error {}: {}", resp.status(), resp.into_string()?);
31+
//! println!("error {}", resp.err().unwrap());
3432
//! }
3533
//! Ok(())
3634
//! }
@@ -103,20 +101,6 @@
103101
//! we first check if the user has set a `; charset=<whatwg charset>` and attempt
104102
//! to encode the request body using that.
105103
//!
106-
//! # Synthetic errors
107-
//!
108-
//! Rather than exposing a custom error type through results, this library has opted for
109-
//! representing potential connection/TLS/etc errors as HTTP response codes. These invented codes
110-
//! are called "[synthetic](struct.Response.html#method.synthetic)."
111-
//!
112-
//! The idea is that from a library user's point of view the distinction of whether a failure
113-
//! originated in the remote server (500, 502) etc, or some transient network failure, the code
114-
//! path of handling that would most often be the same.
115-
//!
116-
//! As a result, reading from a Response may yield an error message generated by the ureq library.
117-
//! To handle these errors, use the
118-
//! [`response.synthetic_error()`](struct.Response.html#method.synthetic_error) method.
119-
//!
120104
121105
mod agent;
122106
mod body;
@@ -158,7 +142,7 @@ pub fn agent() -> Agent {
158142
/// Make a request setting the HTTP method via a string.
159143
///
160144
/// ```
161-
/// ureq::request("GET", "https://www.google.com").call();
145+
/// ureq::request("GET", "http://example.com").call().unwrap();
162146
/// ```
163147
pub fn request(method: &str, path: &str) -> Request {
164148
Agent::new().request(method, path)
@@ -210,7 +194,7 @@ mod tests {
210194

211195
#[test]
212196
fn connect_http_google() {
213-
let resp = get("http://www.google.com/").call();
197+
let resp = get("http://www.google.com/").call().unwrap();
214198
assert_eq!(
215199
"text/html; charset=ISO-8859-1",
216200
resp.header("content-type").unwrap()
@@ -221,7 +205,7 @@ mod tests {
221205
#[test]
222206
#[cfg(any(feature = "tls", feature = "native-tls"))]
223207
fn connect_https_google() {
224-
let resp = get("https://www.google.com/").call();
208+
let resp = get("https://www.google.com/").call().unwrap();
225209
assert_eq!(
226210
"text/html; charset=ISO-8859-1",
227211
resp.header("content-type").unwrap()
@@ -232,8 +216,7 @@ mod tests {
232216
#[test]
233217
#[cfg(any(feature = "tls", feature = "native-tls"))]
234218
fn connect_https_invalid_name() {
235-
let resp = get("https://example.com{REQUEST_URI}/").call();
236-
assert_eq!(400, resp.status());
237-
assert!(resp.synthetic());
219+
let result = get("https://example.com{REQUEST_URI}/").call();
220+
assert!(matches!(result.unwrap_err(), Error::DnsFailed(_)));
238221
}
239222
}

0 commit comments

Comments
 (0)