1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! 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),
}
}
}