traq_bot_http/
parser.rs

1//! `struct RequestParser`の定義
2
3use std::str::from_utf8;
4use std::sync::Arc;
5
6use crate::error::{Error, ErrorKind, Result};
7use crate::macros::all_events;
8use crate::{Event, EventKind, RequestParser};
9
10#[cfg(feature = "http")]
11mod http;
12
13#[cfg(feature = "http")]
14pub use self::http::ParseRequest;
15
16/// ボディをDeserializeして`Event`に渡す
17pub(crate) fn parse_body(kind: EventKind, body: &str) -> Result<Event> {
18    macro_rules! match_kind_parse_body {
19        ($( $k:ident ),*) => {
20            match kind {
21                $(
22                    EventKind::$k => {
23                        ::serde_json::from_str(body).map(Event::$k)
24                    },
25                )*
26            }
27        };
28    }
29
30    all_events!(match_kind_parse_body).map_err(Error::parse_body_failed)
31}
32
33// https://datatracker.ietf.org/doc/html/rfc9110#section-5.5
34fn valid_header_value(value: &str) -> bool {
35    value
36        .as_bytes()
37        .iter()
38        .all(|c| (0x20..=0x7E).contains(c) || *c == 0x09)
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub(crate) struct Inner {
43    verification_token: String,
44}
45
46impl Inner {
47    pub(crate) fn new(verification_token: &str) -> Self {
48        Self {
49            verification_token: verification_token.to_string(),
50        }
51    }
52}
53
54impl RequestParser {
55    /// 新しい`RequestParser`を作成します。
56    ///
57    /// ## Arguments
58    /// * `verification_token` - ボットのVerification Token
59    ///
60    /// ## Example
61    /// ```
62    /// use traq_bot_http::RequestParser;
63    /// let parser = RequestParser::new("verification_token");
64    /// ```
65    pub fn new(verification_token: &str) -> Self {
66        Self {
67            inner: Arc::new(Inner::new(verification_token)),
68        }
69    }
70
71    /// POSTリクエストのヘッダーからイベント名を取得します。
72    ///
73    /// ## Arguments
74    /// * `headers` - リクエストのヘッダー
75    ///
76    /// ## Example
77    /// ```
78    /// use http::HeaderMap;
79    /// use traq_bot_http::RequestParser;
80    ///
81    /// let parser = RequestParser::new("verification_token");
82    /// let headers = HeaderMap::new();
83    /// let kind = parser.parse_headers(&headers);
84    /// assert!(kind.is_err());
85    /// ```
86    ///
87    /// ## Errors
88    /// [`Error`]のうち、[`Error::kind`]が以下のものを返す可能性があります。
89    ///
90    /// - [`ErrorKind::ReadContentTypeFailed`] :
91    ///     ヘッダー`Content-Type`の値をUTF8の文字列として解釈できなかった
92    /// - [`ErrorKind::ContentTypeNotFound`] :
93    ///     ヘッダー`Content-Type`が見つからなかった
94    /// - [`ErrorKind::ContentTypeMismatch`] :
95    ///     ヘッダー`Content-Type`の値が`application/json`で始まらない
96    /// - [`ErrorKind::ReadBotTokenFailed`] : ヘッダー`X-TRAQ-BOT-TOKEN`の値に関して、以下のいずれかの場合
97    ///     - 値をUTF8の文字列として解釈できなかった
98    ///     - 値が`visible US-ASCII octets (VCHAR)`, `SP`, `HTAB`以外の文字を含む ([RFC9110 5.5])
99    /// - [`ErrorKind::BotTokenNotFound`] :
100    ///     ヘッダー`X-TRAQ-BOT-TOKEN`が見つからなかった
101    /// - [`ErrorKind::BotTokenMismatch`] :
102    ///     ヘッダー`X-TRAQ-BOT-TOKEN`の値が[`new`]で与えられたVerification Tokenと合わない
103    /// - [`ErrorKind::ReadBotEventFailed`] : ヘッダー`X-TRAQ-BOT-EVENT`の値に関して、以下のいずれかの場合
104    ///     - 値をUTF8の文字列として解釈できなかった
105    ///     - 値が`visible US-ASCII octets (VCHAR)`, `SP`, `HTAB`以外の文字を含む ([RFC9110 5.5])
106    /// - [`ErrorKind::BotEventNotFound`] :
107    ///     ヘッダー`X-TRAQ-BOT-EVENT`が見つからなかった
108    /// - [`ErrorKind::BotEventMismatch`] :
109    ///     ヘッダー`X-TRAQ-BOT-EVENT`の値が[`EventKind`]の[`std::str::FromStr`]でパースできなかった
110    ///
111    /// [`Error::kind`]: crate::Error::kind
112    /// [RFC9110 5.5]: https://datatracker.ietf.org/doc/html/rfc9110#section-5.5
113    /// [`new`]: RequestParser::new
114    pub fn parse_headers<'a, H, K, V>(&self, headers: H) -> Result<EventKind>
115    where
116        H: IntoIterator<Item = (&'a K, &'a V)>,
117        K: AsRef<[u8]> + ?Sized + 'static,
118        V: AsRef<[u8]> + ?Sized + 'static,
119    {
120        // Content-Type: application/json
121        let mut content_type = None;
122        // X-TRAQ-BOT-TOKEN: ${self.verification_token}
123        let mut token = None;
124        // X-TRAQ-BOT-EVENTがヘッダーに含まれており、かつその値はイベント名のいずれかである
125        let mut kind = None;
126        for (k, v) in headers {
127            let Ok(k) = from_utf8(k.as_ref()) else {
128                continue;
129            };
130            let v = from_utf8(v.as_ref());
131            match k.to_lowercase().as_str() {
132                "content-type" => {
133                    let v = v.map_err(Error::read_content_type_failed)?;
134                    content_type = Some(v);
135                }
136                "x-traq-bot-token" => {
137                    let v = v.map_err(Error::read_bot_token_failed)?;
138                    token = Some(v);
139                }
140                "x-traq-bot-event" => {
141                    let v = v.map_err(Error::read_bot_event_failed)?;
142                    kind = Some(v);
143                }
144                _ => continue,
145            }
146        }
147        content_type
148            .ok_or(ErrorKind::ContentTypeNotFound)
149            .map(|ct| ct.starts_with("application/json"))?
150            .then_some(())
151            .ok_or(ErrorKind::ContentTypeMismatch)?;
152        token
153            .ok_or(ErrorKind::BotTokenNotFound)
154            .and_then(|t| {
155                valid_header_value(t)
156                    .then_some(t)
157                    .ok_or(ErrorKind::ReadBotTokenFailed)
158            })
159            .map(|t| t == self.inner.verification_token)?
160            .then_some(())
161            .ok_or(ErrorKind::BotTokenMismatch)?;
162        kind.ok_or(ErrorKind::BotEventNotFound)
163            .and_then(|k| {
164                valid_header_value(k)
165                    .then_some(k)
166                    .ok_or(ErrorKind::ReadBotEventFailed)
167            })?
168            .parse()
169            .map_err(Error::bot_event_mismatch)
170    }
171
172    /// HTTP POSTリクエストをパースします。
173    ///
174    /// ## Arguments
175    /// * `headers` - リクエストのヘッダー
176    /// * `body` - リクエストのボディ
177    ///
178    /// ## Example
179    /// ```
180    /// use traq_bot_http::{RequestParser, Event};
181    /// let headers = [
182    ///     ("Content-Type", "application/json"),
183    ///     ("X-TRAQ-BOT-TOKEN", "verification_token"),
184    ///     ("X-TRAQ-BOT-EVENT", "PING"),
185    /// ];
186    /// let body = br#"{"eventTime": "2019-05-07T04:50:48.582586882Z"}"#;
187    /// let verification_token = "verification_token";
188    /// let parser = RequestParser::new(verification_token);
189    /// let event = parser.parse(headers, body);
190    /// assert!(matches!(event, Ok(Event::Ping(_))));
191    /// ```
192    ///
193    /// ## Errors
194    /// [`Error`]のうち、[`Error::kind`]が以下のものを返す可能性があります。
195    ///
196    /// - [`parse_headers`]で返されるもの
197    /// - [`ErrorKind::ReadBodyFailed`] :
198    ///     `body`をUTF8の文字列として解釈できなかった
199    /// - [`ErrorKind::ParseBodyFailed`] :
200    ///     `body`を[`parse_headers`]で返される[`EventKind`]に対応する
201    ///     [`Event`]のペイロードJSONとしてデシリアライズできなかった。
202    ///
203    /// [`Error::kind`]: crate::Error::kind
204    /// [`parse_headers`]: RequestParser::parse_headers
205    pub fn parse<'a, H, K, V>(&self, headers: H, body: &[u8]) -> Result<Event>
206    where
207        H: IntoIterator<Item = (&'a K, &'a V)>,
208        K: AsRef<[u8]> + ?Sized + 'static,
209        V: AsRef<[u8]> + ?Sized + 'static,
210    {
211        let kind = self.parse_headers(headers)?;
212        let body = from_utf8(body).map_err(Error::read_body_failed)?;
213        parse_body(kind, body)
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use crate::macros::test_parse_payload;
221
222    use ::http::header::HeaderMap;
223    use ::http::header::CONTENT_TYPE;
224
225    #[test]
226    fn request_parser_new() {
227        let verification_token = "verification_token";
228        let parser = RequestParser::new(verification_token);
229        println!("{parser:?}");
230    }
231
232    #[test]
233    fn parse_failure() {
234        use crate::test_utils::make_parser;
235        let parser = make_parser();
236        let mut headers = HeaderMap::new();
237        assert_eq!(
238            parser.parse(&headers, b"").map_err(|e| e.kind()),
239            Err(ErrorKind::ContentTypeNotFound)
240        );
241        headers.insert(CONTENT_TYPE, "text/plain".parse().unwrap());
242        assert_eq!(
243            parser.parse(&headers, b"").map_err(|e| e.kind()),
244            Err(ErrorKind::ContentTypeMismatch)
245        );
246        headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
247        assert_eq!(
248            parser.parse(&headers, b"").map_err(|e| e.kind()),
249            Err(ErrorKind::BotTokenNotFound)
250        );
251        headers.insert("X-TRAQ-BOT-TOKEN", "invalid token".parse().unwrap());
252        assert_eq!(
253            parser.parse(&headers, b"").map_err(|e| e.kind()),
254            Err(ErrorKind::ReadBotTokenFailed)
255        );
256        headers.insert("X-TRAQ-BOT-TOKEN", "invalid_token".parse().unwrap());
257        assert_eq!(
258            parser.parse(&headers, b"").map_err(|e| e.kind()),
259            Err(ErrorKind::BotTokenMismatch)
260        );
261        headers.insert(
262            "X-TRAQ-BOT-TOKEN",
263            "traqbotverificationtoken".parse().unwrap(),
264        );
265        assert_eq!(
266            parser.parse(&headers, b"").map_err(|e| e.kind()),
267            Err(ErrorKind::BotEventNotFound)
268        );
269        headers.insert("X-TRAQ-BOT-EVENT", "invalid event".parse().unwrap());
270        assert_eq!(
271            parser.parse(&headers, b"").map_err(|e| e.kind()),
272            Err(ErrorKind::ReadBotEventFailed)
273        );
274        headers.insert("X-TRAQ-BOT-EVENT", "invalid_event".parse().unwrap());
275        assert_eq!(
276            parser.parse(&headers, b"").map_err(|e| e.kind()),
277            Err(ErrorKind::BotEventMismatch)
278        );
279        headers.insert("X-TRAQ-BOT-EVENT", "PING".parse().unwrap());
280        assert_eq!(
281            parser
282                .parse(&headers, &[0, 159, 146, 150])
283                .map_err(|e| e.kind()),
284            Err(ErrorKind::ReadBodyFailed)
285        );
286        assert_eq!(
287            parser.parse(&headers, b"").map_err(|e| e.kind()),
288            Err(ErrorKind::ParseBodyFailed)
289        );
290    }
291
292    test_parse_payload! {"system", Ping}
293
294    test_parse_payload! {"system", Joined}
295
296    test_parse_payload! {"system", Left}
297
298    test_parse_payload! {"message", MessageCreated}
299
300    test_parse_payload! {"message", MessageDeleted}
301
302    test_parse_payload! {"message", MessageUpdated}
303
304    test_parse_payload! {"message", DirectMessageCreated}
305
306    test_parse_payload! {"message", DirectMessageDeleted}
307
308    test_parse_payload! {"message", DirectMessageUpdated}
309
310    test_parse_payload! {"message", BotMessageStampsUpdated}
311
312    test_parse_payload! {"channel", ChannelCreated}
313
314    test_parse_payload! {"channel", ChannelTopicChanged}
315
316    test_parse_payload! {"user", UserCreated}
317
318    test_parse_payload! {"stamp", StampCreated}
319
320    test_parse_payload! {"tag", TagAdded}
321
322    test_parse_payload! {"tag", TagRemoved}
323
324    test_parse_payload! {"user-group", UserGroupCreated}
325
326    test_parse_payload! {"user-group", UserGroupUpdated}
327
328    test_parse_payload! {"user-group", UserGroupDeleted}
329
330    test_parse_payload! {"user-group", UserGroupMemberAdded}
331
332    test_parse_payload! {"user-group", UserGroupMemberUpdated}
333
334    test_parse_payload! {"user-group", UserGroupMemberRemoved}
335
336    test_parse_payload! {"user-group", UserGroupAdminAdded}
337
338    test_parse_payload! {"user-group", UserGroupAdminRemoved}
339}