sylvia_iot_corelib/
http.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use async_trait::async_trait;
use axum::{
    extract::{
        rejection::JsonRejection, FromRequest, FromRequestParts, Json as AxumJson,
        Path as AxumPath, Query as AxumQuery, Request,
    },
    http::{header, request::Parts},
    response::{IntoResponse, Response},
};
use bytes::{BufMut, BytesMut};
use serde::{de::DeserializeOwned, Serialize};

use crate::{constants::ContentType, err::ErrResp};

/// JSON Extractor / Response.
///
/// This is the customized [`axum::extract::Json`] version to respose error with [`ErrResp`].
pub struct Json<T>(pub T);

/// Path Extractor / Response.
///
/// This is the customized [`axum::extract::Path`] version to respose error with [`ErrResp`].
pub struct Path<T>(pub T);

/// Query Extractor / Response.
///
/// This is the customized [`axum::extract::Query`] version to respose error with [`ErrResp`].
pub struct Query<T>(pub T);

#[async_trait]
impl<S, T> FromRequest<S> for Json<T>
where
    AxumJson<T>: FromRequest<S, Rejection = JsonRejection>,
    S: Send + Sync,
{
    type Rejection = ErrResp;

    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        match AxumJson::<T>::from_request(req, state).await {
            Err(e) => Err(ErrResp::ErrParam(Some(e.to_string()))),
            Ok(value) => Ok(Self(value.0)),
        }
    }
}

impl<T> IntoResponse for Json<T>
where
    T: Serialize,
{
    fn into_response(self) -> Response {
        // Use a small initial capacity of 128 bytes like serde_json::to_vec
        // https://docs.rs/serde_json/1.0.82/src/serde_json/ser.rs.html#2189
        let mut buf = BytesMut::with_capacity(128).writer();
        match serde_json::to_writer(&mut buf, &self.0) {
            Err(e) => ErrResp::ErrUnknown(Some(e.to_string())).into_response(),
            Ok(()) => (
                [(header::CONTENT_TYPE, ContentType::JSON)],
                buf.into_inner().freeze(),
            )
                .into_response(),
        }
    }
}

#[async_trait]
impl<T, S> FromRequestParts<S> for Path<T>
where
    T: DeserializeOwned + Send,
    S: Send + Sync,
{
    type Rejection = ErrResp;

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        match AxumPath::from_request_parts(parts, state).await {
            Err(e) => Err(ErrResp::ErrParam(Some(e.to_string()))),
            Ok(value) => Ok(Self(value.0)),
        }
    }
}

#[async_trait]
impl<T, S> FromRequestParts<S> for Query<T>
where
    T: DeserializeOwned,
    S: Send + Sync,
{
    type Rejection = ErrResp;

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        match AxumQuery::from_request_parts(parts, state).await {
            Err(e) => Err(ErrResp::ErrParam(Some(e.to_string()))),
            Ok(value) => Ok(Self(value.0)),
        }
    }
}

/// Parse Authorization header content. Returns `None` means no Authorization header.
pub fn parse_header_auth(req: &Request) -> Result<Option<String>, ErrResp> {
    let mut auth_all = req.headers().get_all(header::AUTHORIZATION).iter();
    let auth = match auth_all.next() {
        None => return Ok(None),
        Some(auth) => match auth.to_str() {
            Err(e) => return Err(ErrResp::ErrParam(Some(e.to_string()))),
            Ok(auth) => auth,
        },
    };
    if auth_all.next() != None {
        return Err(ErrResp::ErrParam(Some(
            "invalid multiple Authorization header".to_string(),
        )));
    }
    Ok(Some(auth.to_string()))
}