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 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 #[inline]
41 pub fn name(&self) -> Option<&str> {
42 self.name.as_deref()
43 }
44 #[inline]
46 pub fn name_mut(&mut self) -> Option<&mut String> {
47 self.name.as_mut()
48 }
49 #[inline]
51 pub fn headers(&self) -> &HeaderMap {
52 &self.headers
53 }
54 pub fn headers_mut(&mut self) -> &mut HeaderMap {
56 &mut self.headers
57 }
58 #[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 #[inline]
68 pub fn path(&self) -> &PathBuf {
69 &self.path
70 }
71 #[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#[derive(Clone, Debug)]
122pub struct FormFiles(pub Vec<FormFile>);
123impl FormFiles {
124 pub fn new(file_parts: Vec<&FilePart>) -> Self {
126 Self(file_parts.into_iter().map(FormFile::new).collect())
127 }
128
129 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}