1use serde::{Deserialize, Serialize};
4
5use crate::error::{Error, Result};
6
7#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
9pub struct FilePart {
10 pub filename: String,
12 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 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
52pub(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}