poem_openapi/types/multipart/
upload.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Debug, Formatter},
4};
5
6use poem::web::Field as PoemField;
7use tokio::{
8    fs::File,
9    io::{AsyncRead, AsyncReadExt, AsyncSeek, Error as IoError, ErrorKind},
10};
11
12use crate::{
13    registry::{MetaSchema, MetaSchemaRef},
14    types::{ParseError, ParseFromMultipartField, ParseResult, Type},
15};
16
17/// A uploaded file for multipart.
18pub struct Upload {
19    file_name: Option<String>,
20    content_type: Option<String>,
21    file: File,
22    size: usize,
23}
24
25impl Debug for Upload {
26    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
27        let mut d = f.debug_struct("Upload");
28        if let Some(file_name) = self.file_name() {
29            d.field("filename", &file_name);
30        }
31        if let Some(content_type) = self.content_type() {
32            d.field("content_type", &content_type);
33        }
34        d.finish()
35    }
36}
37
38impl Upload {
39    /// Get the content type of the field.
40    #[inline]
41    pub fn content_type(&self) -> Option<&str> {
42        self.content_type.as_deref()
43    }
44
45    /// The file name found in the `Content-Disposition` header.
46    #[inline]
47    pub fn file_name(&self) -> Option<&str> {
48        self.file_name.as_deref()
49    }
50
51    /// Returns the file size in bytes.
52    #[inline]
53    pub fn size(&self) -> usize {
54        self.size
55    }
56
57    /// Consumes this body object to return a [`Vec<u8>`] that contains all
58    /// data.
59    pub async fn into_vec(self) -> Result<Vec<u8>, IoError> {
60        let mut data = Vec::new();
61        self.into_async_read().read_to_end(&mut data).await?;
62        Ok(data)
63    }
64
65    /// Consumes this body object to return a [`String`] that contains all data.
66    pub async fn into_string(self) -> Result<String, IoError> {
67        String::from_utf8(
68            self.into_vec()
69                .await
70                .map_err(|err| IoError::new(ErrorKind::Other, err))?
71                .to_vec(),
72        )
73        .map_err(|err| IoError::new(ErrorKind::Other, err))
74    }
75
76    /// Consumes this body object to return a reader.
77    pub fn into_async_read(self) -> impl AsyncRead + AsyncSeek + Unpin + Send + 'static {
78        self.file
79    }
80
81    /// Consumes this body object to return the file.
82    pub fn into_file(self) -> File {
83        self.file
84    }
85}
86
87impl Type for Upload {
88    const IS_REQUIRED: bool = true;
89
90    type RawValueType = Self;
91
92    type RawElementValueType = Self;
93
94    fn name() -> Cow<'static, str> {
95        "string_binary".into()
96    }
97
98    fn schema_ref() -> MetaSchemaRef {
99        MetaSchemaRef::Inline(Box::new(MetaSchema::new_with_format("string", "binary")))
100    }
101
102    fn as_raw_value(&self) -> Option<&Self::RawValueType> {
103        Some(self)
104    }
105
106    fn raw_element_iter<'a>(
107        &'a self,
108    ) -> Box<dyn Iterator<Item = &'a Self::RawElementValueType> + 'a> {
109        Box::new(self.as_raw_value().into_iter())
110    }
111}
112
113impl ParseFromMultipartField for Upload {
114    async fn parse_from_multipart(field: Option<PoemField>) -> ParseResult<Self> {
115        match field {
116            Some(field) => {
117                let content_type = field.content_type().map(ToString::to_string);
118                let file_name = field.file_name().map(ToString::to_string);
119                let file = field.tempfile().await.map_err(ParseError::custom)?;
120                let size = file.metadata().await.map_err(ParseError::custom)?.len() as usize;
121                Ok(Self {
122                    content_type,
123                    file_name,
124                    file,
125                    size,
126                })
127            }
128            None => Err(ParseError::expected_input()),
129        }
130    }
131}