Skip to main content

wekan_cli/client/
imports.rs

1//! Bounded preparation shared by the native JSON import endpoints.
2use std::{io::Write, marker::PhantomData};
3
4use serde::Serialize;
5use thiserror::Error;
6
7pub const MAX_IMPORT_JSON_BYTES: usize = 50 * 1024 * 1024;
8
9/// An endpoint's typed request, encoded once before authentication or transport.
10pub struct PreparedImportRequest<T> {
11    body: Vec<u8>,
12    _request: PhantomData<T>,
13}
14
15#[derive(Debug, Error)]
16#[error("encoded import request exceeds the 50 MiB JSON limit; no request was sent")]
17pub struct ImportEncodeError;
18
19impl<T: Serialize> PreparedImportRequest<T> {
20    pub fn new(request: &T) -> Result<Self, ImportEncodeError> {
21        let mut output = BoundedJson(Vec::new());
22        serde_json::to_writer(&mut output, request).map_err(|_| ImportEncodeError)?;
23        Ok(Self {
24            body: output.0,
25            _request: PhantomData,
26        })
27    }
28}
29
30impl<T> PreparedImportRequest<T> {
31    pub(super) fn into_body(self) -> Vec<u8> {
32        self.body
33    }
34}
35
36struct BoundedJson(Vec<u8>);
37
38impl Write for BoundedJson {
39    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
40        if buf.len() > MAX_IMPORT_JSON_BYTES - self.0.len() {
41            return Err(std::io::Error::other("import JSON exceeds 50 MiB"));
42        }
43        self.0.extend_from_slice(buf);
44        Ok(buf.len())
45    }
46
47    fn flush(&mut self) -> std::io::Result<()> {
48        Ok(())
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn final_encoding_limit_includes_json_escaping() {
58        let prepared = PreparedImportRequest::new(&"line\n").unwrap();
59        assert_eq!(prepared.body, br#""line\n""#);
60        assert!(PreparedImportRequest::new(&"\n".repeat(MAX_IMPORT_JSON_BYTES / 2)).is_err());
61    }
62}