aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStanisław Zagórowski <hasohasacz@gmail.com>2024-01-19 21:56:26 +0100
committerStanisław Zagórowski <hasohasacz@gmail.com>2024-01-19 21:56:26 +0100
commit2e77359c2f1cab769e34cde5c0dfb90a2d52b00b (patch)
treef9ef34db90e1247a20eadb91364d1a57de124281 /src
parent0e5bb91658dc4a81213c986442ce1662ba4d0ffc (diff)
feature: add method and body to route params
Diffstat (limited to 'src')
-rw-r--r--src/error.rs2
-rw-r--r--src/luax/mod.rs54
-rw-r--r--src/main.rs11
-rw-r--r--src/server.rs99
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()),