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