1use std::error::Error as StdError;
3use std::fmt;
4use std::time::Duration;
5
6use reqwest::StatusCode;
7
8use crate::types::Error as ApiError;
9
10pub type Result<T, E = Error> = std::result::Result<T, E>;
12
13pub struct Error {
15 inner: Box<Inner>,
16}
17
18type BoxError = Box<dyn StdError + Send + Sync>;
19
20struct Inner {
21 kind: Kind,
22 error_ref: Option<u16>,
23 source: Option<BoxError>,
24}
25
26impl Error {
27 #[inline]
28 pub(crate) fn new(kind: Kind) -> Self {
29 Self {
30 inner: Box::new(Inner {
31 kind,
32 error_ref: None,
33 source: None,
34 }),
35 }
36 }
37
38 #[inline]
39 pub(crate) fn with<E: Into<BoxError>>(mut self, source: E) -> Self {
40 self.inner.source = Some(source.into());
41 self
42 }
43
44 #[inline]
45 pub(crate) fn with_error_ref(mut self, error_ref: u16) -> Self {
46 self.inner.error_ref = Some(error_ref);
47 self
48 }
49
50 pub fn is_auth(&self) -> bool {
53 matches!(self.inner.kind, Kind::Unauthorized | Kind::TokenRequired)
54 }
55
56 pub fn is_terms_acceptance_required(&self) -> bool {
59 matches!(self.inner.kind, Kind::TermsAcceptanceRequired)
60 }
61
62 pub fn is_builder(&self) -> bool {
64 matches!(self.inner.kind, Kind::Builder)
65 }
66
67 pub fn is_download(&self) -> bool {
69 matches!(self.inner.kind, Kind::Download)
70 }
71
72 pub fn is_ratelimited(&self) -> bool {
74 matches!(self.inner.kind, Kind::RateLimit { .. })
75 }
76
77 pub fn is_response(&self) -> bool {
79 matches!(self.inner.kind, Kind::Response { .. })
80 }
81
82 pub fn is_validation(&self) -> bool {
84 matches!(self.inner.kind, Kind::Validation { .. })
85 }
86
87 pub fn is_decode(&self) -> bool {
89 matches!(self.inner.kind, Kind::Decode)
90 }
91
92 pub fn api_error(&self) -> Option<&ApiError> {
94 match &self.inner.kind {
95 Kind::Response { error, .. } => Some(error),
96 _ => None,
97 }
98 }
99
100 pub fn error_ref(&self) -> Option<u16> {
104 self.inner.error_ref
105 }
106
107 pub fn status(&self) -> Option<StatusCode> {
109 match self.inner.kind {
110 Kind::Response { status, .. } => Some(status),
111 _ => None,
112 }
113 }
114
115 pub fn validation(&self) -> Option<(&String, &Vec<(String, String)>)> {
117 match self.inner.kind {
118 Kind::Validation {
119 ref message,
120 ref errors,
121 } => Some((message, errors)),
122 _ => None,
123 }
124 }
125}
126
127impl fmt::Debug for Error {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 let mut builder = f.debug_struct("modio::Error");
130
131 builder.field("kind", &self.inner.kind);
132 if let Some(ref error_ref) = self.inner.error_ref {
133 builder.field("error_ref", error_ref);
134 }
135
136 if let Some(ref source) = self.inner.source {
137 builder.field("source", source);
138 }
139 builder.finish()
140 }
141}
142
143impl fmt::Display for Error {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self.inner.kind {
146 Kind::Unauthorized => f.write_str("unauthorized")?,
147 Kind::TokenRequired => f.write_str("access token is required")?,
148 Kind::TermsAcceptanceRequired => f.write_str("terms acceptance is required")?,
149 Kind::Builder => f.write_str("builder error")?,
150 Kind::Decode => f.write_str("error decoding response body")?,
151 Kind::Download => f.write_str("download error")?,
152 Kind::Request => f.write_str("http request error")?,
153 Kind::Response { status, .. } => {
154 let prefix = if status.is_client_error() {
155 "HTTP status client error"
156 } else {
157 debug_assert!(status.is_server_error());
158 "HTTP status server error"
159 };
160 write!(f, "{prefix} ({status})")?;
161 }
162 Kind::RateLimit { retry_after } => {
163 write!(f, "API rate limit reached. Try again in {retry_after:?}.")?;
164 }
165 Kind::Validation {
166 ref message,
167 ref errors,
168 } => {
169 write!(f, "validation failed: '{message}' {errors:?}")?;
170 }
171 };
172 if let Some(ref e) = self.inner.source {
173 write!(f, ": {e}")?;
174 }
175 Ok(())
176 }
177}
178
179impl StdError for Error {
180 fn source(&self) -> Option<&(dyn StdError + 'static)> {
181 self.inner.source.as_ref().map(|e| &**e as _)
182 }
183}
184
185#[derive(Debug)]
186pub(crate) enum Kind {
187 Unauthorized,
189 TokenRequired,
191 TermsAcceptanceRequired,
193 Download,
194 Validation {
195 message: String,
196 errors: Vec<(String, String)>,
197 },
198 RateLimit {
199 retry_after: Duration,
200 },
201 Builder,
202 Request,
203 Response {
204 status: StatusCode,
205 error: ApiError,
206 },
207 Decode,
208}
209
210pub(crate) fn token_required() -> Error {
211 Error::new(Kind::TokenRequired)
212}
213
214pub(crate) fn builder_or_request(e: reqwest::Error) -> Error {
215 if e.is_builder() {
216 builder(e)
217 } else {
218 request(e)
219 }
220}
221
222pub(crate) fn builder<E: Into<BoxError>>(source: E) -> Error {
223 Error::new(Kind::Builder).with(source)
224}
225
226pub(crate) fn request<E: Into<BoxError>>(source: E) -> Error {
227 Error::new(Kind::Request).with(source)
228}
229
230pub(crate) fn decode<E: Into<BoxError>>(source: E) -> Error {
231 Error::new(Kind::Decode).with(source)
232}
233
234pub(crate) fn error_for_status(status: StatusCode, error: ApiError) -> Error {
235 let error_ref = error.error_ref;
236 let kind = match status {
237 StatusCode::UNPROCESSABLE_ENTITY => Kind::Validation {
238 message: error.message,
239 errors: error.errors,
240 },
241 StatusCode::UNAUTHORIZED => Kind::Unauthorized,
242 StatusCode::FORBIDDEN if error_ref == 11051 => Kind::TermsAcceptanceRequired,
243 _ => Kind::Response { status, error },
244 };
245 Error::new(kind).with_error_ref(error_ref)
246}
247
248pub(crate) fn ratelimit(retry_after: u64) -> Error {
249 Error::new(Kind::RateLimit {
250 retry_after: Duration::from_secs(retry_after),
251 })
252}
253
254pub(crate) fn download<E: Into<BoxError>>(source: E) -> Error {
255 Error::new(Kind::Download).with(source)
256}