1use std::fmt;
4
5use crate::macros::error_with_source;
6
7#[must_use]
26#[derive(Debug)]
27pub struct Error {
28 kind: ErrorKind,
29 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
30}
31
32#[allow(clippy::module_name_repetitions)]
34#[must_use]
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36#[non_exhaustive]
37pub enum ErrorKind {
38 ContentTypeNotFound,
40 ReadContentTypeFailed,
42 ContentTypeMismatch,
44 BotTokenNotFound,
46 ReadBotTokenFailed,
48 BotTokenMismatch,
50 BotEventNotFound,
52 ReadBotEventFailed,
54 BotEventMismatch,
56 ReadBodyFailed,
58 ParseBodyFailed,
60 Handler,
62}
63
64pub type Result<T, E = Error> = std::result::Result<T, E>;
66
67impl Error {
68 pub fn kind(&self) -> ErrorKind {
79 self.kind
80 }
81}
82
83impl Error {
84 pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
85 where
86 E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
87 {
88 Self {
89 kind,
90 source: Some(source.into()),
91 }
92 }
93
94 error_with_source! {#[allow(dead_code)] pub(crate) ContentTypeNotFound}
95 error_with_source! {pub(crate) ReadContentTypeFailed}
96 error_with_source! {#[allow(dead_code)] pub(crate) ContentTypeMismatch}
97 error_with_source! {#[allow(dead_code)] pub(crate) BotTokenNotFound}
98 error_with_source! {pub(crate) ReadBotTokenFailed}
99 error_with_source! {#[allow(dead_code)] pub(crate) BotTokenMismatch}
100 error_with_source! {#[allow(dead_code)] pub(crate) BotEventNotFound}
101 error_with_source! {pub(crate) ReadBotEventFailed}
102 error_with_source! {pub(crate) BotEventMismatch}
103 error_with_source! {pub(crate) ReadBodyFailed}
104 error_with_source! {pub(crate) ParseBodyFailed}
105 error_with_source! {#[allow(dead_code)] pub(crate) Handler}
107}
108
109impl From<ErrorKind> for Error {
110 fn from(kind: ErrorKind) -> Self {
111 Self { kind, source: None }
112 }
113}
114
115impl fmt::Display for Error {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 f.write_str(self.kind.as_str())
118 }
119}
120
121impl std::error::Error for Error {
122 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
123 let s = self.source.as_deref()?;
124 Some(s as &(dyn std::error::Error + 'static))
125 }
126}
127
128impl ErrorKind {
129 pub(crate) fn as_str(self) -> &'static str {
130 match self {
131 Self::ContentTypeNotFound => "Content-Type is not set",
132 Self::ReadContentTypeFailed => "Failed to read Content-Type value",
133 Self::ContentTypeMismatch => "Content-Type value is wrong; it must be application/json",
134 Self::BotTokenNotFound => "X-TRAQ-BOT-TOKEN is not set",
135 Self::ReadBotTokenFailed => "Failed to read X-TRAQ-BOT-TOKEN value",
136 Self::BotTokenMismatch => "X-TRAQ-BOT-TOKEN value is wrong",
137 Self::BotEventNotFound => "X-TRAQ-BOT-EVENT is not set",
138 Self::ReadBotEventFailed => "Failed to read X-TRAQ-BOT-EVENT value",
139 Self::BotEventMismatch => "X-TRAQ-BOT-EVENT value is wrong",
140 Self::ReadBodyFailed => "Failed to read request body",
141 Self::ParseBodyFailed => "Failed to parse request body",
142 Self::Handler => "Event handler raised an error",
143 }
144 }
145}
146
147impl fmt::Display for ErrorKind {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 f.write_str(self.as_str())
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::macros::all_error_kinds;
157
158 fn assert_send_sync_static<T: Send + Sync + 'static>() {}
159 fn assert_display<T: std::fmt::Display>() {}
160 fn assert_error<T: std::error::Error>() {}
161
162 fn assert_convert<A, B>()
164 where
165 A: Into<B>,
166 {
167 }
168
169 #[test]
170 fn error_impl() {
171 assert_send_sync_static::<Error>();
172 assert_error::<Error>();
173 assert_convert::<ErrorKind, Error>();
174 }
175
176 #[test]
177 fn error_kind_impl() {
178 assert_send_sync_static::<ErrorKind>();
179 assert_display::<ErrorKind>();
180 }
181
182 macro_rules! tests_error_kind_convert {
183 ($( $kind:ident ),*) => {
184 $(
185 $crate::macros::test_error_kind_convert! {$kind}
186 )*
187 };
188 }
189
190 all_error_kinds! {tests_error_kind_convert}
191}