diff --git a/.gitignore b/.gitignore index 80e213943d9dbd13f5feff472b5ed7c197211940..fe772498382127ec338b7a6f64a738ffed962a40 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ build target tests -bin # maid .maid/cache @@ -14,4 +13,8 @@ bin .env.production # backup -.bak \ No newline at end of file +.bak + +# bin +bin +config.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4aed173ce8a1261e2bd46752cb6859d63ac98b0e..cc7cfad6bb47de7163588493af957af100ee967a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,6 +234,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "autocfg" version = "1.1.0" @@ -261,6 +267,15 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -355,6 +370,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "const-random" version = "0.1.17" @@ -613,6 +638,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "getrandom" version = "0.2.11" @@ -642,13 +677,25 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.3" @@ -661,6 +708,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "http" version = "0.2.11" @@ -742,6 +798,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -749,7 +815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", ] [[package]] @@ -809,6 +875,12 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -905,6 +977,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -983,6 +1065,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1045,6 +1133,19 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pickledb" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53a5ade47760e8cc4986bdc5e72daeffaaaee64cbc374f9cfe0a00c1cd87b1f" +dependencies = [ + "bincode", + "serde", + "serde_cbor", + "serde_json", + "serde_yaml", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1305,10 +1406,14 @@ name = "script" version = "0.2.3" dependencies = [ "actix-web", + "anyhow", + "colored", "fancy-regex", + "home", "lazy_static", "macros-rs", "peg", + "pickledb", "reqwest", "rhai", "rhai-fs", @@ -1317,6 +1422,10 @@ dependencies = [ "serde_json", "smartstring", "termcolor", + "toml", + "tracing", + "tracing-bunyan-formatter", + "tracing-subscriber", ] [[package]] @@ -1357,6 +1466,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.193" @@ -1379,6 +1498,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1391,6 +1519,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap 1.9.3", + "ryu", + "serde", + "yaml-rust", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1402,6 +1542,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1532,6 +1681,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.30" @@ -1627,6 +1786,40 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -1641,9 +1834,39 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tracing-bunyan-formatter" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373" +dependencies = [ + "ahash", + "gethostname", + "log", + "serde", + "serde_json", + "time", + "tracing", + "tracing-core", + "tracing-log 0.1.4", + "tracing-subscriber", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -1651,6 +1874,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log 0.2.0", ] [[package]] @@ -1697,6 +1957,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1963,6 +2229,15 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "winnow" +version = "0.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -1973,6 +2248,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zerocopy" version = "0.7.30" diff --git a/Cargo.toml b/Cargo.toml index 547fe23e5194da64497ef4dd78436238026dbcf7..7d4da73117ebf80e8beda13b1ef54e4da387d80d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,15 +8,23 @@ description = "barebones http scripting" [dependencies] peg = "0.8.2" +toml = "0.8.8" +home = "0.5.5" rhai-fs = "0.1.3" +colored = "2.1.0" +anyhow = "1.0.75" rhai-url = "0.0.5" actix-web = "4.4.0" macros-rs = "0.5.0" termcolor = "1.4.0" lazy_static = "1.4.0" +smartstring = "1.0.1" serde_json = "1.0.108" -fancy-regex = "0.12.0" serde = { version = "1.0.193", features = ["derive"] } +regex = { package = "fancy-regex", version = "0.12.0" } reqwest = { version = "0.11.22", features = ["blocking"] } rhai = { version = "1.16.3", features = ["serde_json", "serde"] } -smartstring = "1.0.1" +tracing = "0.1.40" +tracing-bunyan-formatter = "0.3.9" +tracing-subscriber = "0.3.18" +pickledb = { version = "0.5.1", features = ["json", "bincode", "cbor", "yaml"] } diff --git a/Maidfile.toml b/Maidfile.toml index 8a02deb9b7609a0abf8b64af9a4ef7965fdb3c24..933b42df938dc4bc877d5462403f6999ba514b81 100644 --- a/Maidfile.toml +++ b/Maidfile.toml @@ -4,7 +4,7 @@ version = "0.2.3" [tasks] clean = { script = ["rm -rf bin", "mkdir bin"] } -run = { script = ["maid build -q", "./bin/script"] } +run = { script = ["maid build -q", "bash -c './bin/script | bunyan'"] } [tasks.build] depends = ["clean"] diff --git a/app.routes b/app.routes index f5d3cfdfb824c0a12eb34cc4fa2a9c4e30d1a107..7f4aeea7cdcad1f6248d0ad37762fc31ae3f6f3a 100644 --- a/app.routes +++ b/app.routes @@ -3,7 +3,7 @@ index { } hello() { - text("Hello World!") + response("Hello World!", "text", 200) } #[route("/example")] @@ -11,6 +11,16 @@ example() { html(http::get("https://example.org").body) } +#[route("/db")] +db() { + let db = kv::load("test.db"); + + db.set("some.key", json::dump(#{name: "John", id: 50})); + let data = json::parse(db.get("some.key")); + + json(data) +} + #[route("/example/{id}.txt")] example(id) { text("base: " + id) @@ -54,10 +64,10 @@ test.json() { let res = #{ hello: "world", info: #{ - path: path, - url: url, - ver: ver, - query: query, + path: request.path, + url: request.url, + ver: request.version, + query: request.query, } }; diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..9f80b2fbfce28c20c7e4b6124a47e69508bb68ee --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,39 @@ +pub mod structs; + +use crate::file::{self, exists}; +use colored::Colorize; +use macros_rs::{crashln, string}; +use std::fs; +use structs::{Config, Settings}; + +pub fn read() -> Config { + let config_path = format!("config.toml"); + + if !exists::file(config_path.clone()).unwrap() { + let config = Config { + env: None, + database: None, + workers: vec!["app.routes".into()], + settings: Settings { + address: string!("127.0.0.1"), + port: 3500, + }, + }; + + let contents = match toml::to_string(&config) { + Ok(contents) => contents, + Err(err) => crashln!("Cannot parse config.\n{}", string!(err).white()), + }; + + if let Err(err) = fs::write(&config_path, contents) { + crashln!("Error writing config.\n{}", string!(err).white()) + } + tracing::info!(path = config_path, created = true, "config"); + } + + file::read(config_path) +} + +impl Config { + pub fn get_address(&self) -> (String, u16) { (self.settings.address.clone(), self.settings.port.clone()) } +} diff --git a/src/config/structs.rs b/src/config/structs.rs new file mode 100644 index 0000000000000000000000000000000000000000..72a5a820990be3e86cb60d84563b4f1fc6900f7c --- /dev/null +++ b/src/config/structs.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf}; +use toml::Value; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Config { + pub workers: Vec, + pub settings: Settings, + pub database: Option, + pub env: Option>, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Settings { + pub address: String, + pub port: u16, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Database { + pub kv: Option, + pub sqlite: Option, + pub mongo: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct KV { + pub method: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SQlite { + pub connect: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Mongo { + pub database: String, + pub address: String, + pub port: u64, + pub auth: MongoAuth, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct MongoAuth { + pub username: String, + pub password: String, +} diff --git a/src/database/kv.rs b/src/database/kv.rs new file mode 100644 index 0000000000000000000000000000000000000000..3e2ad6d7d5f9a45a270240e13d3d87d2ef6633ae --- /dev/null +++ b/src/database/kv.rs @@ -0,0 +1,25 @@ +use macros_rs::string; +use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod}; + +use crate::config::{ + self, + structs::{Database, KV}, +}; + +pub fn load(path: String) -> PickleDb { + let config = config::read().database.unwrap_or(Database { + kv: Some(KV { method: string!("default") }), + mongo: None, + sqlite: None, + }); + + let method = match config.kv.unwrap().method.as_str() { + "json" | "default" => SerializationMethod::Json, + "yaml" | "yml" => SerializationMethod::Yaml, + "cbor" | "conbin" => SerializationMethod::Bin, + "binary" | "bin" => SerializationMethod::Bin, + _ => SerializationMethod::Bin, + }; + + PickleDb::new(path, PickleDbDumpPolicy::AutoDump, method) +} diff --git a/src/database/mod.rs b/src/database/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..05c6d5b95ef33f9ffcfe34f0e75e1c5279ced62e --- /dev/null +++ b/src/database/mod.rs @@ -0,0 +1 @@ +pub mod kv; diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000000000000000000000000000000000000..a097ee44720da487e1311581228f59637c6e33e9 --- /dev/null +++ b/src/file.rs @@ -0,0 +1,35 @@ +use colored::Colorize; +use macros_rs::{crashln, str, string}; +use rhai::plugin::*; + +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +pub fn cwd() -> PathBuf { + match env::current_dir() { + Ok(path) => path, + Err(_) => crashln!("Unable to find current working directory"), + } +} + +#[export_module] +pub mod exists { + #[rhai_fn(global, return_raw, name = "folder")] + pub fn folder(dir_name: String) -> Result> { Ok(Path::new(str!(dir_name)).is_dir()) } + #[rhai_fn(global, return_raw, name = "file")] + pub fn file(file_name: String) -> Result> { Ok(Path::new(str!(file_name)).exists()) } +} + +pub fn read(path: String) -> T { + let contents = match fs::read_to_string(&path) { + Ok(contents) => contents, + Err(err) => crashln!("Cannot find {path}.\n{}", string!(err).white()), + }; + + match toml::from_str(&contents).map_err(|err| string!(err)) { + Ok(parsed) => parsed, + Err(err) => crashln!("Cannot parse {path}.\n{}", err.white()), + } +} diff --git a/src/main.rs b/src/main.rs index 110c7b7f220403ad454c5b3169e216a4a1cad125..58ba87c2e52fbac1ddf41201555705cdf1d23800 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,29 @@ -use fancy_regex::{Captures, Error, Regex}; +mod config; +mod database; +mod file; + +use config::structs::Config; use lazy_static::lazy_static; use macros_rs::{crashln, str, string, ternary}; +use pickledb::PickleDb; +use regex::{Captures, Error, Regex}; use reqwest::blocking::Client; -use serde::{Deserialize, Serialize}; use smartstring::alias::String as SmString; -use std::{collections::BTreeMap, fs, path::PathBuf}; +use std::{cell::RefCell, collections::BTreeMap, env, fs}; +use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; +use tracing_subscriber::{filter::LevelFilter, prelude::*}; use rhai::{packages::Package, plugin::*, Dynamic, Engine, FnNamespace, Map, ParseError, Scope, AST}; use rhai_fs::FilesystemPackage; use rhai_url::UrlPackage; -use actix_web::{get, http::header::ContentType, http::StatusCode, web::Path, App, HttpRequest, HttpResponse, HttpServer, Responder}; +use actix_web::{ + get, + http::header::ContentType, + http::StatusCode, + web::{Data, Path}, + App, HttpRequest, HttpResponse, HttpServer, Responder, +}; // convert to peg lazy_static! { @@ -37,7 +50,7 @@ fn route_to_fn(input: &str) -> String { let re = Regex::new(r#"\{([^{}\s]+)\}"#).unwrap(); let re_dot = Regex::new(r"\.(\w+)").unwrap(); - let result = re.replace_all(&input, |captures: &fancy_regex::Captures| { + let result = re.replace_all(&input, |captures: ®ex::Captures| { let content = captures.get(1).map_or("", |m| m.as_str()); format!("_arg_{content}") }); @@ -60,6 +73,22 @@ fn error(engine: &Engine, path: &str, err: ParseError) -> AST { } } +pub fn response(data: String, content_type: String, status_code: i64) -> (String, ContentType, StatusCode) { + let content_type = match content_type.as_str() { + "xml" => ContentType::xml(), + "png" => ContentType::png(), + "html" => ContentType::html(), + "json" => ContentType::json(), + "jpeg" => ContentType::jpeg(), + "text" => ContentType::plaintext(), + "stream" => ContentType::octet_stream(), + "form" => ContentType::form_url_encoded(), + _ => ContentType::plaintext(), + }; + + (data, content_type, convert_status(status_code)) +} + fn match_route(route_template: &str, placeholders: &[&str], url: &str) -> Option> { let mut matched_placeholders = Vec::new(); @@ -128,9 +157,58 @@ mod status { } } +#[export_module] +mod json { + pub fn dump<'s>(object: Map) -> String { + match serde_json::to_string(&object) { + Ok(result) => result, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global, return_raw, name = "parse")] + pub fn parse<'s>(json: String) -> Result> { + match serde_json::from_str(&json) { + Ok(map) => Ok(map), + Err(err) => Err(format!("{}", &err).into()), + } + } +} + +#[export_module] +mod kv { + #[derive(Clone)] + pub struct KV<'s> { + pub db: &'s RefCell, + } + + pub fn load<'s>(path: String) -> KV<'s> { + let db = RefCell::new(database::kv::load(path)); + KV { db: Box::leak(Box::new(db)) } + } + + #[rhai_fn(global, pure, return_raw, name = "set")] + pub fn set(conn: &mut KV, key: String, value: String) -> Result<(), Box> { + let mut db = conn.db.borrow_mut(); + match db.set(&key, &value) { + Err(err) => Err(format!("{}", &err).into()), + Ok(_) => Ok(()), + } + } + + #[rhai_fn(global, name = "get")] + pub fn get(conn: KV, key: String) -> String { + let db = conn.db.borrow(); + match db.get::(&key) { + Some(data) => data, + None => string!(""), + } + } +} + #[export_module] mod http { - #[derive(Debug, Clone, Serialize, Deserialize)] + #[derive(Clone)] pub struct Http { pub length: Option, pub status: u16, @@ -272,17 +350,20 @@ mod http { } #[get("{url:.*}")] -async fn handler(url: Path, req: HttpRequest) -> impl Responder { +async fn handler(url: Path, req: HttpRequest, config: Data) -> impl Responder { if url.as_str() == "favicon.ico" { return HttpResponse::Ok().body(""); } let mut routes: BTreeMap> = BTreeMap::new(); - let filename: PathBuf = "app.routes".into(); + let filename = &config.workers.get(0).unwrap(); let fs_pkg = FilesystemPackage::new(); let url_pkg = UrlPackage::new(); + + let json = exported_module!(json); let http = exported_module!(http); + let exists = exported_module!(file::exists); let mut engine = Engine::new(); let mut scope = Scope::new(); @@ -294,15 +375,53 @@ async fn handler(url: Path, req: HttpRequest) -> impl Responder { fs_pkg.register_into_engine(&mut engine); url_pkg.register_into_engine(&mut engine); + + engine.register_static_module("json", json.into()); engine.register_static_module("http", http.into()); + engine.register_static_module("exists", exists.into()); + + if let Some(database) = &config.database { + if let Some(_) = &database.kv { + let kv = exported_module!(kv); + engine.register_static_module("kv", kv.into()); + } + if let Some(_) = &database.sqlite {} + if let Some(_) = &database.mongo {} + } - scope - .push_constant("path", url.to_string()) - .push_constant("url", req.uri().to_string()) - .push_constant("ver", format!("{:?}", req.version())) - .push_constant("query", req.query_string().to_string()); + #[derive(Clone)] + struct Request { + path: String, + url: String, + version: String, + query: String, + } + + impl Request { + fn to_dynamic(&self) -> Dynamic { + let mut map = Map::new(); + + map.insert(SmString::from("path"), Dynamic::from(self.path.clone())); + map.insert(SmString::from("url"), Dynamic::from(self.url.clone())); + map.insert(SmString::from("version"), Dynamic::from(self.version.clone())); + map.insert(SmString::from("query"), Dynamic::from(self.query.clone())); + + Dynamic::from(map) + } + } + + let request = Request { + path: url.to_string(), + url: req.uri().to_string(), + version: format!("{:?}", req.version()), + query: req.query_string().to_string(), + }; + + scope.push("request", request.to_dynamic()); engine + .register_fn("cwd", file::cwd) + .register_fn("response", response) .register_fn("text", default::text) .register_fn("json", default::json) .register_fn("html", default::html) @@ -326,24 +445,24 @@ async fn handler(url: Path, req: HttpRequest) -> impl Responder { let re = Regex::new(pattern).unwrap(); let re_combine = Regex::new(pattern_combine).unwrap(); - let result = - re.replace_all(&contents, |captures: &fancy_regex::Captures| { - let content = captures.get(1).map_or("", |m| m.as_str()); - format!("_arg_{content}") - }); + let result = re.replace_all(&contents, |captures: ®ex::Captures| { + let content = captures.get(1).map_or("", |m| m.as_str()); + format!("_arg_{content}") + }); let output = result.replace("#[route(\"", "_route").replace("\")]", ""); - re_combine.replace_all(str!(output), |captures: &fancy_regex::Captures| { + re_combine.replace_all(str!(output), |captures: ®ex::Captures| { let path = captures.get(1).map_or("", |m| m.as_str()); let args = captures.get(3).map_or("", |m| m.as_str()); if args != "" { let r_path = Regex::new(r"(?m)_arg_(\w+)").unwrap(); - let key = r_path.replace_all(&path, |captures: &fancy_regex::Captures| { - let key = captures.get(1).map_or("", |m| m.as_str()); - format!("{{{key}}}") - }); + let key = + r_path.replace_all(&path, |captures: ®ex::Captures| { + let key = captures.get(1).map_or("", |m| m.as_str()); + format!("{{{key}}}") + }); routes.insert(string!(key), args.split(",").map(|s| s.to_string().replace(" ", "")).collect()); format!("fmt_{path}({args})") @@ -383,7 +502,13 @@ async fn handler(url: Path, req: HttpRequest) -> impl Responder { if url.clone() == "" && has_index { let (body, content_type, status_code) = engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, "_route_index", ()).unwrap(); - println!("{}: {} (status={}, type={})", req.method(), req.uri(), status_code, content_type); + tracing::info!( + method = string!(req.method()), + status = string!(status_code), + content = string!(content_type), + "request '{}'", + req.uri() + ); return HttpResponse::build(status_code).content_type(content_type).body(body); }; @@ -395,7 +520,13 @@ async fn handler(url: Path, req: HttpRequest) -> impl Responder { match engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, convert_to_format(&url.clone()), ()) { Ok(response) => { let (body, content_type, status_code) = response; - println!("{}: {} (status={}, type={})", req.method(), req.uri(), status_code, content_type); + tracing::info!( + method = string!(req.method()), + status = string!(status_code), + content = string!(content_type), + "request '{}'", + req.uri() + ); return HttpResponse::build(status_code).content_type(content_type).body(body); } Err(err) => { @@ -408,7 +539,13 @@ async fn handler(url: Path, req: HttpRequest) -> impl Responder { Some(data) => match engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, route_to_fn(&route), data) { Ok(response) => { let (body, content_type, status_code) = response; - println!("{}: {} (status={}, type={})", req.method(), req.uri(), status_code, content_type); + tracing::info!( + method = string!(req.method()), + status = string!(status_code), + content = string!(content_type), + "request '{}'", + req.uri() + ); return HttpResponse::build(status_code).content_type(content_type).body(body); } Err(err) => { @@ -435,15 +572,33 @@ async fn handler(url: Path, req: HttpRequest) -> impl Responder { }; let status_code = ternary!(has_wildcard, status_code, StatusCode::NOT_FOUND); - println!("{}: {} (status={}, type={})", req.method(), req.uri(), status_code, content_type); + tracing::info!( + method = string!(req.method()), + status = string!(status_code), + content = string!(content_type), + "request '{}'", + req.uri() + ); return HttpResponse::build(status_code).content_type(content_type).body(body); } #[actix_web::main] async fn main() -> std::io::Result<()> { - let app = || App::new().service(handler); - let addr = ("127.0.0.1", 3000); + env::set_var("RUST_LOG", "INFO"); + + let config = config::read(); + let app = || App::new().app_data(Data::new(config::read())).service(handler); + + let formatting_layer = BunyanFormattingLayer::new("server".into(), std::io::stdout) + .skip_fields(vec!["file", "line"].into_iter()) + .expect("Unable to create logger"); + + tracing_subscriber::registry() + .with(LevelFilter::from(tracing::Level::INFO)) + .with(JsonStorageLayer) + .with(formatting_layer) + .init(); - println!("listening on {:?}", addr); - HttpServer::new(app).bind(addr).unwrap().run().await + tracing::info!(address = config.settings.address, port = config.settings.port, "server started"); + HttpServer::new(app).bind(config.get_address()).unwrap().run().await } diff --git a/test.db b/test.db new file mode 100644 index 0000000000000000000000000000000000000000..6d287350dca172464b381c69df739bf7e92f9ba6 --- /dev/null +++ b/test.db @@ -0,0 +1 @@ +[{"some.key":"\"{\\\"id\\\":50,\\\"name\\\":\\\"John\\\"}\""},{}] \ No newline at end of file