diff --git a/steamguard/src/transport/mod.rs b/steamguard/src/transport/mod.rs index d587661..2a71145 100644 --- a/steamguard/src/transport/mod.rs +++ b/steamguard/src/transport/mod.rs @@ -7,9 +7,27 @@ use crate::steamapi::{ApiRequest, ApiResponse, BuildableRequest}; pub trait Transport { fn send_request( - &mut self, + &self, req: ApiRequest, - ) -> anyhow::Result>; + ) -> Result, TransportError>; 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), +} diff --git a/steamguard/src/transport/webapi.rs b/steamguard/src/transport/webapi.rs index 81c1f2e..63d5446 100644 --- a/steamguard/src/transport/webapi.rs +++ b/steamguard/src/transport/webapi.rs @@ -2,7 +2,7 @@ use log::{debug, trace}; use protobuf::MessageFull; use reqwest::{blocking::multipart::Form, Url}; -use super::Transport; +use super::{Transport, TransportError}; use crate::steamapi::{ApiRequest, ApiResponse, BuildableRequest, EResult}; lazy_static! { @@ -36,9 +36,9 @@ impl WebApiTransport { impl Transport for WebApiTransport { fn send_request( - &mut self, + &self, apireq: ApiRequest, - ) -> anyhow::Result> { + ) -> anyhow::Result, TransportError> { // 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: // - 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 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(); @@ -76,14 +76,31 @@ impl Transport for WebApiTransport { debug!("Response HTTP status: {}", status); let eresult = if let Some(eresult) = resp.headers().get("x-eresult") { - debug!("HTTP Header x-eresult: {}", eresult.to_str()?); - eresult.to_str()?.parse::()?.into() + let s = eresult + .to_str() + .map_err(|err| TransportError::HeaderParseFailure { + header: "x-eresult".to_owned(), + source: err.into(), + })?; + debug!("HTTP Header x-eresult: {}", s); + s.parse::() + .map_err(|err| TransportError::HeaderParseFailure { + header: "x-eresult".to_owned(), + source: err.into(), + })? + .into() } else { EResult::Invalid }; let error_msg = if let Some(error_message) = resp.headers().get("x-error_message") { - debug!("HTTP Header x-error_message: {}", error_message.to_str()?); - Some(error_message.to_str()?.to_owned()) + let s = error_message + .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 { None }; @@ -91,6 +108,10 @@ impl Transport for WebApiTransport { let bytes = resp.bytes()?; if !status.is_success() { trace!("Response body (raw): {:?}", bytes); + + if status == reqwest::StatusCode::UNAUTHORIZED { + return Err(TransportError::Unauthorized); + } } let res = decode_msg::(bytes.as_ref())?; @@ -113,9 +134,8 @@ fn encode_msg(msg: &T, config: base64::Config) -> anyhow::Result Ok(b64) } -fn decode_msg(bytes: &[u8]) -> anyhow::Result { - let msg = T::parse_from_bytes(bytes)?; - Ok(msg) +fn decode_msg(bytes: &[u8]) -> Result { + T::parse_from_bytes(bytes) } #[cfg(test)]