1use crate::error::Result;
35use crate::http::{content_disposition_value, Disposition, FileBody, IHttpContext, ResponseBody};
36use serde::Serialize;
37
38#[async_trait::async_trait]
45pub trait IActionResult: Send {
46 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()>;
48}
49
50pub type Response = Box<dyn IActionResult>;
53
54pub struct JsonResult<T> {
57 pub value: T,
59 pub status: u16,
61}
62
63impl<T> JsonResult<T> {
64 pub fn new(value: T) -> Self {
66 Self { value, status: 200 }
67 }
68
69 pub fn status(mut self, status: u16) -> Self {
71 self.status = status;
72 self
73 }
74}
75
76#[async_trait::async_trait]
77impl<T: Serialize + Send> IActionResult for JsonResult<T> {
78 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
79 let json =
82 serde_json::to_vec(&self.value).map_err(crate::error::Error::ResponseSerialization)?;
83 let response = ctx.response_mut();
84 response.set_status(self.status);
85 response.set_header("content-type", "application/json");
86 response.write_bytes(json).await
87 }
88}
89
90pub struct ContentResult {
93 body: Vec<u8>,
94 content_type: String,
95 status: u16,
96 headers: Vec<(String, String)>,
97}
98
99impl ContentResult {
100 pub fn text(body: impl Into<String>) -> Self {
102 Self::bytes(body.into().into_bytes(), "text/plain; charset=utf-8")
103 }
104
105 pub fn html(body: impl Into<String>) -> Self {
107 Self::bytes(body.into().into_bytes(), "text/html; charset=utf-8")
108 }
109
110 pub fn bytes(body: impl Into<Vec<u8>>, content_type: impl Into<String>) -> Self {
112 Self {
113 body: body.into(),
114 content_type: content_type.into(),
115 status: 200,
116 headers: Vec::new(),
117 }
118 }
119
120 pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
122 self.content_type = content_type.into();
123 self
124 }
125
126 pub fn status(mut self, status: u16) -> Self {
128 self.status = status;
129 self
130 }
131
132 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
134 self.headers.push((key.into(), value.into()));
135 self
136 }
137
138 pub fn download_name(self, file_name: impl Into<String>) -> Self {
141 self.with_disposition(Disposition::Attachment, file_name.into())
142 }
143
144 pub fn inline_name(self, file_name: impl Into<String>) -> Self {
147 self.with_disposition(Disposition::Inline, file_name.into())
148 }
149
150 fn with_disposition(mut self, disposition: Disposition, file_name: String) -> Self {
151 self.headers.push((
152 "content-disposition".into(),
153 content_disposition_value(disposition, &file_name),
154 ));
155 self
156 }
157}
158
159#[async_trait::async_trait]
160impl IActionResult for ContentResult {
161 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
162 let response = ctx.response_mut();
163 response.set_status(self.status);
164 response.set_header("content-type", &self.content_type);
165 for (key, value) in &self.headers {
166 response.set_header(key, value);
167 }
168 response.write_bytes(self.body).await
169 }
170}
171
172pub struct NoContentResult;
174
175#[async_trait::async_trait]
176impl IActionResult for NoContentResult {
177 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
178 ctx.response_mut().set_status(204);
179 Ok(())
180 }
181}
182
183pub struct StatusCodeResult(pub u16);
185
186#[async_trait::async_trait]
187impl IActionResult for StatusCodeResult {
188 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
189 ctx.response_mut().set_status(self.0);
190 Ok(())
191 }
192}
193
194pub struct FileResult {
204 body: FileBody,
205 status: u16,
206 headers: Vec<(String, String)>,
207}
208
209impl FileResult {
210 pub fn path(path: impl Into<std::path::PathBuf>) -> Self {
212 Self::with_body(FileBody::path(path))
213 }
214
215 pub fn stream(reader: impl tokio::io::AsyncRead + Send + Unpin + 'static) -> Self {
217 Self::with_body(FileBody::stream(reader))
218 }
219
220 pub fn seekable_stream(
223 reader: impl tokio::io::AsyncRead + tokio::io::AsyncSeek + Send + Unpin + 'static,
224 len: u64,
225 ) -> Self {
226 Self::with_body(FileBody::seekable_stream(reader, len))
227 }
228
229 pub fn with_body(body: FileBody) -> Self {
231 Self {
232 body,
233 status: 200,
234 headers: Vec::new(),
235 }
236 }
237
238 pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
240 self.body = self.body.content_type(content_type);
241 self
242 }
243
244 pub fn download_name(mut self, file_name: impl Into<String>) -> Self {
246 self.body = self.body.download_name(file_name);
247 self
248 }
249
250 pub fn inline_name(mut self, file_name: impl Into<String>) -> Self {
252 self.body = self.body.inline_name(file_name);
253 self
254 }
255
256 pub fn enable_range_processing(mut self, enabled: bool) -> Self {
258 self.body = self.body.enable_range_processing(enabled);
259 self
260 }
261
262 pub fn last_modified(mut self, last_modified: std::time::SystemTime) -> Self {
265 self.body = self.body.last_modified(last_modified);
266 self
267 }
268
269 pub fn entity_tag(mut self, entity_tag: impl Into<String>) -> Self {
272 self.body = self.body.entity_tag(entity_tag);
273 self
274 }
275
276 pub fn status(mut self, status: u16) -> Self {
278 self.status = status;
279 self
280 }
281
282 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
284 self.headers.push((key.into(), value.into()));
285 self
286 }
287}
288
289#[async_trait::async_trait]
290impl IActionResult for FileResult {
291 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
292 let response = ctx.response_mut();
293 response.set_status(self.status);
294 for (key, value) in &self.headers {
295 response.set_header(key, value);
296 }
297 response.write_body(ResponseBody::File(self.body)).await
298 }
299}