Skip to main content

axum_bson/
lib.rs

1use std::ops::{Deref, DerefMut};
2
3use axum_core::{
4    extract::{FromRequest, Request},
5    response::{IntoResponse, Response},
6};
7use bytes::Bytes;
8use http::{header, HeaderValue, StatusCode};
9use serde::{de::DeserializeOwned, Serialize};
10use thiserror::Error;
11
12pub struct Bson<T>(pub T);
13
14#[derive(Debug, Error)]
15pub enum BsonRejection {
16    #[error("bytes read error: {}",.0)]
17    BytesRead(#[from] axum_core::extract::rejection::BytesRejection),
18    #[error("missing octet-stream content type")]
19    MissingContentType,
20    #[error("bson parse error: {}",.0)]
21    BsonError(#[from] bson::de::Error),
22}
23
24impl IntoResponse for BsonRejection {
25    fn into_response(self) -> axum_core::response::Response {
26        (
27            StatusCode::BAD_REQUEST,
28            [(header::CONTENT_TYPE, HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()))],
29            self.to_string(),
30        )
31            .into_response()
32    }
33}
34
35impl<S, T> FromRequest<S> for Bson<T>
36where
37    T: DeserializeOwned,
38    S: Send + Sync,
39{
40    type Rejection = BsonRejection;
41
42    async fn from_request(req: Request, _s: &S) -> Result<Self, Self::Rejection> {
43        if bson_content_type(&req) {
44            let bytes = Bytes::from_request(req, _s).await?;
45            match bson::from_slice(&bytes) {
46                Ok(value) => Ok(Bson(value)),
47                Err(err) => Err(err.into()),
48            }
49        } else {
50            Err(BsonRejection::MissingContentType)
51        }
52    }
53}
54
55fn bson_content_type<B>(req: &Request<B>) -> bool {
56    let content_type = if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
57        content_type
58    } else {
59        return false;
60    };
61
62    let content_type = if let Ok(content_type) = content_type.to_str() {
63        content_type
64    } else {
65        return false;
66    };
67
68    let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() {
69        mime
70    } else {
71        return false;
72    };
73
74    let is_binary_content_type = mime.type_() == "application"
75        && (mime.subtype() == "octet-stream"
76            || mime.suffix().map_or(false, |name| name == "octet-stream"));
77
78    is_binary_content_type
79}
80
81impl<T> Deref for Bson<T> {
82    type Target = T;
83
84    fn deref(&self) -> &Self::Target {
85        &self.0
86    }
87}
88
89impl<T> DerefMut for Bson<T> {
90    fn deref_mut(&mut self) -> &mut Self::Target {
91        &mut self.0
92    }
93}
94
95impl<T> From<T> for Bson<T> {
96    fn from(inner: T) -> Self {
97        Self(inner)
98    }
99}
100
101impl<T> IntoResponse for Bson<T>
102where
103    T: Serialize,
104{
105    fn into_response(self) -> Response {
106        match bson::to_raw_document_buf(&self.0) {
107            Ok(buf) => (
108                [(
109                    header::CONTENT_TYPE,
110                    HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
111                )],
112                Bytes::from(buf.into_bytes()),
113            )
114                .into_response(),
115            Err(err) => {
116                (
117                    StatusCode::INTERNAL_SERVER_ERROR,
118                    [(
119                        header::CONTENT_TYPE,
120                        HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
121                    )],
122                    err.to_string(),
123                )
124                    .into_response()
125            }
126        }
127    }
128}
129
130/*
131#[cfg(test)]
132mod tests {
133    use super::*;
134    //use axum_core::{routing::post, test_helpers::*, Router};
135    use serde::Deserialize;
136
137    #[tokio::test]
138    async fn deserialize_body() {
139            #[derive(Debug, Deserialize)]
140            struct Input {
141                    foo: String,
142            }
143
144            let app = Router::new().route("/", post(|input: Bson<Input>| async { input.0.foo }));
145
146            let client = TestClient::new(app);
147            let res = client.post("/").json(&json!({ "foo": "bar" })).send().await;
148            let body = res.text().await;
149
150            assert_eq!(body, "bar");
151    }
152}
153*/