1use crate::error::Result;
29use crate::http::{content_disposition_value, Disposition, FileBody, IHttpContext, ResponseBody};
30use serde::Serialize;
31
32#[async_trait::async_trait]
39pub trait IActionResult: Send {
40 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()>;
42}
43
44pub type Response = Box<dyn IActionResult>;
47
48pub struct JsonResult<T> {
51 pub value: T,
53 pub status: u16,
55}
56
57impl<T> JsonResult<T> {
58 pub fn new(value: T) -> Self {
60 Self { value, status: 200 }
61 }
62
63 pub fn status(mut self, status: u16) -> Self {
65 self.status = status;
66 self
67 }
68}
69
70#[async_trait::async_trait]
71impl<T: Serialize + Send> IActionResult for JsonResult<T> {
72 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
73 let json =
76 serde_json::to_vec(&self.value).map_err(crate::error::Error::ResponseSerialization)?;
77 let response = ctx.response_mut();
78 response.set_status(self.status);
79 response.set_header("content-type", "application/json");
80 response.write_bytes(json).await
81 }
82}
83
84pub struct ContentResult {
87 body: Vec<u8>,
88 content_type: String,
89 status: u16,
90 headers: Vec<(String, String)>,
91}
92
93impl ContentResult {
94 pub fn text(body: impl Into<String>) -> Self {
96 Self::bytes(body.into().into_bytes(), "text/plain; charset=utf-8")
97 }
98
99 pub fn html(body: impl Into<String>) -> Self {
101 Self::bytes(body.into().into_bytes(), "text/html; charset=utf-8")
102 }
103
104 pub fn bytes(body: impl Into<Vec<u8>>, content_type: impl Into<String>) -> Self {
106 Self {
107 body: body.into(),
108 content_type: content_type.into(),
109 status: 200,
110 headers: Vec::new(),
111 }
112 }
113
114 pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
116 self.content_type = content_type.into();
117 self
118 }
119
120 pub fn status(mut self, status: u16) -> Self {
122 self.status = status;
123 self
124 }
125
126 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
128 self.headers.push((key.into(), value.into()));
129 self
130 }
131
132 pub fn download_name(self, file_name: impl Into<String>) -> Self {
135 self.with_disposition(Disposition::Attachment, file_name.into())
136 }
137
138 pub fn inline_name(self, file_name: impl Into<String>) -> Self {
141 self.with_disposition(Disposition::Inline, file_name.into())
142 }
143
144 fn with_disposition(mut self, disposition: Disposition, file_name: String) -> Self {
145 self.headers.push((
146 "content-disposition".into(),
147 content_disposition_value(disposition, &file_name),
148 ));
149 self
150 }
151}
152
153#[async_trait::async_trait]
154impl IActionResult for ContentResult {
155 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
156 let response = ctx.response_mut();
157 response.set_status(self.status);
158 response.set_header("content-type", &self.content_type);
159 for (key, value) in &self.headers {
160 response.set_header(key, value);
161 }
162 response.write_bytes(self.body).await
163 }
164}
165
166pub struct NoContentResult;
168
169#[async_trait::async_trait]
170impl IActionResult for NoContentResult {
171 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
172 ctx.response_mut().set_status(204);
173 Ok(())
174 }
175}
176
177pub struct StatusCodeResult(pub u16);
179
180#[async_trait::async_trait]
181impl IActionResult for StatusCodeResult {
182 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
183 ctx.response_mut().set_status(self.0);
184 Ok(())
185 }
186}
187
188pub struct FileResult {
198 body: FileBody,
199 status: u16,
200 headers: Vec<(String, String)>,
201}
202
203impl FileResult {
204 pub fn path(path: impl Into<std::path::PathBuf>) -> Self {
206 Self::with_body(FileBody::path(path))
207 }
208
209 pub fn stream(reader: impl tokio::io::AsyncRead + Send + Unpin + 'static) -> Self {
211 Self::with_body(FileBody::stream(reader))
212 }
213
214 pub fn seekable_stream(
217 reader: impl tokio::io::AsyncRead + tokio::io::AsyncSeek + Send + Unpin + 'static,
218 len: u64,
219 ) -> Self {
220 Self::with_body(FileBody::seekable_stream(reader, len))
221 }
222
223 pub fn with_body(body: FileBody) -> Self {
225 Self {
226 body,
227 status: 200,
228 headers: Vec::new(),
229 }
230 }
231
232 pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
234 self.body = self.body.content_type(content_type);
235 self
236 }
237
238 pub fn download_name(mut self, file_name: impl Into<String>) -> Self {
240 self.body = self.body.download_name(file_name);
241 self
242 }
243
244 pub fn inline_name(mut self, file_name: impl Into<String>) -> Self {
246 self.body = self.body.inline_name(file_name);
247 self
248 }
249
250 pub fn enable_range_processing(mut self, enabled: bool) -> Self {
252 self.body = self.body.enable_range_processing(enabled);
253 self
254 }
255
256 pub fn last_modified(mut self, last_modified: std::time::SystemTime) -> Self {
259 self.body = self.body.last_modified(last_modified);
260 self
261 }
262
263 pub fn entity_tag(mut self, entity_tag: impl Into<String>) -> Self {
266 self.body = self.body.entity_tag(entity_tag);
267 self
268 }
269
270 pub fn status(mut self, status: u16) -> Self {
272 self.status = status;
273 self
274 }
275
276 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
278 self.headers.push((key.into(), value.into()));
279 self
280 }
281}
282
283#[async_trait::async_trait]
284impl IActionResult for FileResult {
285 async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
286 let response = ctx.response_mut();
287 response.set_status(self.status);
288 for (key, value) in &self.headers {
289 response.set_header(key, value);
290 }
291 response.write_body(ResponseBody::File(self.body)).await
292 }
293}