use crate::error::Result;
use crate::http::{content_disposition_value, Disposition, FileBody, IHttpContext, ResponseBody};
use serde::Serialize;
#[async_trait::async_trait]
pub trait IActionResult: Send {
async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()>;
}
pub type Response = Box<dyn IActionResult>;
pub struct JsonResult<T> {
pub value: T,
pub status: u16,
}
impl<T> JsonResult<T> {
pub fn new(value: T) -> Self {
Self { value, status: 200 }
}
pub fn status(mut self, status: u16) -> Self {
self.status = status;
self
}
}
#[async_trait::async_trait]
impl<T: Serialize + Send> IActionResult for JsonResult<T> {
async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
let json =
serde_json::to_vec(&self.value).map_err(crate::error::Error::ResponseSerialization)?;
let response = ctx.response_mut();
response.set_status(self.status);
response.set_header("content-type", "application/json");
response.write_bytes(json).await
}
}
pub struct ContentResult {
body: Vec<u8>,
content_type: String,
status: u16,
headers: Vec<(String, String)>,
}
impl ContentResult {
pub fn text(body: impl Into<String>) -> Self {
Self::bytes(body.into().into_bytes(), "text/plain; charset=utf-8")
}
pub fn html(body: impl Into<String>) -> Self {
Self::bytes(body.into().into_bytes(), "text/html; charset=utf-8")
}
pub fn bytes(body: impl Into<Vec<u8>>, content_type: impl Into<String>) -> Self {
Self {
body: body.into(),
content_type: content_type.into(),
status: 200,
headers: Vec::new(),
}
}
pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = content_type.into();
self
}
pub fn status(mut self, status: u16) -> Self {
self.status = status;
self
}
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.push((key.into(), value.into()));
self
}
pub fn download_name(self, file_name: impl Into<String>) -> Self {
self.with_disposition(Disposition::Attachment, file_name.into())
}
pub fn inline_name(self, file_name: impl Into<String>) -> Self {
self.with_disposition(Disposition::Inline, file_name.into())
}
fn with_disposition(mut self, disposition: Disposition, file_name: String) -> Self {
self.headers.push((
"content-disposition".into(),
content_disposition_value(disposition, &file_name),
));
self
}
}
#[async_trait::async_trait]
impl IActionResult for ContentResult {
async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
let response = ctx.response_mut();
response.set_status(self.status);
response.set_header("content-type", &self.content_type);
for (key, value) in &self.headers {
response.set_header(key, value);
}
response.write_bytes(self.body).await
}
}
pub struct NoContentResult;
#[async_trait::async_trait]
impl IActionResult for NoContentResult {
async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
ctx.response_mut().set_status(204);
Ok(())
}
}
pub struct StatusCodeResult(pub u16);
#[async_trait::async_trait]
impl IActionResult for StatusCodeResult {
async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
ctx.response_mut().set_status(self.0);
Ok(())
}
}
pub struct FileResult {
body: FileBody,
status: u16,
headers: Vec<(String, String)>,
}
impl FileResult {
pub fn path(path: impl Into<std::path::PathBuf>) -> Self {
Self::with_body(FileBody::path(path))
}
pub fn stream(reader: impl tokio::io::AsyncRead + Send + Unpin + 'static) -> Self {
Self::with_body(FileBody::stream(reader))
}
pub fn seekable_stream(
reader: impl tokio::io::AsyncRead + tokio::io::AsyncSeek + Send + Unpin + 'static,
len: u64,
) -> Self {
Self::with_body(FileBody::seekable_stream(reader, len))
}
pub fn with_body(body: FileBody) -> Self {
Self {
body,
status: 200,
headers: Vec::new(),
}
}
pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
self.body = self.body.content_type(content_type);
self
}
pub fn download_name(mut self, file_name: impl Into<String>) -> Self {
self.body = self.body.download_name(file_name);
self
}
pub fn inline_name(mut self, file_name: impl Into<String>) -> Self {
self.body = self.body.inline_name(file_name);
self
}
pub fn enable_range_processing(mut self, enabled: bool) -> Self {
self.body = self.body.enable_range_processing(enabled);
self
}
pub fn last_modified(mut self, last_modified: std::time::SystemTime) -> Self {
self.body = self.body.last_modified(last_modified);
self
}
pub fn entity_tag(mut self, entity_tag: impl Into<String>) -> Self {
self.body = self.body.entity_tag(entity_tag);
self
}
pub fn status(mut self, status: u16) -> Self {
self.status = status;
self
}
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.push((key.into(), value.into()));
self
}
}
#[async_trait::async_trait]
impl IActionResult for FileResult {
async fn execute(self: Box<Self>, ctx: &mut dyn IHttpContext) -> Result<()> {
let response = ctx.response_mut();
response.set_status(self.status);
for (key, value) in &self.headers {
response.set_header(key, value);
}
response.write_body(ResponseBody::File(self.body)).await
}
}