serenity/http/
multipart.rs

1use std::borrow::Cow;
2
3use reqwest::multipart::{Form, Part};
4
5use crate::builder::CreateAttachment;
6use crate::internal::prelude::*;
7
8impl CreateAttachment {
9    fn into_part(self) -> Result<Part> {
10        let mut part = Part::bytes(self.data);
11        part = guess_mime_str(part, &self.filename)?;
12        part = part.file_name(self.filename);
13        Ok(part)
14    }
15}
16
17#[derive(Clone, Debug)]
18pub enum MultipartUpload {
19    /// A file sent with the form data as an individual upload. For example, a sticker.
20    File(CreateAttachment),
21    /// Files sent with the form as message attachments.
22    Attachments(Vec<CreateAttachment>),
23}
24
25/// Holder for multipart body. Contains upload data, multipart fields, and payload_json for
26/// creating requests with attachments.
27#[derive(Clone, Debug)]
28pub struct Multipart {
29    pub upload: MultipartUpload,
30    /// Multipart text fields that are sent with the form data as individual fields. If a certain
31    /// endpoint does not support passing JSON body via `payload_json`, this must be used instead.
32    pub fields: Vec<(Cow<'static, str>, Cow<'static, str>)>,
33    /// JSON body that will set as the form value as `payload_json`.
34    pub payload_json: Option<String>,
35}
36
37impl Multipart {
38    pub(crate) fn build_form(self) -> Result<Form> {
39        let mut multipart = Form::new();
40
41        match self.upload {
42            MultipartUpload::File(upload_file) => {
43                multipart = multipart.part("file", upload_file.into_part()?);
44            },
45            MultipartUpload::Attachments(attachment_files) => {
46                for file in attachment_files {
47                    multipart = multipart.part(format!("files[{}]", file.id), file.into_part()?);
48                }
49            },
50        }
51
52        for (name, value) in self.fields {
53            multipart = multipart.text(name, value);
54        }
55
56        if let Some(payload_json) = self.payload_json {
57            multipart = multipart.text("payload_json", payload_json);
58        }
59
60        Ok(multipart)
61    }
62}
63
64fn guess_mime_str(part: Part, filename: &str) -> Result<Part> {
65    // This is required for certain endpoints like create sticker, otherwise the Discord API will
66    // respond with a 500 Internal Server Error. The mime type chosen is the same as what reqwest
67    // does internally when using Part::file(), but it is not done for any of the other methods we
68    // use.
69    // https://datatracker.ietf.org/doc/html/rfc7578#section-4.4
70    let mime_type = mime_guess::from_path(filename).first_or_octet_stream();
71    part.mime_str(mime_type.essence_str()).map_err(Into::into)
72}