by_loco/controller/middleware/
format.rs

1//! Detect a content type and format and responds accordingly
2use 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}