I'm trying to use reqwest
to do a series of HTTP GETs and although many of the error states will be from reqwest
not all will be, so I decided to create my own error enum to wrap the reqwest
ones and provide the others.
I find though that many reqwest
errors do not report the URL that was attempted so I would like for my error display to report that. I cannot work out how to do it though, because when the error variant is wrapped it can only have a source
member, not anything else like a String
for holding the URL.
Here is a minimal example using thiserror
for removing some boiler plate from the error enum.
The first GET succeeds and the second one fails because it's not a valid URL. I can print the attempted URL from the caller but I'd like for my error message to report it. How should that be done?
use anyhow::Result;
#[derive(Debug, thiserror::Error)]
pub enum WebClientError {
// I can't use a format string to get the URL our of the `reqwest::Error` because
// this is a private struct with only some public methods. Also even if I could,
// `e.url()` doesn't always return a URL, for example when the error occurs during
// building the URL. I want to report what the caller asked for.
#[error("request error")]
Reqwest(#[from] reqwest::Error),
#[error("unknown web get error")]
Unknown,
}
pub async fn do_get(
client: &reqwest::Client,
url: &str,
) -> Result<reqwest::Response, WebClientError> {
let response = client.get(url).send().await;
match response {
Ok(r) => {
return Ok(r);
}
Err(e) => {
return Err(WebClientError::Reqwest(e));
}
}
// I haven't got an example of a different kind of error yet, but assume if one does happen
// then it'll be ::Unknown for now.
//response.map_err(|_| WebClientError::Unknown)
}
#[tokio::main]
async fn main() -> Result<()> {
let client = reqwest::Client::new();
// Some URLs to try to GET. First one should work; the latter two should produce an error from
// reqwest.
let urls = ["http://example.com", "foo", "telnet://"];
for url in urls.iter() {
let result = do_get(&client, url).await?;
println!("Successful GET for <{}>:", url);
println!("\t{:?}", result);
}
Ok(())
}
When run the above will produce:
Successful GET for <http://example.com>:
Response { url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("example.com")), port: None, path: "/", query: None, fragment: None }, status: 200, headers: {"accept-ranges": "bytes", "age": "311703", "cache-control": "max-age=604800", "content-type": "text/html; charset=UTF-8", "date": "Fri, 17 May 2024 18:34:07 GMT", "etag": "\"3147526947\"", "expires": "Fri, 24 May 2024 18:34:07 GMT", "last-modified": "Thu, 17 Oct 2019 07:18:26 GMT", "server": "ECAcc (nyd/D13B)", "vary": "Accept-Encoding", "x-cache": "HIT", "content-length": "1256"} }
Error: request error
Caused by:
0: builder error
1: relative URL without a base
Instead of just builder error
I'd prefer if it reported something like builder error for <foo>
.
Or should I just live with this? If I get rid of the "foo"
case so the "telnet://"
case is attempted, the error report will be:
Caused by:
0: builder error for url (telnet://)
1: URL scheme is not allowed
so reqwest
does actually report the URL when it has it, Still I feel like it's bad that I am unable to show what the caller supplied.
Thanks for your thoughts.