Skip to main content

salvo_oapi/extract/payload/
file.rs

1use std::ops::{Deref, DerefMut};
2use std::path::PathBuf;
3
4use salvo_core::extract::{Extractible, Metadata};
5use salvo_core::http::form::FilePart;
6use salvo_core::http::header::CONTENT_TYPE;
7use salvo_core::http::{HeaderMap, Mime, ParseError};
8use salvo_core::{Depot, Request, async_trait};
9
10use crate::endpoint::EndpointArgRegister;
11use crate::{
12    Array, BasicType, Components, Content, KnownFormat, Object, Operation, RequestBody, Schema,
13    SchemaFormat,
14};
15
16/// Represents the upload file.
17#[derive(Clone, Debug)]
18pub struct FormFile {
19    name: Option<String>,
20    /// The headers of the part
21    headers: HeaderMap,
22    /// A temporary file containing the file content
23    path: PathBuf,
24    /// Optionally, the size of the file.  This is filled when multiparts are parsed, but is
25    /// not necessary when they are generated.
26    size: u64,
27}
28impl FormFile {
29    /// Create a new `FormFile` from a `FilePart`.
30    #[must_use]
31    pub fn new(file_part: &FilePart) -> Self {
32        Self {
33            name: file_part.name().map(|s| s.to_owned()),
34            headers: file_part.headers().clone(),
35            path: file_part.path().to_owned(),
36            size: file_part.size(),
37        }
38    }
39
40    /// Get file name.
41    #[inline]
42    #[must_use]
43    pub fn name(&self) -> Option<&str> {
44        self.name.as_deref()
45    }
46    /// Get file name mutable reference.
47    #[inline]
48    pub fn name_mut(&mut self) -> Option<&mut String> {
49        self.name.as_mut()
50    }
51    /// Get headers.
52    #[inline]
53    #[must_use]
54    pub fn headers(&self) -> &HeaderMap {
55        &self.headers
56    }
57    /// Get headers mutable reference.
58    pub fn headers_mut(&mut self) -> &mut HeaderMap {
59        &mut self.headers
60    }
61    /// Get content type.
62    #[inline]
63    pub fn content_type(&self) -> Option<Mime> {
64        self.headers
65            .get(CONTENT_TYPE)
66            .and_then(|h| h.to_str().ok())
67            .and_then(|v| v.parse().ok())
68    }
69    /// Get file path.
70    #[inline]
71    #[must_use]
72    pub fn path(&self) -> &PathBuf {
73        &self.path
74    }
75    /// Get file size.
76    #[inline]
77    #[must_use]
78    pub fn size(&self) -> u64 {
79        self.size
80    }
81}
82
83impl<'ex> Extractible<'ex> for FormFile {
84    fn metadata() -> &'static Metadata {
85        static METADATA: Metadata = Metadata::new("");
86        &METADATA
87    }
88    #[allow(refining_impl_trait)]
89    async fn extract(_req: &'ex mut Request, _depot: &'ex mut Depot) -> Result<Self, ParseError> {
90        panic!("query parameter can not be extracted from request")
91    }
92    #[allow(refining_impl_trait)]
93    async fn extract_with_arg(
94        req: &'ex mut Request,
95        _depot: &'ex mut Depot,
96        arg: &str,
97    ) -> Result<Self, ParseError> {
98        req.file(arg)
99            .await
100            .map(Self::new)
101            .ok_or_else(|| ParseError::other("file not found"))
102    }
103}
104
105#[async_trait]
106impl EndpointArgRegister for FormFile {
107    fn register(_components: &mut Components, operation: &mut Operation, arg: &str) {
108        let schema = Schema::from(
109            Object::new().property(
110                arg,
111                Object::with_type(BasicType::String)
112                    .format(SchemaFormat::KnownFormat(KnownFormat::Binary)),
113            ),
114        );
115
116        if let Some(request_body) = &mut operation.request_body {
117            request_body
118                .contents
119                .insert("multipart/form-data".into(), Content::new(schema));
120        } else {
121            let request_body = RequestBody::new()
122                .description("Upload a file.")
123                .add_content("multipart/form-data", Content::new(schema));
124            operation.request_body = Some(request_body);
125        }
126    }
127}
128
129/// Represents the upload files.
130#[derive(Clone, Debug)]
131pub struct FormFiles(pub Vec<FormFile>);
132impl FormFiles {
133    /// Create a new `FormFiles` from a `Vec<&FilePart>`.
134    #[must_use]
135    pub fn new(file_parts: Vec<&FilePart>) -> Self {
136        Self(file_parts.into_iter().map(FormFile::new).collect())
137    }
138
139    /// Get inner files.
140    #[must_use]
141    pub fn into_inner(self) -> Vec<FormFile> {
142        self.0
143    }
144}
145impl Deref for FormFiles {
146    type Target = Vec<FormFile>;
147
148    fn deref(&self) -> &Self::Target {
149        &self.0
150    }
151}
152
153impl DerefMut for FormFiles {
154    fn deref_mut(&mut self) -> &mut Self::Target {
155        &mut self.0
156    }
157}
158
159impl<'ex> Extractible<'ex> for FormFiles {
160    fn metadata() -> &'static Metadata {
161        static METADATA: Metadata = Metadata::new("");
162        &METADATA
163    }
164    #[allow(refining_impl_trait)]
165    async fn extract(_req: &'ex mut Request, _depot: &'ex mut Depot) -> Result<Self, ParseError> {
166        panic!("query parameter can not be extracted from request")
167    }
168    #[allow(refining_impl_trait)]
169    async fn extract_with_arg(
170        req: &'ex mut Request,
171        _depot: &'ex mut Depot,
172        arg: &str,
173    ) -> Result<Self, ParseError> {
174        Ok(Self(
175            req.files(arg)
176                .await
177                .ok_or_else(|| ParseError::other("file not found"))?
178                .iter()
179                .map(FormFile::new)
180                .collect(),
181        ))
182    }
183}
184
185#[async_trait]
186impl EndpointArgRegister for FormFiles {
187    fn register(_components: &mut Components, operation: &mut Operation, arg: &str) {
188        let schema = Schema::from(
189            Object::new().property(
190                arg,
191                Array::new().items(Schema::from(
192                    Object::with_type(BasicType::String)
193                        .format(SchemaFormat::KnownFormat(KnownFormat::Binary)),
194                )),
195            ),
196        );
197        if let Some(request_body) = &mut operation.request_body {
198            request_body
199                .contents
200                .insert("multipart/form-data".into(), Content::new(schema));
201        } else {
202            let request_body = RequestBody::new()
203                .description("Upload files.")
204                .add_content("multipart/form-data", Content::new(schema));
205            operation.request_body = Some(request_body);
206        }
207    }
208}