Skip to main content

uarp_sdk/
multipart.rs

1//! Helpers for the handful of `multipart/form-data` endpoints.
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{Error, Result};
6
7/// A file to upload. `data` is sent verbatim as the part body.
8#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
9pub struct FilePart {
10    /// Filename advertised in the `Content-Disposition` header.
11    pub filename: String,
12    /// MIME type; defaults to `application/octet-stream`.
13    pub content_type: Option<String>,
14    pub data: Vec<u8>,
15}
16
17impl FilePart {
18    pub fn new(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
19        Self {
20            filename: filename.into(),
21            content_type: None,
22            data: data.into(),
23        }
24    }
25
26    pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
27        self.content_type = Some(content_type.into());
28        self
29    }
30
31    /// Read a file from disk, using its name as the part filename.
32    pub fn from_path(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
33        let path = path.as_ref();
34        let filename = path
35            .file_name()
36            .map(|name| name.to_string_lossy().into_owned())
37            .unwrap_or_else(|| "file".to_string());
38        Ok(Self::new(filename, std::fs::read(path)?))
39    }
40
41    pub(crate) fn into_part(self) -> Result<reqwest::multipart::Part> {
42        let part = reqwest::multipart::Part::bytes(self.data).file_name(self.filename);
43        match self.content_type {
44            Some(mime) => part
45                .mime_str(&mime)
46                .map_err(|err| Error::Encode(err.to_string())),
47            None => Ok(part),
48        }
49    }
50}
51
52/// Render a scalar field for a multipart form.
53///
54/// Strings are sent as-is; everything else is JSON-encoded, which matches how
55/// the platform parses structured form fields.
56pub(crate) fn field_text<T: Serialize>(value: &T) -> Result<String> {
57    match serde_json::to_value(value).map_err(|err| Error::Encode(err.to_string()))? {
58        serde_json::Value::String(text) => Ok(text),
59        serde_json::Value::Null => Ok(String::new()),
60        other => Ok(other.to_string()),
61    }
62}