summaryrefslogtreecommitdiff
path: root/src/response.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/response.rs')
-rw-r--r--src/response.rs133
1 files changed, 133 insertions, 0 deletions
diff --git a/src/response.rs b/src/response.rs
new file mode 100644
index 0000000..f6f0fa0
--- /dev/null
+++ b/src/response.rs
@@ -0,0 +1,133 @@
+mod builder;
+
+use get::Get;
+
+use mlua::{FromLua, Lua, Value};
+
+use std::collections::HashMap;
+
+use tokio::{
+ fs::File,
+ io::{self, AsyncWrite, AsyncWriteExt},
+};
+
+use strum::{Display, EnumString, FromRepr};
+
+use builder::Builder;
+
+use crate::lua;
+
+#[allow(unreachable_patterns)]
+#[derive(Debug, Clone, Copy, Display, EnumString, FromRepr)]
+pub enum Status {
+ #[strum(to_string = "200 OK", serialize = "OK")]
+ Ok = 200,
+
+ #[strum(to_string = "404 Not Found", serialize = "Not Found")]
+ NotFound = 404,
+
+ #[strum(
+ to_string = "500 Internal Server Error",
+ serialize = "Internal ServerError"
+ )]
+ InternalServerError = 500,
+}
+
+#[derive(Debug)]
+pub enum Body {
+ File(File),
+ Buffer(Vec<u8>),
+ Empty,
+}
+
+#[derive(Debug, Get)]
+pub struct Response {
+ status: Status,
+ headers: HashMap<String, Vec<u8>>,
+ body: Body,
+}
+
+impl Response {
+ pub fn builder() -> Builder {
+ Builder::new()
+ }
+}
+
+impl Response {
+ pub async fn serialize<W: AsyncWrite + Unpin>(
+ mut self,
+ mut writer: W,
+ ) -> Result<(), io::Error> {
+ writer.write_all(b"HTTP/1.1 ").await?;
+ writer.write_all(self.status.to_string().as_bytes()).await?;
+ writer.write_all(b"\r\n").await?;
+
+ for (key, value) in &self.headers {
+ writer.write_all(key.as_bytes()).await?;
+ writer.write_all(b": ").await?;
+ writer.write_all(value).await?;
+ writer.write_all(b"\r\n").await?;
+ }
+
+ writer.write_all(b"\r\n").await?;
+
+ match &mut self.body {
+ Body::File(file) => {
+ io::copy(file, &mut writer).await?;
+ }
+ Body::Buffer(buf) => {
+ writer.write_all(buf).await?;
+ }
+ Body::Empty => (),
+ }
+
+ Ok(())
+ }
+}
+
+impl FromLua for Response {
+ fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
+ match value {
+ Value::Table(table) => {
+ let status = match table.get("status")? {
+ Value::Integer(i) => match Status::from_repr(i as usize) {
+ Some(status) => status,
+ None => return Err(mlua::Error::runtime(format!("invalid status: {i}"))),
+ },
+ _ => return Err(mlua::Error::runtime("invalid status")),
+ };
+
+ let headers = match table.get("headers")? {
+ Value::Table(table) => {
+ let mut headers = HashMap::new();
+
+ for result in table.pairs() {
+ let (key, value): (String, mlua::String) = result?;
+
+ headers.insert(key, value.as_bytes().to_vec());
+ }
+
+ headers
+ }
+ Value::Nil => HashMap::new(),
+ _ => return Err(mlua::Error::runtime("invalid headers")),
+ };
+
+ let body = match table.get("body")? {
+ Value::String(string) => Body::Buffer(string.as_bytes().to_vec()),
+ Value::UserData(userdata) if userdata.is::<lua::File>() => {
+ Body::File(userdata.borrow_mut::<lua::File>().unwrap().take().unwrap())
+ }
+ _ => return Err(mlua::Error::runtime("invalid body")),
+ };
+
+ Ok(Self {
+ status,
+ headers,
+ body,
+ })
+ }
+ _ => Err(mlua::Error::runtime("invalid response, expected a table")),
+ }
+ }
+}