Skip to content

Commit b83191d

Browse files
authored
Merge pull request #239 from H1rono/parse-request-future
2 parents 6a5d602 + 57af6dd commit b83191d

File tree

6 files changed

+244
-85
lines changed

6 files changed

+244
-85
lines changed

.cspell-dict/lib-words.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ TRAQ
55
chrono
66
eprintln
77
serde
8+
combinators
89

910
HTAB
1011
VCHAR

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+7-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ version = "0.4"
4141
optional = true
4242
features = ["serde"]
4343

44+
[dependencies.bytes]
45+
version = "1"
46+
features = []
47+
optional = true
48+
4449
[dependencies.http]
4550
version = "1"
4651
features = []
@@ -85,5 +90,5 @@ futures = { version = "0.3", features = ["executor"] }
8590
uuid = ["dep:uuid"]
8691
time = ["dep:time"]
8792
chrono = ["dep:chrono"]
88-
http = ["dep:http", "dep:http-body", "dep:http-body-util"]
89-
tower = ["http", "dep:tower", "dep:tower-http", "dep:futures", "dep:pin-project-lite"]
93+
http = ["dep:bytes", "dep:http", "dep:http-body", "dep:http-body-util", "dep:pin-project-lite", "dep:futures"]
94+
tower = ["http", "dep:tower", "dep:tower-http"]

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
mod error;
1313
mod events;
1414
pub(crate) mod macros;
15-
mod parser;
15+
pub mod parser;
1616
pub mod payloads;
1717

1818
#[cfg(feature = "tower")]

src/parser.rs

+23-82
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,31 @@
22
33
use std::str::from_utf8;
44

5-
use serde::Deserialize;
6-
75
use crate::error::{Error, ErrorKind, Result};
86
use crate::macros::all_events;
97
use crate::{Event, EventKind, RequestParser};
108

