actix_bincode/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(rust_2018_idioms, nonstandard_style)]
3#![warn(future_incompatible)]
4
5pub mod config;
6pub mod error;
7
8#[cfg(feature = "serde")]
9mod serde_compat;
10#[cfg(feature = "serde")]
11pub use serde_compat::BincodeSerde;
12
13use config::{BincodeConfig, DEFAULT_LIMIT_BYTES};
14
15use std::{ops::Deref, pin::Pin};
16
17use actix_web::{dev::Payload, web::BytesMut, FromRequest, HttpMessage, HttpRequest};
18use bincode::config::Configuration;
19use futures::{Future, StreamExt};
20
21/// Extract and decode bincode from payload
22///
23///     use actix_web::HttpResponse;
24///     use actix_bincode::Bincode;
25///     use bincode::{Decode, Encode};
26///
27///     #[derive(Decode, Encode)]
28///     struct Object {
29///         text: String,
30///     }
31///
32///     // Route
33///     pub async fn index(object: Bincode<Object>) -> HttpResponse {
34///         println!("{}", object.text);
35///         let body = object.into_bytes(None).unwrap(); // Use standard config
36///         HttpResponse::Ok().body(body)
37///     }
38pub struct Bincode<T>(T);
39
40// Extractor for bincode::Decode derived struct
41impl<T> FromRequest for Bincode<T>
42where
43    T: bincode::Decode<()>, // () seems to work?
44{
45    type Error = error::BincodePayloadError;
46    type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
47
48    fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
49        // Validate content type
50        if req.content_type() != "application/octet-stream" {
51            let content_type = req.content_type().to_string();
52            return Box::pin(async { Err(error::BincodePayloadError::ContentType(content_type)) });
53        }
54
55        // Read config if present
56        let config = req
57            .app_data::<BincodeConfig>()
58            .map_or(BincodeConfig::default(), |c| *c);
59
60        // Read bincode config
61        let bincode_config = req
62            .app_data::<Configuration>()
63            .map_or(bincode::config::standard(), |c| *c);
64
65        let mut payload = payload.take();
66
67        Box::pin(async move {
68            let mut buffer: BytesMut = BytesMut::with_capacity(config.buf_size);
69
70            while let Some(bytes) = payload.next().await {
71                let bytes = bytes?;
72
73                // Prevent too large payloads
74                if buffer.len() + bytes.len() > config.limit {
75                    return Err(error::BincodePayloadError::Overflow(config.limit));
76                }
77
78                buffer.extend(bytes);
79            }
80
81            match bincode::decode_from_slice::<T, _>(&buffer, bincode_config) {
82                Ok((obj, _)) => Ok(Bincode(obj)),
83                Err(e) => Err(error::BincodePayloadError::Decode(e)),
84            }
85        })
86    }
87}
88
89impl<T: bincode::Encode> Bincode<T> {
90    /// Take the inner type
91    pub fn into_inner(self) -> T {
92        self.0
93    }
94
95    /// Serializes body into bytes
96    #[allow(clippy::missing_errors_doc)]
97    pub fn into_bytes(
98        self,
99        config: Option<Configuration>,
100    ) -> Result<BytesMut, error::BincodePayloadError> {
101        let mut bytes = BytesMut::with_capacity(DEFAULT_LIMIT_BYTES);
102        let ser = bincode::encode_to_vec(
103            self.into_inner(),
104            config.unwrap_or(bincode::config::standard()),
105        )?;
106        bytes.extend(ser);
107        Ok(bytes)
108    }
109}
110
111// For usability, skip the zero
112impl<T> Deref for Bincode<T> {
113    type Target = T;
114    fn deref(&self) -> &Self::Target {
115        &self.0
116    }
117}