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::{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) -> 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(req: &'ex mut Request, arg: &str) -> Result<Self, ParseError> {
94 req.file(arg)
95 .await
96 .map(Self::new)
97 .ok_or_else(|| ParseError::other("file not found"))
98 }
99}
100
101#[async_trait]
102impl EndpointArgRegister for FormFile {
103 fn register(_components: &mut Components, operation: &mut Operation, arg: &str) {
104 let schema = Schema::from(
105 Object::new().property(
106 arg,
107 Object::with_type(BasicType::String)
108 .format(SchemaFormat::KnownFormat(KnownFormat::Binary)),
109 ),
110 );
111
112 if let Some(request_body) = &mut operation.request_body {
113 request_body
114 .contents
115 .insert("multipart/form-data".into(), Content::new(schema));
116 } else {
117 let request_body = RequestBody::new()
118 .description("Upload a file.")
119 .add_content("multipart/form-data", Content::new(schema));
120 operation.request_body = Some(request_body);
121 }
122 }
123}
124
125#[derive(Clone, Debug)]
127pub struct FormFiles(pub Vec<FormFile>);
128impl FormFiles {
129 #[must_use]
131 pub fn new(file_parts: Vec<&FilePart>) -> Self {
132 Self(file_parts.into_iter().map(FormFile::new).collect())
133 }
134
135 #[must_use]
137 pub fn into_inner(self) -> Vec<FormFile> {
138 self.0
139 }
140}
141impl Deref for FormFiles {
142 type Target = Vec<FormFile>;
143
144 fn deref(&self) -> &Self::Target {
145 &self.0
146 }
147}
148
149impl DerefMut for FormFiles {
150 fn deref_mut(&mut self) -> &mut Self::Target {
151 &mut self.0
152 }
153}
154
155impl<'ex> Extractible<'ex> for FormFiles {
156 fn metadata() -> &'static Metadata {
157 static METADATA: Metadata = Metadata::new("");
158 &METADATA
159 }
160 #[allow(refining_impl_trait)]
161 async fn extract(_req: &'ex mut Request) -> Result<Self, ParseError> {
162 panic!("query parameter can not be extracted from request")
163 }
164 #[allow(refining_impl_trait)]
165 async fn extract_with_arg(req: &'ex mut Request, arg: &str) -> Result<Self, ParseError> {
166 Ok(Self(
167 req.files(arg)
168 .await
169 .ok_or_else(|| ParseError::other("file not found"))?
170 .iter()
171 .map(FormFile::new)
172 .collect(),
173 ))
174 }
175}
176
177#[async_trait]
178impl EndpointArgRegister for FormFiles {
179 fn register(_components: &mut Components, operation: &mut Operation, arg: &str) {
180 let schema = Schema::from(
181 Object::new().property(
182 arg,
183 Array::new().items(Schema::from(
184 Object::with_type(BasicType::String)
185 .format(SchemaFormat::KnownFormat(KnownFormat::Binary)),
186 )),
187 ),
188 );
189 if let Some(request_body) = &mut operation.request_body {
190 request_body
191 .contents
192 .insert("multipart/form-data".into(), Content::new(schema));
193 } else {
194 let request_body = RequestBody::new()
195 .description("Upload files.")
196 .add_content("multipart/form-data", Content::new(schema));
197 operation.request_body = Some(request_body);
198 }
199 }
200}