9+
#[cfg(feature = "http")]
10+
mod http;
11+
12+
#[cfg(feature = "http")]
13+
pub use self::http::ParseRequest;
14+
1115
/// ボディをDeserializeして`Event`に渡す
12-
fn parse_body<'a, T, F>(f: F, body: &'a str) -> Result<Event>
13-
where
14-
T: Deserialize<'a>,
15-
F: Fn(T) -> Event,
16-
{
17-
serde_json::from_str(body)
18-
.map(f)
19-
.map_err(Error::parse_body_failed)
16+
pub(crate) fn parse_body(kind: EventKind, body: &str) -> Result<Event> {
17+
macro_rules! match_kind_parse_body {
18+
($( $k:ident ),*) => {
19+
match kind {
20+
$(
21+
EventKind::$k => {
22+
::serde_json::from_str(body).map(Event::$k)
23+
},
24+
)*
25+
}
26+
};
27+
}
28+
29+
all_events!(match_kind_parse_body).map_err(Error::parse_body_failed)
2030
}
2131

2232
// https://datatracker.ietf.org/doc/html/rfc9110#section-5.5
@@ -186,76 +196,7 @@ impl RequestParser {
186196
{
187197
let kind = self.parse_headers(headers)?;
188198
let body = from_utf8(body).map_err(Error::read_body_failed)?;
189-
190-
macro_rules! match_kind_parse_body {
191-
($( $k:ident ),*) => {
192-
match kind {
193-
$(
194-
EventKind::$k => parse_body(Event::$k, body),
195-
)*
196-
}
197-
};
198-
}
199-
200-
all_events!(match_kind_parse_body)
201-
}
202-
}
203-
204-
#[cfg(feature = "http")]
205-
impl RequestParser {
206-
/// [`http::Request`]をパースします。
207-
///
208-
/// **Note**: この関数は`http`featureが有効になっている時のみ有効です。
209-
///
210-
/// ## Arguments
211-
/// * `request`: リクエスト全体
212-
///
213-
/// ## Example
214-
/// ```
215-
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
216-
/// # let res: Result<(), Box<dyn std::error::Error>> = futures::executor::block_on(async {
217-
/// use traq_bot_http::{EventKind, RequestParser};
218-
///
219-
/// let verification_token = "verification_token";
220-
/// let body = r#"{"eventTime": "2019-05-07T04:50:48.582586882Z"}"#.to_string();
221-
/// let request = http::Request::builder()
222-
/// .method(http::Method::POST)
223-
/// .header(http::header::CONTENT_TYPE, "application/json")
224-
/// .header("X-TRAQ-BOT-TOKEN", verification_token)
225-
/// .header("X-TRAQ-BOT-EVENT", "PING")
226-
/// .body(body)?;
227-
/// let parser = RequestParser::new(verification_token);
228-
/// let event = parser.parse_request(request).await?;
229-
/// assert_eq!(event.kind(), EventKind::Ping);
230-
/// # Ok(())
231-
/// # });
232-
/// # res
233-
/// # }
234-
/// ```
235-
///
236-
/// ## Errors
237-
/// [`Error`]のうち、[`Error::kind`]が以下のものを返す可能性があります。
238-
///
239-
/// - [`parse`]で返されるもの
240-
/// - [`ErrorKind::ReadBodyFailed`] :
241-
/// リクエストボディの読み込みに失敗した
242-
///
243-
/// [`Error::kind`]: crate::Error::kind
244-
/// [`parse`]: RequestParser::parse
245-
pub async fn parse_request<B>(&self, request: http::Request<B>) -> Result<Event>
246-
where
247-
B: http_body::Body,
248-
B::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
249-
{
250-
use http_body_util::BodyExt;
251-
252-
let (parts, body) = request.into_parts();
253-
let body = body
254-
.collect()
255-
.await
256-
.map_err(Error::read_body_failed)?
257-
.to_bytes();
258-
self.parse(&parts.headers, &body)
199+
parse_body(kind, body)
259200
}
260201
}
261202

@@ -264,8 +205,8 @@ mod tests {
264205
use super::*;
265206
use crate::macros::test_parse_payload;
266207

267-
use http::header::HeaderMap;
268-
use http::header::CONTENT_TYPE;
208+
use ::http::header::HeaderMap;
209+
use ::http::header::CONTENT_TYPE;
269210

270211
#[test]
271212
fn request_parser_new() {

src/parser/http.rs

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// #![cfg(feature = "http")]
2+
3+
use std::future::Future;
4+
use std::pin::Pin;
5+
use std::task::{Context, Poll};
6+
7+
use bytes::Bytes;
8+
use futures::future::Ready;
9+
use futures::ready;
10+
use http_body::Body;
11+
use http_body_util::{combinators::Collect, Collected};
12+
13+
use crate::error::{Error, Result};
14+
use crate::events::{Event, EventKind};
15+
use crate::parser::RequestParser;
16+
17+
pin_project_lite::pin_project! {
18+
#[must_use]
19+
#[project = CollectBodyProject]
20+
struct CollectBody<B>
21+
where
22+
B: Body,
23+
B: ?Sized,
24+
{
25+
#[pin]
26+
collect: Collect<B>,
27+
}
28+
}
29+
30+
impl<B> Future for CollectBody<B>
31+
where
32+
B: Body + ?Sized,
33+
B::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
34+
{
35+
type Output = Result<Bytes>;
36+
37+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
38+
let s = self.project();
39+
let collected = ready!(s.collect.poll(cx));
40+
let res = collected
41+
.map(Collected::to_bytes)
42+
.map_err(Error::read_body_failed);
43+
Poll::Ready(res)
44+
}
45+
}
46+
47+
pin_project_lite::pin_project! {
48+
#[must_use]
49+
#[project = ParseRequestInnerProject]
50+
struct ParseRequestInner<K, B> {
51+
#[pin]
52+
kind: K,
53+
#[pin]
54+
body: B,
55+
}
56+
}
57+
58+
impl<K, B> Future for ParseRequestInner<K, B>
59+
where
60+
K: Future<Output = Result<EventKind>>,
61+
B: Future<Output = Result<Bytes>>,
62+
{
63+
type Output = Result<Event>;
64+
65+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
66+
let s = self.project();
67+
let kind = match ready!(s.kind.poll(cx)) {
68+
Ok(k) => k,
69+
Err(e) => return Poll::Ready(Err(e)),
70+
};
71+
let body = ready!(s.body.poll(cx));
72+
let res: Result<Event> = {
73+
let body = body?;
74+
let body = std::str::from_utf8(&body).map_err(Error::read_body_failed)?;
75+
super::parse_body(kind, body)
76+
};
77+
Poll::Ready(res)
78+
}
79+
}
80+
81+
pin_project_lite::pin_project! {
82+
/// <code>impl [Future]<Output = Result<[Event], [Error]>></code>
83+
///
84+
/// [Future]: std::future::Future
85+
/// [Event]: crate::Event
86+
/// [Error]: crate::Error
87+
#[must_use]
88+
#[project = ParseRequestProject]
89+
pub struct ParseRequest<B>
90+
where
91+
B: Body,
92+
{
93+
#[pin]
94+
inner: ParseRequestInner<Ready<Result<EventKind>>, CollectBody<B>>
95+
}
96+
}
97+
98+
impl<B> ParseRequest<B>
99+
where
100+
B: Body,
101+
{
102+
fn new(kind: Result<EventKind>, body: B) -> Self {
103+
use http_body_util::BodyExt;
104+
105+
let kind = futures::future::ready(kind);
106+
let inner = ParseRequestInner {
107+
kind,
108+
body: CollectBody {
109+
collect: body.collect(),
110+
},
111+
};
112+
Self { inner }
113+
}
114+
}
115+
116+
impl<B> Future for ParseRequest<B>
117+
where
118+
B: Body,
119+
B::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
120+
{
121+
type Output = Result<Event>;
122+
123+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
124+
let s = self.project();
125+
s.inner.poll(cx)
126+
}
127+
}
128+
129+
impl RequestParser {
130+
/// [`http::Request`]をパースします。
131+
///
132+
/// **Note**: この関数は`http`featureが有効になっている時のみ有効です。
133+
///
134+
/// # Arguments
135+
///
136+
/// * `request`: リクエスト全体
137+
///
138+
/// # Example
139+
///
140+
/// ```
141+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
142+
/// # let res: Result<(), Box<dyn std::error::Error>> = futures::executor::block_on(async {
143+
/// use traq_bot_http::{EventKind, RequestParser};
144+
///
145+
/// let verification_token = "verification_token";
146+
/// let body = r#"{"eventTime": "2019-05-07T04:50:48.582586882Z"}"#.to_string();
147+
/// let request = http::Request::builder()
148+
/// .method(http::Method::POST)
149+
/// .header(http::header::CONTENT_TYPE, "application/json")
150+
/// .header("X-TRAQ-BOT-TOKEN", verification_token)
151+
/// .header("X-TRAQ-BOT-EVENT", "PING")
152+
/// .body(body)?;
153+
/// let parser = RequestParser::new(verification_token);
154+
/// let event = parser.parse_request(request).await?;
155+
/// assert_eq!(event.kind(), EventKind::Ping);
156+
/// # Ok(())
157+
/// # });
158+
/// # res
159+
/// # }
160+
/// ```
161+
///
162+
/// # Errors
163+
///
164+
/// [`Error`]のうち、[`Error::kind`]が以下のものを返す可能性があります。
165+
///
166+
/// - [`parse`]で返されるもの
167+
/// - [`ErrorKind::ReadBodyFailed`] :
168+
/// リクエストボディの読み込みに失敗した
169+
///
170+
/// [`Error::kind`]: crate::Error::kind
171+
/// [`parse`]: crate::RequestParser::parse
172+
/// [`ErrorKind::ReadBodyFailed`]: crate::ErrorKind::ReadBodyFailed
173+
pub fn parse_request<B>(&self, request: http::Request<B>) -> ParseRequest<B>
174+
where
175+
B: Body,
176+
B::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
177+
{
178+
let (parts, body) = request.into_parts();
179+
let kind = self.parse_headers(&parts.headers);
180+
ParseRequest::new(kind, body)
181+
}
182+
}
183+
184+
#[cfg(test)]
185+
mod tests {
186+
use futures::executor::block_on;
187+
use http_body_util::BodyExt;
188+
189+
use super::{CollectBody, ParseRequest};
190+
use crate::{Event, EventKind};
191+
192+
#[test]
193+
fn collect_body() {
194+
let body_content = "some content";
195+
let fut = CollectBody {
196+
collect: body_content.to_string().collect(),
197+
};
198+
let collected = block_on(fut).unwrap();
199+
assert_eq!(collected, body_content.as_bytes());
200+
}
201+
202+
#[test]
203+
fn parse_request_future() {
204+
let kind = EventKind::Ping;
205+
let payload = r#"{"eventTime": "2019-05-07T04:50:48.582586882Z"}"#;
206+
let body = payload.to_string();
207+
let fut = ParseRequest::new(Ok(kind), body);
208+
let event = block_on(fut).unwrap();
209+
assert!(matches!(event, Event::Ping(_)));
210+
}
211+
}

0 commit comments

Comments
 (0)