1use std::fmt;
2
3use ::http::header::{InvalidHeaderName, InvalidHeaderValue};
4
5pub struct Error {
6 inner: Box<Inner>,
7}
8
9#[derive(Debug)]
10struct Inner {
11 kind: Kind,
12 message: Option<String>,
13 input: Option<String>,
14 source: Option<BoxError>,
15}
16
17#[derive(Debug)]
18pub enum Kind {
19 RequestBuild,
20 HttpInvalidHeader,
21 AuthInvalidScheme,
22 ContentTypeInvalid,
23 ContentTypeUnsupported,
24}
25
26impl Kind {
27 pub fn category(self) -> ErrorCategory {
28 match self {
29 Kind::RequestBuild => ErrorCategory::Request,
30 Kind::HttpInvalidHeader => ErrorCategory::Http,
31 Kind::AuthInvalidScheme => ErrorCategory::Authentication,
32 Kind::ContentTypeInvalid | Kind::ContentTypeUnsupported => ErrorCategory::ContentType,
33 }
34 }
35}
36
37impl fmt::Display for Kind {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 Kind::RequestBuild => f.write_str("failed to build request"),
41 Kind::HttpInvalidHeader => f.write_str("invalid HTTP header"),
42 Kind::AuthInvalidScheme => f.write_str("invalid authentication scheme"),
43 Kind::ContentTypeInvalid => f.write_str("invalid content type"),
44 Kind::ContentTypeUnsupported => f.write_str("unsupported content type"),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum ErrorCategory {
51 Request,
52 Http,
53 Authentication,
54 ContentType,
55}
56
57type BoxError = Box<dyn std::error::Error + Send + Sync>;
58
59impl Error {
60 pub fn new(kind: Kind) -> Self {
61 Self {
62 inner: Box::new(Inner {
63 kind,
64 message: None,
65 input: None,
66 source: None,
67 }),
68 }
69 }
70
71 pub fn with_message(kind: Kind, message: impl Into<String>) -> Self {
72 Self {
73 inner: Box::new(Inner {
74 kind,
75 message: Some(message.into()),
76 input: None,
77 source: None,
78 }),
79 }
80 }
81
82 pub fn with_source(kind: Kind, source: impl Into<BoxError>) -> Self {
83 Self {
84 inner: Box::new(Inner {
85 kind,
86 message: None,
87 input: None,
88 source: Some(source.into()),
89 }),
90 }
91 }
92
93 pub fn with_message_and_source(
94 kind: Kind,
95 message: impl Into<String>,
96 source: impl Into<BoxError>,
97 ) -> Self {
98 Self {
99 inner: Box::new(Inner {
100 kind,
101 message: Some(message.into()),
102 input: None,
103 source: Some(source.into()),
104 }),
105 }
106 }
107
108 pub fn with_input(mut self, input: impl Into<String>) -> Self {
109 self.inner.input = Some(input.into());
110 self
111 }
112
113 pub fn message(&self) -> Option<&str> {
114 self.inner.message.as_deref()
115 }
116
117 pub fn input(&self) -> Option<&str> {
118 self.inner.input.as_deref()
119 }
120
121 pub fn is_request(&self) -> bool {
122 matches!(self.inner.kind, Kind::RequestBuild)
123 }
124}
125
126impl fmt::Debug for Error {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 let mut builder = f.debug_struct("asknothingx2-util::api::Error");
129
130 builder.field("kind", &self.inner.kind);
131
132 if let Some(ref message) = self.inner.message {
133 builder.field("message", message);
134 }
135
136 if let Some(ref input) = self.inner.input {
137 builder.field("input", input);
138 }
139
140 if let Some(ref source) = self.inner.source {
141 builder.field("source", source);
142 }
143
144 builder.finish()
145 }
146}
147
148impl fmt::Display for Error {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 if let Some(ref message) = self.inner.message {
151 write!(f, "{message}")?;
152 } else {
153 write!(f, "{}", self.inner.kind)?;
154 }
155
156 if let Some(ref input) = self.inner.input {
157 let truncated = truncate_input(input);
158 if !truncated.is_empty() {
159 write!(
160 f,
161 " [input: {}{}]",
162 truncated,
163 if input.len() > truncated.len() {
164 "..."
165 } else {
166 ""
167 }
168 )?;
169 }
170 }
171
172 if let Some(ref source) = self.inner.source {
173 write!(f, " -> {source})")?;
174 }
175
176 Ok(())
177 }
178}
179
180impl std::error::Error for Error {
181 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
182 self.inner.source.as_ref().map(|e| &**e as _)
183 }
184}
185
186pub mod request {
187 use super::{BoxError, Error, Kind};
188
189 pub fn build<E: Into<BoxError>>(source: E) -> Error {
190 Error::with_source(Kind::RequestBuild, source)
191 }
192}
193
194pub mod http {
195 use super::{BoxError, Error, Kind};
196 pub fn invalid_header<E: Into<BoxError>>(source: E) -> Error {
197 Error::with_source(Kind::HttpInvalidHeader, source)
198 }
199}
200
201pub mod auth {
202 use super::{Error, Kind};
203
204 pub fn invalid_scheme<S: Into<String>>(scheme: S) -> Error {
205 Error::with_message(
206 Kind::AuthInvalidScheme,
207 format!("invalid authentication scheme '{}'", scheme.into()),
208 )
209 }
210}
211
212pub mod content {
213 use super::{Error, Kind};
214
215 pub fn invalid_type<T: Into<String>>(content_type: T) -> Error {
216 Error::with_message(
217 Kind::ContentTypeInvalid,
218 format!("invalid content type '{}'", content_type.into()),
219 )
220 }
221
222 pub fn unsupported<T: Into<String>>(content_type: T) -> Error {
223 Error::with_message(
224 Kind::ContentTypeUnsupported,
225 format!("unsupported content type '{}'", content_type.into()),
226 )
227 }
228}
229
230fn truncate_input(input: &str) -> &str {
231 const MAX_LEN: usize = 80;
232 if input.len() <= MAX_LEN {
233 input
234 } else {
235 &input[..MAX_LEN]
236 }
237}
238
239impl From<InvalidHeaderName> for Error {
240 fn from(err: InvalidHeaderName) -> Self {
241 Error::with_source(Kind::HttpInvalidHeader, err)
242 }
243}
244
245impl From<InvalidHeaderValue> for Error {
246 fn from(err: InvalidHeaderValue) -> Self {
247 Error::with_source(Kind::HttpInvalidHeader, err)
248 }
249}