salvo_oapi/extract/payload/
file.rs1use 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#[derive(Clone, Debug)]
18pub struct FormFile {
19 name: Option<String>,
20 headers: HeaderMap,
22 path: PathBuf,
24 size: u64,
27}
28impl FormFile {
29 #[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 #[inline]
42 #[must_use]
43 pub fn name(&self) -> Option<&str> {
44 self.name.as_deref()
45 }
46 #[inline]
48 pub fn name_mut(&mut self) -> Option<&mut String> {
49 self.name.as_mut()
50 }
51 #[inline]
53 #[must_use]
54 pub fn headers(&self) -> &HeaderMap {
55 &self.headers
56 }
57 pub fn headers_mut(&mut self) -> &mut HeaderMap {
59 &mut self.headers
60 }
61 #[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 #[inline]
71 #[must_use]
72 pub fn path(&self) -> &PathBuf {
73 &self.path
74 }
75 #[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#[derive(Clone, Debug)]
131pub struct FormFiles(pub Vec<FormFile>);
132impl FormFiles {
133 #[must_use]
135 pub fn new(file_parts: Vec<&FilePart>) -> Self {
136 Self(file_parts.into_iter().map(FormFile::new).collect())
137 }
138
139 #[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}