by_loco/controller/middleware/
format.rs1use axum::{
3 extract::FromRequestParts,
4 http::{
5 header::{HeaderMap, ACCEPT, CONTENT_TYPE},
6 request::Parts,
7 },
8};
9use serde::{Deserialize, Serialize};
10
11use crate::Error;
12
13#[derive(Debug, Deserialize, Serialize)]
14pub struct Format(pub RespondTo);
15
16#[derive(Debug, Deserialize, Serialize)]
17pub enum RespondTo {
18 None,
19 Html,
20 Json,
21 Xml,
22 Other(String),
23}
24
25fn detect_format(content_type: &str) -> RespondTo {
26 if content_type.starts_with("application/json") {
27 RespondTo::Json
28 } else if content_type.starts_with("text/html") {
29 RespondTo::Html
30 } else if content_type.starts_with("text/xml")
31 || content_type.starts_with("application/xml")
32 || content_type.starts_with("application/xhtml")
33 {
34 RespondTo::Xml
35 } else {
36 RespondTo::Other(content_type.to_string())
37 }
38}
39
40pub fn get_respond_to(headers: &HeaderMap) -> RespondTo {
41 #[allow(clippy::option_if_let_else)]
42 if let Some(content_type) = headers.get(CONTENT_TYPE).and_then(|h| h.to_str().ok()) {
43 detect_format(content_type)
44 } else if let Some(content_type) = headers.get(ACCEPT).and_then(|h| h.to_str().ok()) {
45 detect_format(content_type)
46 } else {
47 RespondTo::None
48 }
49}
50
51impl<S> FromRequestParts<S> for Format
52where
53 S: Send + Sync,
54{
55 type Rejection = Error;
56
57 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Error> {
58 Ok(Self(get_respond_to(&parts.headers)))
59 }
60}
61
62impl<S> FromRequestParts<S> for RespondTo
63where
64 S: Send + Sync,
65{
66 type Rejection = Error;
67
68 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Error> {
69 Ok(get_respond_to(&parts.headers))
70 }
71}