steam-client-rs 0.1.0

Steam client for Rust - Individual and Anonymous user account types
Documentation
//! Steam client error types.

use steam_auth::EAuthSessionGuardType;
use steam_cm_provider::CmError;
use steam_enums::EResult;
use thiserror::Error;

/// Errors that can occur when using the Steam client.
#[derive(Error, Debug)]
pub enum SteamError {
    /// Steam returned an error result.
    #[error("Steam error: {0:?}")]
    SteamResult(EResult),

    /// Connection error.
    #[error("Connection error: {0}")]
    ConnectionError(String),

    /// Already logged on.
    #[error("Already logged on")]
    AlreadyLoggedOn,

    /// Already connecting.
    #[error("Already connecting")]
    AlreadyConnecting,

    /// Not logged on.
    #[error("Not logged on")]
    NotLoggedOn,

    /// Not connected.
    #[error("Not connected")]
    NotConnected,

    /// Invalid credentials.
    #[error("Invalid credentials")]
    InvalidCredentials,

    /// Steam Guard authentication required.
    ///
    /// This error is returned when password authentication requires a Steam
    /// Guard code. The `guard_type` indicates what kind of code is needed:
    /// - `EmailCode`: A code sent to the account's email
    /// - `DeviceCode`: A TOTP code from the Steam mobile app
    /// - `DeviceConfirmation`: Approval via the Steam mobile app
    #[error("Steam Guard required: {guard_type:?}")]
    SteamGuardRequired {
        /// Type of Steam Guard verification needed.
        guard_type: EAuthSessionGuardType,
        /// Email domain hint (e.g., "g****.com") if guard_type is EmailCode.
        email_domain: Option<String>,
    },

    /// Two-factor authentication required (legacy).
    #[error("Two-factor authentication required")]
    TwoFactorRequired,

    /// Invalid token.
    #[error("Invalid token: {0}")]
    InvalidToken(String),

    /// Network error.
    #[error("Network error: {0}")]
    NetworkError(std::io::Error),

    /// Timeout.
    #[error("Operation timed out")]
    Timeout,

    /// Response timed out.
    #[error("Response timed out")]
    ResponseTimeout,

    /// Deserialization failed.
    #[error("Deserialization failed")]
    DeserializationFailed,

    /// Protocol error.
    #[error("Protocol error: {0}")]
    ProtocolError(String),

    /// Bad response from Steam.
    ///
    /// This error is returned when Steam returns a malformed response or one
    /// that violates expectations (e.g. missing SteamID in logon response).
    #[error("Bad response: {message}")]
    BadResponse {
        /// Human-readable error message.
        message: String,
        /// The EMsg that triggered this error (if known).
        emsg: Option<steam_enums::EMsg>,
        /// The raw bytes that failed to parse (truncated for display).
        raw_bytes: Option<Vec<u8>>,
    },

    /// Session error.
    #[error("Session error: {0}")]
    SessionError(#[from] steam_auth::SessionError),

    /// Not implemented yet.
    #[error("Not implemented: {0}")]
    NotImplemented(String),

    /// Other error.
    #[error("{0}")]
    Other(String),
}

impl SteamError {
    /// Returns the EResult if this is a Steam result error.
    pub fn eresult(&self) -> Option<EResult> {
        match self {
            SteamError::SteamResult(result) => Some(*result),
            _ => None,
        }
    }

    /// Returns true if the error is a transient error that might be resolved by
    /// retrying.
    ///
    /// Matches Node.js behavior for handling:
    /// - Fail
    /// - ServiceUnavailable
    /// - TryAnotherCM
    /// - NoConnection (in logoff context)
    pub fn is_retryable(&self) -> bool {
        match self {
            SteamError::SteamResult(result) => matches!(result, EResult::Fail | EResult::ServiceUnavailable | EResult::TryAnotherCM | EResult::NoConnection),
            SteamError::NetworkError(_) | SteamError::Timeout => true,
            _ => false,
        }
    }

    /// Create a BadResponse error with just a message (backwards-compatible
    /// shorthand).
    pub fn bad_response(message: impl Into<String>) -> Self {
        SteamError::BadResponse { message: message.into(), emsg: None, raw_bytes: None }
    }

    /// Create a BadResponse error with full context.
    pub fn bad_response_with_context(message: impl Into<String>, emsg: Option<steam_enums::EMsg>, raw_bytes: Option<Vec<u8>>) -> Self {
        SteamError::BadResponse { message: message.into(), emsg, raw_bytes }
    }
}

impl From<CmError> for SteamError {
    fn from(e: CmError) -> Self {
        match e {
            CmError::Network(s) => SteamError::NetworkError(std::io::Error::other(s)),
            CmError::Protocol(s) => SteamError::ProtocolError(s),
            CmError::ApiError(status, msg) => SteamError::ProtocolError(format!("Steam API error (status {}): {}", status, msg)),
            CmError::InvalidResponse(s) => SteamError::bad_response(s),
            CmError::Connection(s) => SteamError::ConnectionError(s),
            CmError::CacheError(s) => SteamError::Other(format!("Cache error: {}", s)),
            CmError::Timeout => SteamError::Timeout,
            CmError::NoServers => SteamError::ConnectionError("No CM servers available".into()),
            CmError::Io(e) => SteamError::NetworkError(e),
            CmError::Json(e) => SteamError::ProtocolError(format!("JSON error: {}", e)),
            CmError::Other(s) => SteamError::Other(s),
        }
    }
}