1use thiserror::Error;
4
5#[derive(Debug, Error)]
7pub enum SteamUserError {
8 #[error("Not logged in")]
10 NotLoggedIn,
11
12 #[error("Session expired")]
14 SessionExpired,
15
16 #[error("Renewal rejected: Steam returned no access token (refresh token no longer renewable)")]
22 RenewalRejected,
23
24 #[error("Family View restricted")]
26 FamilyViewRestricted,
27
28 #[error("Account is limited: {0}")]
30 LimitedAccount(String),
31
32 #[error("HTTP error: {0}")]
34 HttpError(#[from] reqwest::Error),
35
36 #[error("Malformed response: {0}")]
38 MalformedResponse(String),
39
40 #[error("Steam error: {0}")]
42 SteamError(String),
43
44 #[error("EResult {code}: {message}")]
46 EResult {
47 code: i32,
49 message: String,
51 },
52
53 #[error("Invalid confirmation key")]
55 InvalidConfirmationKey,
56
57 #[error("Confirmation not found for object {0}")]
59 ConfirmationNotFound(u64),
60
61 #[error("2FA error: {0}")]
63 TwoFactorError(String),
64
65 #[error("Invalid image format: {0}")]
67 InvalidImageFormat(String),
68
69 #[error("Rate limited")]
71 RateLimited,
72
73 #[error("Missing credential: {field}")]
75 MissingCredential {
76 field: &'static str,
79 },
80
81 #[error("HTTP {status} from {url}")]
83 HttpStatus {
84 status: u16,
86 url: String,
88 },
89
90 #[error("HTTP client build failed: {0}")]
92 ClientBuild(String),
93
94 #[error("Redirect error: {0}")]
96 RedirectError(String),
97
98 #[error("Invalid input: {0}")]
100 InvalidInput(String),
101
102 #[error("Protobuf encode error: {0}")]
104 ProtobufEncode(#[from] prost::EncodeError),
105
106 #[error("Protobuf decode error: {0}")]
108 ProtobufDecode(#[from] prost::DecodeError),
109
110 #[error("URL error: {0}")]
112 UrlError(#[from] url::ParseError),
113
114 #[error("JSON error: {0}")]
116 JsonError(#[from] serde_json::Error),
117
118 #[error("Base64 error: {0}")]
120 Base64Error(#[from] base64::DecodeError),
121
122 #[error("I/O error: {0}")]
124 Io(#[from] std::io::Error),
125
126 #[error("System time error: {0}")]
128 SystemTime(#[from] std::time::SystemTimeError),
129
130 #[error("{0}")]
132 Other(String),
133
134 #[cfg(feature = "remote")]
136 #[error(transparent)]
137 RemoteFailed(#[from] Box<crate::remote::RemoteSteamUserError>),
138
139 #[cfg(feature = "gas")]
141 #[error(transparent)]
142 GasFailed(#[from] Box<crate::gas::GasError>),
143
144 #[error("TOTP error: {0}")]
146 Totp(#[from] steam_totp::TotpError),
147
148 #[error("Middleware error: {0:#}")]
153 Middleware(anyhow::Error),
154
155 #[error("Failed to execute {action:?}: {source}")]
157 ActionFailed {
158 action: crate::action::ApiAction,
160 #[source]
162 source: Box<SteamUserError>,
163 },
164}
165
166impl From<reqwest_middleware::Error> for SteamUserError {
167 fn from(err: reqwest_middleware::Error) -> Self {
168 match err {
169 reqwest_middleware::Error::Reqwest(e) => Self::HttpError(e),
170 reqwest_middleware::Error::Middleware(e) => Self::Middleware(e),
171 }
172 }
173}
174
175impl SteamUserError {
176 pub fn api_action(&self) -> Option<crate::action::ApiAction> {
178 match self {
179 Self::ActionFailed { action, .. } => Some(*action),
180 _ => None,
181 }
182 }
183
184 pub fn from_eresult(code: i32) -> Self {
186 let message = steam_enums::eresult::EResult::from_i32(code).map(|e| format!("{e:?}")).unwrap_or_else(|| format!("Unknown({code})"));
187 Self::EResult { code, message }
188 }
189
190 pub fn check_eresult(code: i32) -> Result<(), Self> {
192 if code == 1 {
193 Ok(())
194 } else {
195 Err(Self::from_eresult(code))
196 }
197 }
198
199 pub fn is_retryable(&self) -> bool {
201 match self {
202 Self::ActionFailed { action, source } => action.is_read_only() && source.is_retryable(),
203 Self::RateLimited => true,
204 Self::HttpStatus { status, .. } => *status == 429 || *status >= 500,
205 Self::HttpError(e) => e.is_connect() || e.is_timeout(),
206 Self::Middleware(_) => true,
207 #[cfg(feature = "remote")]
208 Self::RemoteFailed(e) => matches!(
209 e.as_ref(),
210 crate::remote::RemoteSteamUserError::Http(_)
211 | crate::remote::RemoteSteamUserError::AllRetriesFailed
212 ),
213 #[cfg(feature = "gas")]
214 Self::GasFailed(e) => matches!(
215 e.as_ref(),
216 crate::gas::GasError::Http(_) | crate::gas::GasError::AllRetriesFailed
217 ),
218 _ => false,
219 }
220 }
221}