diff --git a/src/error.rs b/src/error.rs index e2c59d6..511f7b6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,7 @@ use std::fmt; /// Re-exported `reqwest` types. pub mod reqwest { pub use reqwest::{ - Error, Response, StatusCode, Url + Error, Response, StatusCode, Url, header::HeaderMap }; } use ::reqwest::*; @@ -20,16 +20,24 @@ pub type Result = std::result::Result; pub struct RiotApiError { reqwest_error: Error, retries: u8, - response: Option, + headers: Option, status_code: Option, + response_body: Option, } impl RiotApiError { - pub(crate) fn new(reqwest_error: Error, retries: u8, response: Option, status_code: Option) -> Self { + pub(crate) fn new( + reqwest_error: Error, + retries: u8, + headers: Option, + status_code: Option, + response_body: Option + ) -> Self { Self { reqwest_error: reqwest_error, retries: retries, - response: response, + headers: headers, status_code: status_code, + response_body: response_body, } } /// The reqwest::Error for the final failed request. @@ -40,18 +48,24 @@ impl RiotApiError { pub fn retries(&self) -> u8 { self.retries } - /// The failed response. - /// `Some(reqwest::Response)` if the request was sent and failed. - /// `None` if the request was not sent, OR if parsing the response JSON failed. - pub fn response(&self) -> Option<&Response> { - self.response.as_ref() - } /// The failed response's HTTP status code. /// `Some(reqwest::StatusCode)` if the request was sent and failed, OR if parsing the response JSON failed. /// `None` if the request was not sent. pub fn status_code(&self) -> Option { self.status_code } + /// The failed response's headers. + /// `Some(reqwest::header::HeaderMap)` if the request was sent and failed. + /// `None` if the request was not sent. + pub fn headers(&self) -> Option { + self.headers.clone() + } + /// The failed response's body (as a copy). + /// `Some(String)` if the request was sent and failed. + /// `None` if the request was not sent. + pub fn response_body(&self) -> Option { + self.response_body.clone() + } } impl fmt::Display for RiotApiError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/req/regional_requester.rs b/src/req/regional_requester.rs index 0d7f93a..d47231b 100644 --- a/src/req/regional_requester.rs +++ b/src/req/regional_requester.rs @@ -86,20 +86,22 @@ impl RegionalRequester { .header(Self::RIOT_KEY_HEADER, &*config.api_key) .send() .await - .map_err(|e| RiotApiError::new(e, retries, None, None))?; + .map_err(|e| RiotApiError::new(e, retries, None, None, None))?; // Maybe update rate limits (based on response headers). self.app_rate_limit.on_response(&config, &response); method_rate_limit.on_response(&config, &response); let status = response.status(); + let headers = response.headers().clone(); + // Handle normal success / failure cases. match response.error_for_status_ref() { // Success. Ok(_response) => { log::trace!("Response {} (retried {} times), parsed result.", status, retries); let value = response.json::().await; - break value.map_err(|e| RiotApiError::new(e, retries, None, Some(status))); + break value.map_err(|e| RiotApiError::new(e, retries, Some(headers), Some(status), None)); }, // Failure, may or may not be retryable. Err(err) => { @@ -110,7 +112,23 @@ impl RegionalRequester { && !status.is_server_error()) { log::debug!("Response {} (retried {} times), returning.", status, retries); - break Err(RiotApiError::new(err, retries, Some(response), Some(status))); + + // Extract the response body from bytes into a String, + // accounting for potentially non-utf-8 characters. + let content = response.bytes().await; + + break match content { + Ok(bytes) => { + let body = String::from_utf8_lossy(&bytes).into_owned(); + + Err(RiotApiError::new(err, retries, Some(headers), Some(status), Some(body))) + } + Err(_inner_err) => { + // Throw the inner error away and ignore response body parsing + + Err(RiotApiError::new(err, retries, Some(headers), Some(status), None)) + } + } } log::debug!("Response {} (retried {} times), retrying.", status, retries); },