Add TransportError type (#219)

- add TransportError type
- adjust Transport trait
This commit is contained in:
Carson McManus 2023-06-24 12:41:03 -04:00 committed by GitHub
parent 2dc6376533
commit 7e04a4240a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 13 deletions

View file

@ -7,9 +7,27 @@ use crate::steamapi::{ApiRequest, ApiResponse, BuildableRequest};
pub trait Transport { pub trait Transport {
fn send_request<Req: BuildableRequest + MessageFull, Res: MessageFull>( fn send_request<Req: BuildableRequest + MessageFull, Res: MessageFull>(
&mut self, &self,
req: ApiRequest<Req>, req: ApiRequest<Req>,
) -> anyhow::Result<ApiResponse<Res>>; ) -> Result<ApiResponse<Res>, TransportError>;
fn close(&mut self); fn close(&mut self);
} }
#[derive(Debug, thiserror::Error)]
pub enum TransportError {
#[error("Transport failed to parse response headers")]
HeaderParseFailure {
header: String,
#[source]
source: anyhow::Error,
},
#[error("Transport failed to parse response body")]
ProtobufError(#[from] protobuf::Error),
#[error("Unauthorized: Access token is missing or invalid")]
Unauthorized,
#[error("NetworkFailure: Transport failed to make request: {0}")]
NetworkFailure(#[from] reqwest::Error),
#[error("Unexpected error when transport was making request: {0}")]
Unknown(#[from] anyhow::Error),
}

View file

@ -2,7 +2,7 @@ use log::{debug, trace};
use protobuf::MessageFull; use protobuf::MessageFull;
use reqwest::{blocking::multipart::Form, Url}; use reqwest::{blocking::multipart::Form, Url};
use super::Transport; use super::{Transport, TransportError};
use crate::steamapi::{ApiRequest, ApiResponse, BuildableRequest, EResult}; use crate::steamapi::{ApiRequest, ApiResponse, BuildableRequest, EResult};
lazy_static! { lazy_static! {
@ -36,9 +36,9 @@ impl WebApiTransport {
impl Transport for WebApiTransport { impl Transport for WebApiTransport {
fn send_request<Req: BuildableRequest + MessageFull, Res: MessageFull>( fn send_request<Req: BuildableRequest + MessageFull, Res: MessageFull>(
&mut self, &self,
apireq: ApiRequest<Req>, apireq: ApiRequest<Req>,
) -> anyhow::Result<ApiResponse<Res>> { ) -> anyhow::Result<ApiResponse<Res>, TransportError> {
// All the API endpoints accept 2 data formats: json and protobuf. // All the API endpoints accept 2 data formats: json and protobuf.
// Depending on the http method for the request, the data can go in 2 places: // Depending on the http method for the request, the data can go in 2 places:
// - GET: query string, with the key `input_protobuf_encoded` or `input_json` // - GET: query string, with the key `input_protobuf_encoded` or `input_json`
@ -47,7 +47,7 @@ impl Transport for WebApiTransport {
// input protobuf data is always encoded in base64 // input protobuf data is always encoded in base64
if Req::requires_access_token() && apireq.access_token().is_none() { if Req::requires_access_token() && apireq.access_token().is_none() {
return Err(anyhow::anyhow!("Access token required for this request")); return Err(TransportError::Unauthorized);
} }
let url = apireq.build_url(); let url = apireq.build_url();
@ -76,14 +76,31 @@ impl Transport for WebApiTransport {
debug!("Response HTTP status: {}", status); debug!("Response HTTP status: {}", status);
let eresult = if let Some(eresult) = resp.headers().get("x-eresult") { let eresult = if let Some(eresult) = resp.headers().get("x-eresult") {
debug!("HTTP Header x-eresult: {}", eresult.to_str()?); let s = eresult
eresult.to_str()?.parse::<i32>()?.into() .to_str()
.map_err(|err| TransportError::HeaderParseFailure {
header: "x-eresult".to_owned(),
source: err.into(),
})?;
debug!("HTTP Header x-eresult: {}", s);
s.parse::<i32>()
.map_err(|err| TransportError::HeaderParseFailure {
header: "x-eresult".to_owned(),
source: err.into(),
})?
.into()
} else { } else {
EResult::Invalid EResult::Invalid
}; };
let error_msg = if let Some(error_message) = resp.headers().get("x-error_message") { let error_msg = if let Some(error_message) = resp.headers().get("x-error_message") {
debug!("HTTP Header x-error_message: {}", error_message.to_str()?); let s = error_message
Some(error_message.to_str()?.to_owned()) .to_str()
.map_err(|err| TransportError::HeaderParseFailure {
header: "x-error_message".to_owned(),
source: err.into(),
})?;
debug!("HTTP Header x-error_message: {}", s);
Some(s.to_owned())
} else { } else {
None None
}; };
@ -91,6 +108,10 @@ impl Transport for WebApiTransport {
let bytes = resp.bytes()?; let bytes = resp.bytes()?;
if !status.is_success() { if !status.is_success() {
trace!("Response body (raw): {:?}", bytes); trace!("Response body (raw): {:?}", bytes);
if status == reqwest::StatusCode::UNAUTHORIZED {
return Err(TransportError::Unauthorized);
}
} }
let res = decode_msg::<Res>(bytes.as_ref())?; let res = decode_msg::<Res>(bytes.as_ref())?;
@ -113,9 +134,8 @@ fn encode_msg<T: MessageFull>(msg: &T, config: base64::Config) -> anyhow::Result
Ok(b64) Ok(b64)
} }
fn decode_msg<T: MessageFull>(bytes: &[u8]) -> anyhow::Result<T> { fn decode_msg<T: MessageFull>(bytes: &[u8]) -> Result<T, protobuf::Error> {
let msg = T::parse_from_bytes(bytes)?; T::parse_from_bytes(bytes)
Ok(msg)
} }
#[cfg(test)] #[cfg(test)]