1use std::{error::Error, fmt};
4
5use serde::{
6 de::{value::Error as SerdeError, DeserializeOwned},
7 Deserialize,
8};
9
10use crate::Body;
11
12#[derive(Debug)]
16pub enum PayloadError {
17 Json(serde_json::Error),
19 WwwFormUrlEncoded(SerdeError),
21}
22
23#[derive(Debug)]
25pub enum JsonPayloadError {
26 Parsing(serde_json::Error),
28}
29
30#[derive(Debug)]
32pub enum FormUrlEncodedPayloadError {
33 Parsing(SerdeError),
35}
36
37impl From<JsonPayloadError> for PayloadError {
38 fn from(err: JsonPayloadError) -> Self {
39 match err {
40 JsonPayloadError::Parsing(inner_err) => PayloadError::Json(inner_err),
41 }
42 }
43}
44
45impl From<FormUrlEncodedPayloadError> for PayloadError {
46 fn from(err: FormUrlEncodedPayloadError) -> Self {
47 match err {
48 FormUrlEncodedPayloadError::Parsing(inner_err) => PayloadError::WwwFormUrlEncoded(inner_err),
49 }
50 }
51}
52
53impl fmt::Display for PayloadError {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 PayloadError::Json(json) => writeln!(f, "failed to parse payload from application/json {json}"),
57 PayloadError::WwwFormUrlEncoded(form) => writeln!(
58 f,
59 "failed to parse payload from application/x-www-form-urlencoded {form}"
60 ),
61 }
62 }
63}
64
65impl Error for PayloadError {
66 fn source(&self) -> Option<&(dyn Error + 'static)> {
67 match self {
68 PayloadError::Json(json) => Some(json),
69 PayloadError::WwwFormUrlEncoded(form) => Some(form),
70 }
71 }
72}
73
74pub trait RequestPayloadExt {
76 fn payload<D>(&self) -> Result<Option<D>, PayloadError>
135 where
136 D: DeserializeOwned;
137
138 fn json<D>(&self) -> Result<Option<D>, JsonPayloadError>
193 where
194 D: DeserializeOwned;
195
196 fn form_url_encoded<D>(&self) -> Result<Option<D>, FormUrlEncodedPayloadError>
222 where
223 D: DeserializeOwned;
224}
225
226impl RequestPayloadExt for http::Request<Body> {
227 fn payload<D>(&self) -> Result<Option<D>, PayloadError>
228 where
229 for<'de> D: Deserialize<'de>,
230 {
231 self.headers()
232 .get(http::header::CONTENT_TYPE)
233 .map(|ct| match ct.to_str() {
234 Ok(content_type) => {
235 if content_type.starts_with("application/x-www-form-urlencoded") {
236 return self.form_url_encoded().map_err(PayloadError::from);
237 } else if content_type.starts_with("application/json") {
238 return self.json().map_err(PayloadError::from);
239 }
240 Ok(None)
241 }
242 _ => Ok(None),
243 })
244 .unwrap_or_else(|| Ok(None))
245 }
246
247 fn json<D>(&self) -> Result<Option<D>, JsonPayloadError>
248 where
249 D: DeserializeOwned,
250 {
251 if self.body().is_empty() {
252 return Ok(None);
253 }
254 serde_json::from_slice::<D>(self.body().as_ref())
255 .map(Some)
256 .map_err(JsonPayloadError::Parsing)
257 }
258
259 fn form_url_encoded<D>(&self) -> Result<Option<D>, FormUrlEncodedPayloadError>
260 where
261 D: DeserializeOwned,
262 {
263 if self.body().is_empty() {
264 return Ok(None);
265 }
266 serde_urlencoded::from_bytes::<D>(self.body().as_ref())
267 .map(Some)
268 .map_err(FormUrlEncodedPayloadError::Parsing)
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use serde::Deserialize;
275
276 use super::{FormUrlEncodedPayloadError, JsonPayloadError, RequestPayloadExt};
277
278 use crate::Body;
279
280 #[derive(Deserialize, Eq, PartialEq, Debug)]
281 struct Payload {
282 foo: String,
283 baz: usize,
284 }
285
286 fn get_test_payload_as_json_body() -> Body {
287 Body::from(r#"{"foo":"bar", "baz": 2}"#)
288 }
289
290 fn assert_eq_test_payload(payload: Option<Payload>) {
291 assert_eq!(
292 payload,
293 Some(Payload {
294 foo: "bar".into(),
295 baz: 2
296 })
297 );
298 }
299
300 #[test]
301 fn requests_have_form_post_parsable_payloads() {
302 let request = http::Request::builder()
303 .header("Content-Type", "application/x-www-form-urlencoded")
304 .body(Body::from("foo=bar&baz=2"))
305 .expect("failed to build request");
306 let payload: Option<Payload> = request.payload().unwrap_or_default();
307 assert_eq!(
308 payload,
309 Some(Payload {
310 foo: "bar".into(),
311 baz: 2
312 })
313 );
314 }
315
316 #[test]
317 fn requests_have_json_parsable_payloads() {
318 let request = http::Request::builder()
319 .header("Content-Type", "application/json")
320 .body(get_test_payload_as_json_body())
321 .expect("failed to build request");
322 let payload: Option<Payload> = request.payload().unwrap_or_default();
323 assert_eq_test_payload(payload)
324 }
325
326 #[test]
327 fn requests_match_form_post_content_type_with_charset() {
328 let request = http::Request::builder()
329 .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
330 .body(Body::from("foo=bar&baz=2"))
331 .expect("failed to build request");
332 let payload: Option<Payload> = request.payload().unwrap_or_default();
333 assert_eq!(
334 payload,
335 Some(Payload {
336 foo: "bar".into(),
337 baz: 2
338 })
339 );
340 }
341
342 #[test]
343 fn requests_match_json_content_type_with_charset() {
344 let request = http::Request::builder()
345 .header("Content-Type", "application/json; charset=UTF-8")
346 .body(Body::from(r#"{"foo":"bar", "baz": 2}"#))
347 .expect("failed to build request");
348 let payload: Option<Payload> = request.payload().unwrap_or_default();
349 assert_eq!(
350 payload,
351 Some(Payload {
352 foo: "bar".into(),
353 baz: 2
354 })
355 );
356 }
357
358 #[test]
359 fn requests_omitting_content_types_do_not_support_parsable_payloads() {
360 let request = http::Request::builder()
361 .body(Body::from(r#"{"foo":"bar", "baz": 2}"#))
362 .expect("failed to build request");
363 let payload: Option<Payload> = request.payload().unwrap_or_default();
364 assert_eq!(payload, None);
365 }
366
367 #[test]
368 fn requests_omitting_body_returns_none() {
369 let request = http::Request::builder()
370 .body(Body::Empty)
371 .expect("failed to build request");
372 let payload: Option<String> = request.payload().unwrap();
373 assert_eq!(payload, None)
374 }
375
376 #[test]
377 fn requests_with_json_content_type_hdr_omitting_body_returns_none() {
378 let request = http::Request::builder()
379 .header("Content-Type", "application/json; charset=UTF-8")
380 .body(Body::Empty)
381 .expect("failed to build request");
382 let payload: Option<String> = request.payload().unwrap();
383 assert_eq!(payload, None)
384 }
385
386 #[test]
387 fn requests_with_formurlencoded_content_type_hdr_omitting_body_returns_none() {
388 let request = http::Request::builder()
389 .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
390 .body(Body::Empty)
391 .expect("failed to build request");
392 let payload: Option<String> = request.payload().unwrap();
393 assert_eq!(payload, None)
394 }
395
396 #[derive(Deserialize, Eq, PartialEq, Debug)]
397 struct Person {
398 name: String,
399 age: u8,
400 }
401
402 #[test]
403 fn json_fn_parses_json_strings() {
404 let req = http::Request::builder()
405 .body(Body::from("\"I am a JSON string\""))
406 .expect("failed to build request");
407 match req.json::<String>() {
408 Ok(Some(json)) => assert_eq!(json, "I am a JSON string"),
409 Ok(None) => panic!("payload is missing."),
410 Err(err) => panic!("error processing json: {err:?}"),
411 }
412 }
413
414 #[test]
415 fn json_fn_parses_objects() {
416 let req = http::Request::builder()
417 .body(Body::from(r#"{"name": "Adam", "age": 23}"#))
418 .expect("failed to build request");
419
420 match req.json::<Person>() {
421 Ok(Some(person)) => assert_eq!(
422 person,
423 Person {
424 name: "Adam".to_string(),
425 age: 23
426 }
427 ),
428 Ok(None) => panic!("request data missing"),
429 Err(JsonPayloadError::Parsing(err)) => {
430 if err.is_data() {
431 panic!("payload does not match Person: {err:?}")
432 }
433 if err.is_syntax() {
434 panic!("invalid json: {err:?}")
435 }
436 panic!("failed to parse json: {err:?}")
437 }
438 }
439 }
440
441 #[test]
442 fn json_fn_parses_list_of_objects() {
443 let req = http::Request::builder()
444 .body(Body::from(
445 r#"[{"name": "Adam", "age": 23}, {"name": "Sarah", "age": 47}]"#,
446 ))
447 .expect("failed to build request");
448 let expected_result = vec![
449 Person {
450 name: "Adam".to_string(),
451 age: 23,
452 },
453 Person {
454 name: "Sarah".to_string(),
455 age: 47,
456 },
457 ];
458 let result: Vec<Person> = req.json().expect("invalid payload").expect("missing payload");
459 assert_eq!(result, expected_result);
460 }
461
462 #[test]
463 fn json_fn_parses_nested_objects() {
464 #[derive(Deserialize, Eq, PartialEq, Debug)]
465 struct Pet {
466 name: String,
467 owner: Person,
468 }
469
470 let req = http::Request::builder()
471 .body(Body::from(
472 r#"{"name": "Gumball", "owner": {"name": "Adam", "age": 23}}"#,
473 ))
474 .expect("failed to build request");
475
476 let expected_result = Pet {
477 name: "Gumball".to_string(),
478 owner: Person {
479 name: "Adam".to_string(),
480 age: 23,
481 },
482 };
483 let result: Pet = req.json().expect("invalid payload").expect("missing payload");
484 assert_eq!(result, expected_result);
485 }
486
487 #[test]
488 fn json_fn_accepts_request_with_content_type_header() {
489 let request = http::Request::builder()
490 .header("Content-Type", "application/json")
491 .body(get_test_payload_as_json_body())
492 .expect("failed to build request");
493 let payload: Option<Payload> = request.json().unwrap();
494 assert_eq_test_payload(payload)
495 }
496
497 #[test]
498 fn json_fn_accepts_request_without_content_type_header() {
499 let request = http::Request::builder()
500 .body(get_test_payload_as_json_body())
501 .expect("failed to build request");
502 let payload: Option<Payload> = request.json().expect("failed to parse json");
503 assert_eq_test_payload(payload)
504 }
505
506 #[test]
507 fn json_fn_given_nonjson_payload_returns_syntax_error() {
508 let request = http::Request::builder()
509 .body(Body::Text(String::from("Not a JSON")))
510 .expect("failed to build request");
511 let payload = request.json::<String>();
512 assert!(payload.is_err());
513
514 if let Err(JsonPayloadError::Parsing(err)) = payload {
515 assert!(err.is_syntax())
516 } else {
517 panic!(
518 "{}",
519 format!("payload should have caused a parsing error. instead, it was {payload:?}")
520 );
521 }
522 }
523
524 #[test]
525 fn json_fn_given_unexpected_payload_shape_returns_data_error() {
526 let request = http::Request::builder()
527 .body(Body::from(r#"{"foo":"bar", "baz": "!SHOULD BE A NUMBER!"}"#))
528 .expect("failed to build request");
529 let result = request.json::<Payload>();
530
531 if let Err(JsonPayloadError::Parsing(err)) = result {
532 assert!(err.is_data())
533 } else {
534 panic!(
535 "{}",
536 format!("payload should have caused a parsing error. instead, it was {result:?}")
537 );
538 }
539 }
540
541 #[test]
542 fn json_fn_given_empty_payload_returns_none() {
543 let empty_request = http::Request::default();
544 let payload: Option<Payload> = empty_request.json().expect("failed to parse json");
545 assert_eq!(payload, None)
546 }
547
548 #[test]
549 fn form_url_encoded_fn_parses_forms() {
550 let req = http::Request::builder()
551 .body(Body::from("name=Adam&age=23"))
552 .expect("failed to build request");
553 match req.form_url_encoded::<Person>() {
554 Ok(Some(person)) => assert_eq!(
555 person,
556 Person {
557 name: "Adam".to_string(),
558 age: 23
559 }
560 ),
561 Ok(None) => panic!("payload is missing."),
562 Err(err) => panic!("error processing payload: {err:?}"),
563 }
564 }
565
566 #[test]
567 fn form_url_encoded_fn_accepts_request_with_content_type_header() {
568 let request = http::Request::builder()
569 .header("Content-Type", "application/x-www-form-urlencoded")
570 .body(Body::from("foo=bar&baz=2"))
571 .expect("failed to build request");
572 let payload: Option<Payload> = request.form_url_encoded().unwrap();
573 assert_eq_test_payload(payload);
574 }
575
576 #[test]
577 fn form_url_encoded_fn_accepts_request_without_content_type_header() {
578 let request = http::Request::builder()
579 .body(Body::from("foo=bar&baz=2"))
580 .expect("failed to build request");
581 let payload: Option<Payload> = request.form_url_encoded().expect("failed to parse form");
582 assert_eq_test_payload(payload);
583 }
584
585 #[test]
586 fn form_url_encoded_fn_given_non_form_urlencoded_payload_errors() {
587 let request = http::Request::builder()
588 .body(Body::Text(String::from("Not a url-encoded form")))
589 .expect("failed to build request");
590 let payload = request.form_url_encoded::<String>();
591 assert!(payload.is_err());
592 assert!(matches!(payload, Err(FormUrlEncodedPayloadError::Parsing(_))));
593 }
594
595 #[test]
596 fn form_url_encoded_fn_given_unexpected_payload_shape_errors() {
597 let request = http::Request::builder()
598 .body(Body::from("foo=bar&baz=SHOULD_BE_A_NUMBER"))
599 .expect("failed to build request");
600 let result = request.form_url_encoded::<Payload>();
601 assert!(result.is_err());
602 assert!(matches!(result, Err(FormUrlEncodedPayloadError::Parsing(_))));
603 }
604
605 #[test]
606 fn form_url_encoded_fn_given_empty_payload_returns_none() {
607 let empty_request = http::Request::default();
608 let payload: Option<Payload> = empty_request.form_url_encoded().expect("failed to parse form");
609 assert_eq!(payload, None);
610 }
611}