diff options
| author | Stanisław Zagórowski <hasohasacz@gmail.com> | 2024-01-19 21:56:26 +0100 |
|---|---|---|
| committer | Stanisław Zagórowski <hasohasacz@gmail.com> | 2024-01-19 21:56:26 +0100 |
| commit | 2e77359c2f1cab769e34cde5c0dfb90a2d52b00b (patch) | |
| tree | f9ef34db90e1247a20eadb91364d1a57de124281 /src | |
| parent | 0e5bb91658dc4a81213c986442ce1662ba4d0ffc (diff) | |
feature: add method and body to route params
Diffstat (limited to 'src')
| -rw-r--r-- | src/error.rs | 2 | ||||
| -rw-r--r-- | src/luax/mod.rs | 54 | ||||
| -rw-r--r-- | src/main.rs | 11 | ||||
| -rw-r--r-- | src/server.rs | 99 |
4 files changed, 142 insertions, 24 deletions
diff --git a/src/error.rs b/src/error.rs index 3c8b756..fabe012 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,7 @@ pub enum LuaXError { ExpectedVar, ExpectedExpression, UnterminatedStringLiteral, + NonJsonType, } impl std::error::Error for LuaXError {} @@ -23,6 +24,7 @@ impl Display for LuaXError { LuaXError::ExpectedVar => write!(f, "Expected variable"), LuaXError::ExpectedExpression => write!(f, "Expected expression"), LuaXError::UnterminatedStringLiteral => write!(f, "Unterminated string literal"), + LuaXError::NonJsonType => write!(f, "Type cannot be represented in JSON"), } } } diff --git a/src/luax/mod.rs b/src/luax/mod.rs index 55a9f48..e5adc9a 100644 --- a/src/luax/mod.rs +++ b/src/luax/mod.rs @@ -76,6 +76,30 @@ pub fn table_to_html<W: std::io::Write>(table: rlua::Table, f: &mut W) -> Result Ok(()) } +pub fn table_to_json<W: std::io::Write>(table: rlua::Table, f: &mut W) -> Result<()> { + let mut first = true; + write!(f, "{{")?; + for pair in table.pairs::<String, rlua::Value>() { + let (key, value) = pair?; + if !first { + write!(f, ",")?; + } + first = false; + write!(f, "\"{}\":", key)?; + match value { + rlua::Value::Table(t) => table_to_json(t, f)?, + rlua::Value::String(s) => write!(f, "\"{}\"", s.to_str()?)?, + rlua::Value::Boolean(b) => write!(f, "{}", b)?, + rlua::Value::Number(n) => write!(f, "{}", n)?, + rlua::Value::Nil => write!(f, "null")?, + _ => return Err(LuaXError::NonJsonType.into()), + } + } + write!(f, "}}")?; + + Ok(()) +} + pub fn preprocess(s: &str) -> Result<String> { let mut buf = Vec::new(); let preprocessor = preprocessor::Preprocessor::new(s, &mut buf)?; @@ -142,6 +166,12 @@ pub fn prepare_lua(dev_mode: bool) -> Result<Lua> { reluax.set("url_matches", url_matches)?; let url_extract = ctx.create_function(utils::url_extract)?; reluax.set("url_extract", url_extract)?; + let html = ctx.create_function(utils::wrap_html)?; + reluax.set("html", html)?; + let html_page = ctx.create_function(utils::wrap_html_page)?; + reluax.set("html_page", html_page)?; + let json = ctx.create_function(utils::wrap_json)?; + reluax.set("json", json)?; reluax.set("dev_mode", dev_mode)?; ctx.globals().set("reluax", reluax)?; @@ -268,6 +298,30 @@ mod utils { } } + /// Wrap a table in a table to signal that it should be rendered as HTML + pub fn wrap_html<'lua>(ctx: Context<'lua>, table: Table<'lua>) -> Result<Table<'lua>> { + let html_table = ctx.create_table()?; + html_table.set("type", "html")?; + html_table.set("value", table)?; + Ok(html_table) + } + + /// Wrap a table in a table to signal that it should be rendered as a full HTML page + pub fn wrap_html_page<'lua>(ctx: Context<'lua>, table: Table<'lua>) -> Result<Table<'lua>> { + let html_table = ctx.create_table()?; + html_table.set("type", "html-page")?; + html_table.set("value", table)?; + Ok(html_table) + } + + /// Wrap a table in a table to signal that it should be rendered as JSON + pub fn wrap_json<'lua>(ctx: Context<'lua>, table: Table<'lua>) -> Result<Table<'lua>> { + let json_table = ctx.create_table()?; + json_table.set("type", "json")?; + json_table.set("value", table)?; + Ok(json_table) + } + #[cfg(test)] mod tests { use rlua::Lua; diff --git a/src/main.rs b/src/main.rs index 881c0d3..64300fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -219,8 +219,7 @@ async fn serve_from_temp( tmp_dir.path().display().bright_blue() ); - // copy all files from the current directory to the temporary directory recursively. - let copied = recurse_copy(&change_dir, tmp_dir.path())?; + let copied = recurse_copy_lua(&change_dir, tmp_dir.path())?; println!("⏲️ {} files copied", copied.bright_green()); @@ -266,7 +265,7 @@ async fn serve(dev_mode: bool, port: u16, public_dir: Option<PathBuf>) -> Result server::Server::serve(lua, port, public_dir).await } -fn recurse_copy(from: &Path, to: &Path) -> Result<usize> { +fn recurse_copy_lua(from: &Path, to: &Path) -> Result<usize> { let mut copied = 0; for entry in std::fs::read_dir(from)? { let entry = entry?; @@ -276,8 +275,12 @@ fn recurse_copy(from: &Path, to: &Path) -> Result<usize> { if path.is_dir() { std::fs::create_dir(&to)?; - copied += recurse_copy(&path, &to)?; + copied += recurse_copy_lua(&path, &to)?; } else { + let ext = path.extension().and_then(|s| s.to_str()); + if ext != Some("lua") && ext != Some("luax") { + continue; + } std::fs::copy(&path, &to)?; copied += 1; } diff --git a/src/server.rs b/src/server.rs index 16b543e..307aa0c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,16 +5,16 @@ use std::path::PathBuf; use std::pin::Pin; use std::sync::{Arc, Mutex}; -use http_body_util::Full; +use http_body_util::{BodyExt, Collected, Full}; use hyper::body::{Bytes, Incoming}; use hyper::server::conn::http1; use hyper::service::Service; -use hyper::{Request, Response, StatusCode}; +use hyper::{Method, Request, Response, StatusCode}; use hyper_util::rt::TokioIo; use tokio::net::TcpListener; use crate::error::ReluaxError; -use crate::luax::table_to_html; +use crate::luax::{table_to_html, table_to_json}; use color_eyre::Result; use rlua::Lua; @@ -88,6 +88,57 @@ fn mk_file_response(path: PathBuf) -> Result<Response<Full<Bytes>>> { .body(Full::new(Bytes::from(bytes)))?) } +fn decode_luax_response(status: StatusCode, t: rlua::Table) -> Result<Response<Full<Bytes>>> { + if t.contains_key("type")? { + let ty: String = t.get("type")?; + let mime_type: Option<String> = t.get("mime_type")?; + + match ty.as_str() { + "html" => { + let html: rlua::Table = t.get("value")?; + let mut buf = Vec::new(); + table_to_html(html, &mut buf)?; + Ok(Response::builder() + .header("Content-Type", mime_type.unwrap_or("text/html".to_string())) + .status(status) + .body(Full::new(Bytes::from(buf)))?) + } + "json" => { + let json: rlua::Table = t.get("value")?; + let mut buf = Vec::new(); + table_to_json(json, &mut buf)?; + + Ok(Response::builder() + .header( + "Content-Type", + mime_type.unwrap_or("application/json".to_string()), + ) + .status(status) + .body(Full::new(Bytes::from(buf)))?) + } + "html-page" => { + let html: rlua::Table = t.get("value")?; + let mut buf = Vec::new(); + writeln!(&mut buf, "<!DOCTYPE html>")?; + table_to_html(html, &mut buf)?; + Ok(Response::builder() + .header("Content-Type", mime_type.unwrap_or("text/html".to_string())) + .status(status) + .body(Full::new(Bytes::from(buf)))?) + } + _ => Err(ReluaxError::Server("Unknown response type".to_string()).into()), + } + } else { + let mut buf = Vec::new(); + writeln!(&mut buf, "<!DOCTYPE html>")?; + table_to_html(t, &mut buf)?; + Ok(Response::builder() + .header("Content-Type", "text/html") + .status(status) + .body(Full::new(Bytes::from(buf)))?) + } +} + impl Service<Request<Incoming>> for State { type Response = Response<Full<Bytes>>; type Error = color_eyre::Report; @@ -95,16 +146,27 @@ impl Service<Request<Incoming>> for State { Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>; fn call(&self, req: Request<Incoming>) -> Self::Future { - let path = req.uri().path(); - let res = self.serve(path); + let path = req.uri().path().to_string(); + let method = req.method().clone(); + let lua = self.lua.clone(); + let public_dir = self.public_dir.clone(); + Box::pin(async { + let body = req.into_body().collect().await?; - Box::pin(async { res }) + Self::serve(lua, public_dir, path, method, body) + }) } } impl State { - fn serve(&self, path: &str) -> Result<Response<Full<Bytes>>> { - let lua = self.lua.lock().unwrap(); + fn serve( + lua: Arc<Mutex<Lua>>, + public_dir: Option<PathBuf>, + path: String, + method: Method, + body: Collected<Bytes>, + ) -> Result<Response<Full<Bytes>>> { + let lua = lua.lock().unwrap(); let res = lua.context(|ctx| -> Result<Response<Full<Bytes>>> { let manifest: rlua::Result<rlua::Table> = ctx.load("require('reluax')").eval(); @@ -123,7 +185,11 @@ impl State { let route: rlua::Function = manifest.get("route")?; - let res: rlua::Result<(rlua::Integer, rlua::Value)> = route.call((path,)); + let method = method.as_str(); + let body: Vec<u8> = body.to_bytes().to_vec(); + + let res: rlua::Result<(rlua::Integer, rlua::Value)> = + route.call((path.clone(), method, body)); let res = match res { Ok(r) => r, @@ -132,7 +198,7 @@ impl State { return mk_response( StatusCode::INTERNAL_SERVER_ERROR, - "Internal lua error".to_string(), + "Internal server error".to_string(), ); } }; @@ -140,10 +206,9 @@ impl State { let status = StatusCode::from_u16(res.0 as u16).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); - if status == StatusCode::NOT_FOUND && self.public_dir.is_some() { + if status == StatusCode::NOT_FOUND && public_dir.is_some() { // try to serve a static file - let path = self - .public_dir + let path = public_dir .clone() .unwrap() .join(path.trim_start_matches('/')); @@ -155,13 +220,7 @@ impl State { match res.1 { rlua::Value::String(s) => mk_response(status, s.to_str()?.to_string()), - rlua::Value::Table(t) => { - let mut buf = Vec::new(); - writeln!(&mut buf, "<!DOCTYPE html>")?; - table_to_html(t, &mut buf)?; - let s = String::from_utf8(buf).unwrap(); - mk_response(status, s) - } + rlua::Value::Table(t) => decode_luax_response(status, t), rlua::Value::Nil => Err(ReluaxError::Server("No route found".to_string()).into()), rlua::Value::Error(e) => Err(ReluaxError::Lua(e).into()), _ => Err(ReluaxError::Server("Route returned invalid type".to_string()).into()), |
