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