1use std::sync::Arc;
22
23use rainier_support::{BoxedFuture, Error, Result};
24use serde::de::DeserializeOwned;
25
26use crate::coerce;
27use crate::request::Request;
28use crate::upload::UploadedFile;
29
30pub trait FromRequest: Sized + Send + 'static {
32 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>>;
35}
36
37impl FromRequest for Arc<Request> {
39 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
40 Box::pin(async move { Ok(request) })
41 }
42}
43
44impl<T: FromRequest> FromRequest for Option<T> {
47 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
48 Box::pin(async move { Ok(T::from_request(request).await.ok()) })
49 }
50}
51
52impl<T: FromRequest> FromRequest for Result<T> {
55 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
56 Box::pin(async move { Ok(T::from_request(request).await) })
57 }
58}
59
60#[derive(Debug, Clone, Copy, Default)]
77pub struct Json<T>(pub T);
78
79impl<T: DeserializeOwned + Send + 'static> FromRequest for Json<T> {
80 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
81 Box::pin(async move { request.json::<T>().map(Json) })
82 }
83}
84
85#[derive(Debug, Clone, Copy, Default)]
88pub struct Form<T>(pub T);
89
90impl<T: DeserializeOwned + Send + 'static> FromRequest for Form<T> {
91 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
92 Box::pin(async move {
93 coerce::from_value(&request.all())
94 .map(Form)
95 .map_err(|e| Error::bad_request(format!("invalid form input: {e}")))
96 })
97 }
98}
99
100#[derive(Debug, Clone, Copy, Default)]
102pub struct Query<T>(pub T);
103
104impl<T: DeserializeOwned + Send + 'static> FromRequest for Query<T> {
105 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
106 Box::pin(async move {
107 coerce::from_value(request.query())
108 .map(Query)
109 .map_err(|e| Error::bad_request(format!("invalid query string: {e}")))
110 })
111 }
112}
113
114#[derive(Debug, Clone, Copy, Default)]
129pub struct Path<T>(pub T);
130
131impl<T: DeserializeOwned + Send + 'static> FromRequest for Path<T> {
132 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
133 Box::pin(async move {
134 let params = request.route_params();
135 let as_object = coerce::object_from_strings(params.iter());
136
137 if let Ok(value) = coerce::from_value::<T>(&as_object) {
139 return Ok(Path(value));
140 }
141
142 if params.len() == 1 {
145 let only = params.values().next().expect("just checked len == 1");
146 let scalar = serde_json::Value::String(only.clone());
147 if let Ok(value) = coerce::from_value::<T>(&scalar) {
148 return Ok(Path(value));
149 }
150 }
151
152 Err(Error::bad_request(format!(
153 "could not read the route parameters {:?} as the expected type",
154 params.keys().collect::<Vec<_>>()
155 )))
156 })
157 }
158}
159
160pub trait StaticName: Send + Sync + 'static {
167 const NAME: &'static str;
169}
170
171#[macro_export]
178macro_rules! static_name {
179 ($(#[$meta:meta])* $vis:vis $name:ident, $value:expr) => {
180 $(#[$meta])*
181 #[doc = concat!("A [`StaticName`](", stringify!($crate), "::extract::StaticName) for `", $value, "`.")]
182 #[derive(Debug, Clone, Copy, Default)]
183 $vis struct $name;
184
185 impl $crate::extract::StaticName for $name {
186 const NAME: &'static str = $value;
187 }
188 };
189 ($(#[$meta:meta])* $name:ident, $value:expr) => {
190 $crate::static_name!($(#[$meta])* pub $name, $value);
191 };
192}
193
194#[derive(Debug, Clone)]
208pub struct Header<K: StaticName>(pub String, pub std::marker::PhantomData<K>);
209
210impl<K: StaticName> FromRequest for Header<K> {
211 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
212 Box::pin(async move {
213 request
214 .header(K::NAME)
215 .map(|value| Header(value.to_string(), std::marker::PhantomData))
216 .ok_or_else(|| Error::bad_request(format!("the `{}` header is required", K::NAME)))
217 })
218 }
219}
220
221#[derive(Debug, Clone)]
223pub struct Bearer(pub String);
224
225impl FromRequest for Bearer {
226 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
227 Box::pin(async move {
228 request
229 .bearer_token()
230 .map(|token| Bearer(token.to_string()))
231 .ok_or_else(|| Error::unauthenticated("a bearer token is required"))
232 })
233 }
234}
235
236#[derive(Debug, Clone)]
238pub struct RawBody(pub bytes::Bytes);
239
240impl FromRequest for RawBody {
241 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
242 Box::pin(async move { Ok(RawBody(request.body().clone())) })
243 }
244}
245
246#[derive(Debug, Clone)]
248pub struct Files<K: StaticName>(pub Vec<UploadedFile>, pub std::marker::PhantomData<K>);
249
250impl<K: StaticName> FromRequest for Files<K> {
251 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
252 Box::pin(
253 async move { Ok(Files(request.files(K::NAME).to_vec(), std::marker::PhantomData)) },
254 )
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use http::Method;
262 use serde::Deserialize;
263 use serde_json::json;
264
265 #[derive(Debug, PartialEq, Deserialize)]
266 struct NewPost {
267 title: String,
268 draft: bool,
269 }
270
271 #[derive(Debug, PartialEq, Deserialize)]
272 struct Page {
273 page: u32,
274 }
275
276 fn shared(request: Request) -> Arc<Request> {
277 Arc::new(request)
278 }
279
280 #[tokio::test]
281 async fn json_extracts_the_body() {
282 let request =
283 shared(Request::builder().json(&json!({ "title": "Hi", "draft": true })).build());
284 let Json(post) = Json::<NewPost>::from_request(request).await.unwrap();
285 assert_eq!(post, NewPost { title: "Hi".into(), draft: true });
286 }
287
288 #[tokio::test]
289 async fn json_reports_a_bad_body_as_a_400() {
290 let request = shared(Request::builder().json(&json!({ "title": "Hi" })).build());
291 let err = Json::<NewPost>::from_request(request).await.unwrap_err();
292 assert_eq!(err.status(), 400);
293 }
294
295 #[tokio::test]
296 async fn query_coerces_strings() {
297 let request = shared(Request::builder().uri("/x?page=3").build());
298 let Query(page) = Query::<Page>::from_request(request).await.unwrap();
299 assert_eq!(page, Page { page: 3 });
300 }
301
302 #[tokio::test]
303 async fn form_reads_the_merged_input() {
304 let request = shared(
305 Request::builder()
306 .method(Method::POST)
307 .uri("/x?page=1")
308 .form(&[("title", "Hi"), ("draft", "1")])
309 .build(),
310 );
311 let Form(post) = Form::<NewPost>::from_request(request).await.unwrap();
312 assert_eq!(post, NewPost { title: "Hi".into(), draft: true });
313 }
314
315 #[tokio::test]
316 async fn path_reads_a_single_scalar_parameter() {
317 let request = shared(Request::builder().route_param("post", "42").build());
318 let Path(id) = Path::<u64>::from_request(request).await.unwrap();
319 assert_eq!(id, 42);
320 }
321
322 #[tokio::test]
323 async fn path_reads_a_struct_of_parameters() {
324 #[derive(Debug, PartialEq, Deserialize)]
325 struct Params {
326 post: u64,
327 comment: u64,
328 }
329
330 let request =
331 shared(Request::builder().route_param("post", "1").route_param("comment", "2").build());
332 let Path(params) = Path::<Params>::from_request(request).await.unwrap();
333 assert_eq!(params, Params { post: 1, comment: 2 });
334 }
335
336 #[tokio::test]
337 async fn path_refuses_to_guess_between_two_parameters() {
338 let request =
339 shared(Request::builder().route_param("post", "1").route_param("comment", "2").build());
340 let err = Path::<u64>::from_request(request).await.unwrap_err();
341 assert_eq!(err.status(), 400);
342 }
343
344 crate::static_name!(XRequestId, "x-request-id");
345 crate::static_name!(XMissing, "x-missing");
346 crate::static_name!(DocField, "doc");
347
348 #[tokio::test]
349 async fn header_and_bearer_extractors() {
350 let request = shared(
351 Request::builder()
352 .header("x-request-id", "abc")
353 .header("authorization", "Bearer tok")
354 .build(),
355 );
356
357 let Header(id, ..) =
358 Header::<XRequestId>::from_request(Arc::clone(&request)).await.unwrap();
359 assert_eq!(id, "abc");
360
361 let Bearer(token) = Bearer::from_request(Arc::clone(&request)).await.unwrap();
362 assert_eq!(token, "tok");
363
364 let missing = Header::<XMissing>::from_request(request).await.unwrap_err();
365 assert_eq!(missing.status(), 400);
366 }
367
368 #[tokio::test]
369 async fn a_missing_bearer_token_is_a_401() {
370 let request = shared(Request::builder().build());
371 assert_eq!(Bearer::from_request(request).await.unwrap_err().status(), 401);
372 }
373
374 #[tokio::test]
375 async fn option_swallows_the_failure() {
376 let request = shared(Request::builder().build());
377 let extracted = Option::<Json<NewPost>>::from_request(request).await.unwrap();
378 assert!(extracted.is_none());
379 }
380
381 #[tokio::test]
382 async fn result_hands_the_failure_to_the_handler() {
383 let request = shared(Request::builder().build());
384 let extracted = Result::<Json<NewPost>>::from_request(request).await.unwrap();
385 assert!(extracted.is_err());
386 }
387
388 #[tokio::test]
389 async fn the_whole_request_is_an_extractor() {
390 let request = shared(Request::builder().uri("/here").build());
391 let extracted = Arc::<Request>::from_request(request).await.unwrap();
392 assert_eq!(extracted.path(), "/here");
393 }
394
395 #[tokio::test]
396 async fn raw_body_and_files() {
397 let boundary = "B";
398 let body = format!(
399 "--{boundary}\r\nContent-Disposition: form-data; name=\"doc\"; filename=\"a.txt\"\r\n\r\nDATA\r\n--{boundary}--\r\n"
400 );
401 let request = shared(
402 Request::builder()
403 .method(Method::POST)
404 .header("content-type", &format!("multipart/form-data; boundary={boundary}"))
405 .body(body.clone())
406 .build(),
407 );
408
409 let RawBody(raw) = RawBody::from_request(Arc::clone(&request)).await.unwrap();
410 assert_eq!(raw.len(), body.len());
411
412 let Files(files, ..) = Files::<DocField>::from_request(request).await.unwrap();
413 assert_eq!(files.len(), 1);
414 assert_eq!(files[0].bytes().as_ref(), b"DATA");
415 }
416}