1use crate::dep::DepResolver;
5use crate::error::{Error, Result};
6use crate::response::Json;
7use bytes::Bytes;
8use serde::de::DeserializeOwned;
9use std::future::Future;
10
11pub(crate) type StreamLane =
15 http_body_util::combinators::UnsyncBoxBody<Bytes, Box<dyn std::error::Error + Send + Sync>>;
16
17pub(crate) enum BodyLane {
21 Buffered(Bytes),
22 Stream(Option<StreamLane>),
24}
25
26#[derive(Clone, Copy, Debug)]
30pub struct ClientAddr(pub std::net::SocketAddr);
31
32pub struct RequestCtx {
35 pub(crate) parts: http::request::Parts,
36 pub(crate) body: BodyLane,
37 pub(crate) params: Vec<(String, String)>,
39 pub(crate) deps: DepResolver,
40 pub(crate) is_task: bool,
43}
44
45impl RequestCtx {
46 pub(crate) fn new(parts: http::request::Parts, body: Bytes, deps: DepResolver) -> Self {
50 Self::with_lane(parts, BodyLane::Buffered(body), deps)
51 }
52
53 pub(crate) fn with_lane(
56 parts: http::request::Parts,
57 body: BodyLane,
58 deps: DepResolver,
59 ) -> Self {
60 Self {
61 parts,
62 body,
63 params: Vec::new(),
64 deps,
65 is_task: false,
66 }
67 }
68
69 pub(crate) async fn drain_body(&mut self) -> Result<Bytes> {
73 match &mut self.body {
74 BodyLane::Buffered(bytes) => Ok(bytes.clone()),
75 BodyLane::Stream(slot) => {
76 let stream = slot
80 .take()
81 .ok_or_else(|| Error::internal("request body was already consumed"))?;
82 use http_body_util::BodyExt;
83 let collected = stream.collect().await.map_err(map_stream_error)?;
84 let bytes = collected.to_bytes();
85 self.body = BodyLane::Buffered(bytes.clone());
86 Ok(bytes)
87 }
88 }
89 }
90
91 pub fn method(&self) -> &http::Method {
92 &self.parts.method
93 }
94 pub fn uri(&self) -> &http::Uri {
95 &self.parts.uri
96 }
97 pub fn headers(&self) -> &http::HeaderMap {
98 &self.parts.headers
99 }
100
101 pub fn param(&self, name: &str) -> Option<&str> {
105 self.params
106 .iter()
107 .find(|(k, _)| k == name)
108 .map(|(_, v)| v.as_str())
109 }
110
111 pub fn peer_addr(&self) -> Option<std::net::SocketAddr> {
116 self.parts.extensions.get::<ClientAddr>().map(|c| c.0)
117 }
118
119 pub fn take_extension<T: Send + Sync + 'static>(&mut self) -> Option<T> {
123 self.parts.extensions.remove::<T>()
124 }
125}
126
127pub(crate) fn map_stream_error(e: Box<dyn std::error::Error + Send + Sync>) -> Error {
131 if e.downcast_ref::<http_body_util::LengthLimitError>()
132 .is_some()
133 {
134 return Error::payload_too_large();
135 }
136 if e.downcast_ref::<crate::serve::RecvTimeout>().is_some() {
137 return Error::new(
138 http::StatusCode::REQUEST_TIMEOUT,
139 "JC0408",
140 "timed out reading the request body",
141 );
142 }
143 Error::bad_request("request body failed mid-read")
144}
145
146pub trait FromRequest: Sized + Send {
149 fn from_request(ctx: &mut RequestCtx) -> impl Future<Output = Result<Self>> + Send;
150}
151
152pub struct Path<T>(pub T);
158
159#[doc(hidden)]
163pub mod sealed {
164 pub trait Sealed {}
165}
166
167pub trait PathParam: sealed::Sealed + Sized + Send {
172 fn parse_param(name: &str, raw: &str) -> Result<Self>;
173}
174
175macro_rules! impl_path_param {
176 ($($t:ty),* $(,)?) => {$(
177 impl sealed::Sealed for $t {}
178 impl PathParam for $t {
179 fn parse_param(name: &str, raw: &str) -> Result<Self> {
180 raw.parse::<$t>().map_err(|e| {
181 Error::bad_request(format!("invalid path parameter `{name}`: {e}"))
182 })
183 }
184 }
185 )*};
186}
187
188#[macro_export]
203macro_rules! path_param {
204 ($($t:ty),* $(,)?) => {$(
205 impl $crate::extract::sealed::Sealed for $t {}
206 impl $crate::extract::PathParam for $t {
207 fn parse_param(name: &str, raw: &str) -> $crate::Result<Self> {
208 raw.parse::<$t>().map_err(|e| {
209 $crate::Error::bad_request(format!("invalid path parameter `{name}`: {e}"))
210 })
211 }
212 }
213 )*};
214}
215impl_path_param!(
216 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, bool, char, String,
217);
218
219impl<T: PathParam> FromRequest for Path<T> {
220 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
221 if ctx.is_task {
222 return Err(Error::task_context());
223 }
224 let (name, raw) = ctx
228 .params
229 .last()
230 .ok_or_else(|| Error::internal("route has no path parameters"))?;
231 T::parse_param(name, raw).map(Path)
232 }
233}
234
235impl<A: PathParam, B: PathParam> FromRequest for Path<(A, B)> {
236 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
237 if ctx.is_task {
238 return Err(Error::task_context());
239 }
240 let [a, b] = take_params::<2>(ctx)?;
241 Ok(Path((
242 A::parse_param(&a.0, &a.1)?,
243 B::parse_param(&b.0, &b.1)?,
244 )))
245 }
246}
247
248impl<A: PathParam, B: PathParam, C: PathParam> FromRequest for Path<(A, B, C)> {
249 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
250 if ctx.is_task {
251 return Err(Error::task_context());
252 }
253 let [a, b, c] = take_params::<3>(ctx)?;
254 Ok(Path((
255 A::parse_param(&a.0, &a.1)?,
256 B::parse_param(&b.0, &b.1)?,
257 C::parse_param(&c.0, &c.1)?,
258 )))
259 }
260}
261
262fn take_params<const N: usize>(ctx: &RequestCtx) -> Result<[(String, String); N]> {
265 if ctx.params.len() < N {
266 return Err(Error::internal(format!(
267 "route captures {} path parameter(s) but the handler expects {N}",
268 ctx.params.len()
269 )));
270 }
271 Ok(std::array::from_fn(|i| ctx.params[i].clone()))
272}
273
274pub struct PathParams(Vec<(String, String)>);
283
284impl PathParams {
285 pub fn get(&self, name: &str) -> Option<&str> {
288 self.0
289 .iter()
290 .find(|(k, _)| k == name)
291 .map(|(_, v)| v.as_str())
292 }
293}
294
295impl FromRequest for PathParams {
296 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
297 if ctx.is_task {
298 return Err(Error::task_context());
299 }
300 Ok(PathParams(ctx.params.clone()))
301 }
302}
303
304pub struct Query<T>(pub T);
306
307impl<T: DeserializeOwned + Send> FromRequest for Query<T> {
308 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
309 if ctx.is_task {
310 return Err(Error::task_context());
311 }
312 let q = ctx.parts.uri.query().unwrap_or("");
313 serde_urlencoded::from_str::<T>(q)
314 .map(Query)
315 .map_err(|e| Error::bad_request(format!("invalid query string: {e}")))
316 }
317}
318
319impl<T: DeserializeOwned + Send> FromRequest for Json<T> {
320 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
321 if ctx.is_task {
322 return Err(Error::task_context());
323 }
324 let body = ctx.drain_body().await?;
325 serde_json::from_slice::<T>(&body)
326 .map(Json)
327 .map_err(|e| Error::unprocessable(format!("invalid JSON body: {e}")))
328 }
329}
330
331pub struct Headers(pub(crate) http::HeaderMap);
333
334impl Headers {
335 pub fn get(&self, name: &str) -> Option<&str> {
337 self.0.get(name).and_then(|v| v.to_str().ok())
338 }
339}
340
341impl FromRequest for Headers {
342 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
343 if ctx.is_task {
344 return Err(Error::task_context());
345 }
346 Ok(Headers(ctx.headers().clone()))
347 }
348}
349
350pub struct RawBody(pub Bytes);
355
356impl FromRequest for RawBody {
357 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
358 if ctx.is_task {
359 return Err(Error::task_context());
360 }
361 Ok(RawBody(ctx.drain_body().await?))
362 }
363}
364
365impl<T: FromRequest> FromRequest for Option<T> {
371 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
372 Ok(T::from_request(ctx).await.ok())
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379 use crate::dep::DepEnv;
380 use std::sync::Arc;
381
382 fn ctx(uri: &str, body: &str) -> RequestCtx {
383 let req = http::Request::builder()
384 .method(http::Method::GET)
385 .uri(uri)
386 .body(())
387 .unwrap();
388 let (parts, ()) = req.into_parts();
389 RequestCtx::new(
390 parts,
391 Bytes::from(body.to_string()),
392 DepResolver::new(Arc::new(DepEnv::default()), Default::default()),
393 )
394 }
395
396 #[tokio::test]
397 async fn peer_addr_is_none_without_a_socket_and_readable_when_set() {
398 let mut c = ctx("/x", "");
399 assert!(c.peer_addr().is_none());
400 let addr: std::net::SocketAddr = "203.0.113.7:5000".parse().unwrap();
401 c.parts.extensions.insert(crate::extract::ClientAddr(addr));
402 assert_eq!(c.peer_addr(), Some(addr));
403 }
404
405 #[tokio::test]
406 async fn path_extracts_typed_param() {
407 let mut c = ctx("/todos/42", "");
408 c.params.push(("id".into(), "42".into()));
409 let Path(id): Path<i64> = Path::<i64>::from_request(&mut c).await.unwrap();
410 assert_eq!(id, 42);
411 }
412
413 #[test]
414 fn ctx_param_reads_a_named_captured_param() {
415 let mut c = ctx("/x", "");
420 c.params.push(("club_id".into(), "42".into()));
421 c.params.push(("id".into(), "7".into()));
422 assert_eq!(c.param("club_id"), Some("42"));
423 assert_eq!(c.param("id"), Some("7"));
424 assert_eq!(c.param("missing"), None);
425 }
426
427 #[tokio::test]
428 async fn path_params_reads_named_and_rejects_task() {
429 let mut c = ctx("/x", "");
435 c.params.push(("club_id".into(), "42".into()));
436 c.params.push(("id".into(), "7".into()));
437 let p = PathParams::from_request(&mut c).await.unwrap();
438 assert_eq!(p.get("club_id"), Some("42"));
439 assert_eq!(p.get("id"), Some("7"));
440 assert_eq!(p.get("missing"), None);
441
442 let mut task = ctx("/x", "");
444 task.is_task = true;
445 let err = PathParams::from_request(&mut task).await.err().unwrap();
446 assert_eq!(err.code(), "JC1003");
447 }
448
449 #[tokio::test]
450 async fn path_with_wrong_type_is_400() {
451 let mut c = ctx("/todos/abc", "");
452 c.params.push(("id".into(), "abc".into()));
453 let err = Path::<i64>::from_request(&mut c).await.err().unwrap();
454 assert_eq!(err.code(), "JC0400");
455 }
456
457 #[tokio::test]
458 async fn path_missing_param_is_500() {
459 let mut c = ctx("/todos", "");
462 let err = Path::<i64>::from_request(&mut c).await.err().unwrap();
463 assert_eq!(err.code(), "JC0500");
464 }
465
466 #[tokio::test]
467 async fn query_deserializes_struct() {
468 #[derive(serde::Deserialize)]
469 struct Page {
470 limit: u32,
471 offset: u32,
472 }
473 let mut c = ctx("/todos?limit=10&offset=20", "");
474 let Query(p): Query<Page> = Query::from_request(&mut c).await.unwrap();
475 assert_eq!((p.limit, p.offset), (10, 20));
476 }
477
478 #[tokio::test]
479 async fn option_extractor_yields_none_on_failure_and_some_on_success() {
480 #[derive(serde::Deserialize)]
484 struct P {
485 n: i64,
486 }
487 async fn probe(q: Option<Query<P>>) -> Result<Json<Option<i64>>> {
488 Ok(Json(q.map(|Query(p)| p.n)))
489 }
490 let t = crate::App::new()
491 .route("/probe", crate::get(probe))
492 .into_test();
493 assert_eq!(t.get("/probe?n=7").await.text(), "7");
494 assert_eq!(t.get("/probe").await.text(), "null");
496 assert_eq!(t.get("/probe?n=not-a-number").await.text(), "null");
497 }
498
499 #[tokio::test]
500 async fn single_path_param_binds_the_leaf_segment() {
501 use crate::prelude::*;
502 async fn show(Path(id): Path<i64>) -> Result<Json<i64>> {
503 Ok(Json(id))
504 }
505 let t = App::new()
506 .mount(
507 "/ws/{ws}",
508 Module::new("leads").route("/leads/{id}", get(show)),
509 )
510 .into_test();
511 assert_eq!(
512 t.get("/ws/7/leads/42").await.json::<i64>(),
513 42,
514 "leaf param, not mount param"
515 );
516 }
517
518 #[tokio::test]
519 async fn tuples_still_read_root_to_leaf() {
520 use crate::prelude::*;
521 async fn pair(Path((ws, id)): Path<(i64, i64)>) -> Result<Json<(i64, i64)>> {
522 Ok(Json((ws, id)))
523 }
524 let t = App::new()
525 .mount(
526 "/ws/{ws}",
527 Module::new("leads").route("/leads/{id}", get(pair)),
528 )
529 .into_test();
530 assert_eq!(t.get("/ws/7/leads/42").await.json::<(i64, i64)>(), (7, 42));
531 }
532
533 #[tokio::test]
534 async fn path_param_macro_admits_custom_newtypes() {
535 use crate::prelude::*;
536 #[derive(Debug)]
537 struct LeadId(i64);
538 impl std::str::FromStr for LeadId {
539 type Err = std::num::ParseIntError;
540 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
541 Ok(LeadId(s.parse()?))
542 }
543 }
544 crate::path_param!(LeadId);
545 async fn show(Path(id): Path<LeadId>) -> Result<Json<i64>> {
546 Ok(Json(id.0))
547 }
548 let t = App::new().route("/leads/{id}", get(show)).into_test();
549 assert_eq!(t.get("/leads/42").await.json::<i64>(), 42);
550 }
551
552 #[tokio::test]
553 async fn raw_body_yields_exact_bytes_and_coexists_with_headers() {
554 use crate::prelude::*;
555 async fn verify(headers: Headers, body: RawBody) -> Result<Json<(usize, bool)>> {
556 let signed = headers.get("x-signature").is_some();
557 Ok(Json((body.0.len(), signed)))
558 }
559 let t = App::new().route("/hook", post(verify)).into_test();
560 let res = t
561 .post_bytes_with("/hook", b"{\"raw\": 1}", &[("x-signature", "abc")])
562 .await;
563 assert_eq!(res.status().as_u16(), 200);
564 assert_eq!(res.json::<(usize, bool)>(), (10, true));
565 }
566
567 #[tokio::test]
568 async fn raw_body_drains_a_stream_route_transparently() {
569 use crate::prelude::*;
570 async fn len(body: RawBody) -> Result<Json<usize>> {
571 Ok(Json(body.0.len()))
572 }
573 let t = App::new().route("/up", post(len).stream_body()).into_test();
574 let payload = vec![b'x'; 100]; let res = t.post_bytes("/up", &payload).await;
576 assert_eq!(res.json::<usize>(), 100);
577 }
578
579 #[tokio::test]
580 async fn json_body_deserializes_and_bad_json_is_422() {
581 #[derive(serde::Deserialize)]
582 struct NewTodo {
583 title: String,
584 }
585 let mut c = ctx("/todos", r#"{"title":"x"}"#);
586 let Json(t): Json<NewTodo> = Json::from_request(&mut c).await.unwrap();
587 assert_eq!(t.title, "x");
588
589 let mut bad = ctx("/todos", r#"{"title":"#);
590 let err = Json::<NewTodo>::from_request(&mut bad).await.err().unwrap();
591 assert_eq!(err.code(), "JC0422");
592 }
593
594 fn stream_ctx(body: &[u8], limit: Option<usize>) -> RequestCtx {
599 use http_body_util::BodyExt;
600 use http_body_util::combinators::UnsyncBoxBody;
601 let req = http::Request::builder().uri("/up").body(()).unwrap();
602 let (parts, ()) = req.into_parts();
603 let bytes = Bytes::copy_from_slice(body);
604 let lane: StreamLane = match limit {
605 Some(limit) => {
606 let limited = http_body_util::Limited::new(
607 http_body_util::Full::<Bytes>::new(bytes).map_err(
608 |never| -> Box<dyn std::error::Error + Send + Sync> { match never {} },
609 ),
610 limit,
611 );
612 UnsyncBoxBody::new(limited.map_err(Into::into))
613 }
614 None => {
615 let full = http_body_util::Full::<Bytes>::new(bytes);
616 UnsyncBoxBody::new(full.map_err(
617 |never| -> Box<dyn std::error::Error + Send + Sync> { match never {} },
618 ))
619 }
620 };
621 RequestCtx::with_lane(
622 parts,
623 BodyLane::Stream(Some(lane)),
624 DepResolver::new(Arc::new(DepEnv::default()), Default::default()),
625 )
626 }
627
628 #[tokio::test]
629 async fn stream_routes_deliver_the_body_and_enforce_the_limit() {
630 use crate::prelude::*;
631 async fn echo(Json(v): Json<serde_json::Value>) -> Result<Json<serde_json::Value>> {
632 Ok(Json(v))
633 }
634 let t = App::new()
635 .route("/up", post(echo).stream_body().body_limit(64))
636 .into_test();
637 let res = t.post_json("/up", &serde_json::json!({"k": "v"})).await;
639 assert_eq!(res.status().as_u16(), 200);
640 let big = serde_json::json!({"k": "x".repeat(200)});
642 let res = t.post_json("/up", &big).await;
643 assert_eq!(res.status().as_u16(), 413, "body: {}", res.text());
644 }
645
646 #[tokio::test]
647 async fn drain_body_twice_caches_the_stream_bytes() {
648 use bytes::Bytes;
652 let mut c = stream_ctx(br#"{"k":"v"}"#, None);
653 let first = c.drain_body().await.unwrap();
654 assert_eq!(first, Bytes::from_static(br#"{"k":"v"}"#));
655 let second = c.drain_body().await.unwrap();
656 assert_eq!(second, first, "second drain returns the cached bytes");
657 }
658
659 #[tokio::test]
660 async fn stream_lane_over_limit_maps_to_413() {
661 let mut c = stream_ctx(&[b'x'; 200], Some(64));
664 let err = c.drain_body().await.err().unwrap();
665 assert_eq!(err.code(), "JC0413");
666 }
667
668 #[tokio::test]
669 async fn limit_trips_through_the_timed_recv_wrapper_still_map_to_413() {
670 use crate::serve::TimedRecvBody;
677 use http_body_util::BodyExt;
678 use http_body_util::combinators::UnsyncBoxBody;
679 use std::time::Duration;
680
681 let req = http::Request::builder().uri("/up").body(()).unwrap();
682 let (parts, ()) = req.into_parts();
683 let over_limit_body = http_body_util::Full::<Bytes>::new(Bytes::from_static(&[b'x'; 200]))
684 .map_err(|never| -> Box<dyn std::error::Error + Send + Sync> { match never {} });
685 let lane: StreamLane = UnsyncBoxBody::new(TimedRecvBody::new(
686 http_body_util::Limited::new(over_limit_body, 64),
687 Duration::from_secs(5),
688 ));
689 let mut c = RequestCtx::with_lane(
690 parts,
691 BodyLane::Stream(Some(lane)),
692 DepResolver::new(Arc::new(DepEnv::default()), Default::default()),
693 );
694 let err = c.drain_body().await.err().unwrap();
695 assert_eq!(err.code(), "JC0413");
696 assert_eq!(err.status().as_u16(), 413);
697 }
698}