From 2d7e55161c7c2f92c50f63c9bfd6113e40a9a6e5 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Mon, 18 Dec 2023 02:14:48 -0800 Subject: [PATCH] push all mongo functions, start formatted static pages --- app.routes | 17 +- src/main.rs | 489 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 439 insertions(+), 67 deletions(-) diff --git a/app.routes b/app.routes index 0141a0c..e504b7c 100644 --- a/app.routes +++ b/app.routes @@ -25,7 +25,7 @@ db() { redis() { let db = redis::connect(); - db.set("some.key", json::dump(#{name: "John", id: 50})); + db.set("some.key", json::dump(#{name: "John Doe", id: 50})); let data = json::parse(db.get("some.key")); json(data) @@ -50,14 +50,21 @@ mongo(name) { mongo(name, collection) { let conn = mongo::connect().db(name).get(collection); let list = conn.find().collect(); - + json(#{count: list.count(), items: list}) } #[route("/mongo/test")] -one() { - let conn = mongo::connect().db("app").get("users"); - let list = conn.find_one(#{firstname: "John"}); +test() { + let conn = mongo::connect().db("app").create("users"); + + conn.insert([ + #{firstname: "John", lastname: "Doe", id: 50}, + #{firstname: "John", lastname: "Doe", id: 51}, + ]); + + let list = conn.find(#{firstname: "John"}).collect(); + conn.delete_many(#{firstname: "John"}); json(list) } diff --git a/src/main.rs b/src/main.rs index c41fd84..3a5ea74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,13 +15,13 @@ use std::{cell::RefCell, collections::BTreeMap, env, fs, sync::Arc}; use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; use tracing_subscriber::{filter::LevelFilter, prelude::*}; -use rhai::{packages::Package, plugin::*, serde::to_dynamic, Dynamic, Engine, FnNamespace, Map, ParseError, Scope, AST}; +use rhai::{packages::Package, plugin::*, serde::to_dynamic, Array, Dynamic, Engine, FnNamespace, Map, ParseError, Scope, AST}; use rhai_fs::FilesystemPackage; use rhai_url::UrlPackage; use mongodb::{ - bson::Document, - results::CollectionSpecification, + bson::{doc, Document}, + results::{CollectionSpecification, DeleteResult, InsertManyResult, InsertOneResult, UpdateResult}, sync::{Client as MongoClient, Collection, Cursor, Database}, }; @@ -120,6 +120,15 @@ fn match_route(route_template: &str, placeholders: &[&str], url: &str) -> Option Some(matched_placeholders) } +fn collection_exists(d: &Database, name: &String) -> Result> { + let filter = doc! { "name": &name }; + + match d.list_collection_names(Some(filter)) { + Err(err) => Err(err.to_string().into()), + Ok(list) => Ok(list.into_iter().any(|col| col == *name)), + } +} + fn match_segment(route_segment: &str, url_segment: &str, placeholders: &[&str]) -> Option { if route_segment.starts_with('{') && route_segment.ends_with('}') { let placeholder = &route_segment[1..route_segment.len() - 1]; @@ -178,7 +187,7 @@ mod json { pub fn parse<'s>(json: String) -> Result> { match serde_json::from_str(&json) { Ok(map) => Ok(map), - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), } } } @@ -201,24 +210,38 @@ mod mongo_db { unsafe impl Send for MongoDynamic {} unsafe impl Sync for MongoDynamic {} - impl FromIterator for Vec { - fn from_iter>(iter: I) -> Self { iter.into_iter().map(|mongo_dynamic| mongo_dynamic.0).collect() } + trait MongoVec { + fn into_vec(self) -> Vec; + } + + trait MongoDocument { + fn into_doc(self) -> Document; + fn into_map(self) -> MongoDynamic; + } + + impl Into for Dynamic { + fn into(self) -> MongoDynamic { MongoDynamic(self) } + } + + impl FromIterator for Array { + fn from_iter>(iter: I) -> Self { iter.into_iter().map(|m| m.0).collect() } } - trait IntoDocument { - fn into(self) -> Document; + impl MongoVec for Array { + fn into_vec(self) -> Vec { self.into_iter().map(|m| m.into_map()).collect() } } - impl IntoDocument for Map { - fn into(self) -> Document { + impl MongoDocument for Dynamic { + fn into_doc(self) -> Document { Document::from( serde_json::from_str(&match serde_json::to_string(&self) { Ok(data) => data, Err(err) => format!("{{\"err\": \"{err}\"}}"), }) - .expect("failed to deserialize"), + .unwrap_or(doc! {"err": format!("unable to deserialize {self}")}), ) } + fn into_map(self) -> MongoDynamic { MongoDynamic(to_dynamic(self.into_doc()).unwrap()) } } pub fn connect() -> Client { @@ -235,10 +258,10 @@ mod mongo_db { pub fn list_databases(conn: Client) -> Result> { match conn.client { Some(client) => match client.list_databases(None, None) { - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), Ok(list) => to_dynamic(list), }, - None => to_dynamic::>(vec![]), + None => to_dynamic::(vec![]), } } @@ -246,7 +269,7 @@ mod mongo_db { pub fn count_databases(conn: Client) -> Result> { match conn.client { Some(client) => match client.list_databases(None, None) { - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), Ok(list) => Ok(list.len() as i64), }, None => Ok(0), @@ -262,21 +285,35 @@ mod mongo_db { } #[rhai_fn(global, return_raw, name = "list")] - pub fn list_collections(conn: Mongo) -> Result> { - match conn.db { + pub fn list_collections(m: Mongo) -> Result> { + match m.db { Some(client) => match client.list_collections(None, None) { - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), Ok(list) => to_dynamic(list.map(|item| item.unwrap()).collect::>()), }, - None => to_dynamic::>(vec![]), + None => to_dynamic::(vec![]), } } #[rhai_fn(global, return_raw, name = "get")] - pub fn collection(conn: Mongo, name: String) -> Result, Box> { - match conn.db { + pub fn collection(m: Mongo, name: String) -> Result, Box> { + match m.db { Some(client) => Ok(client.collection(&name)), - None => Err("No collection found".into()), + None => Err("No database found".into()), + } + } + + #[rhai_fn(global, return_raw, name = "create")] + pub fn create_collection(m: Mongo, name: String) -> Result, Box> { + match m.db { + Some(client) => match collection_exists(&client, &name).unwrap() { + true => Ok(client.collection(&name)), + false => match client.create_collection(&name, None) { + Err(err) => Err(err.to_string().into()), + Ok(_) => Ok(client.collection(&name)), + }, + }, + None => Err("No database found".into()), } } @@ -284,7 +321,7 @@ mod mongo_db { pub fn count_collections(collection: Collection) -> Result> { match collection.count_documents(None, None) { Ok(count) => Ok(count as i64), - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), } } @@ -292,26 +329,26 @@ mod mongo_db { pub fn find_all(collection: Collection) -> Result>, Box> { match collection.find(None, None) { Ok(cursor) => Ok(Arc::new(cursor)), - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), } } #[rhai_fn(global, return_raw, name = "find_one")] - pub fn find_one(collection: Collection, filter: Map) -> Result> { - match collection.find_one(IntoDocument::into(filter), None) { + pub fn find_one(collection: Collection, filter: Dynamic) -> Result> { + match collection.find_one(filter.into_doc(), None) { Ok(cursor) => match cursor { Some(item) => to_dynamic::(item), None => to_dynamic::>(vec![]), }, - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), } } #[rhai_fn(global, return_raw, name = "find")] - pub fn find_filter(collection: Collection, filter: Map) -> Result>, Box> { - match collection.find(IntoDocument::into(filter), None) { + pub fn find_filter(collection: Collection, filter: Dynamic) -> Result>, Box> { + match collection.find(filter.into_doc(), None) { Ok(cursor) => Ok(Arc::new(cursor)), - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), } } @@ -324,45 +361,78 @@ mod mongo_db { } #[rhai_fn(global, name = "count")] - pub fn count_collect(items: Vec) -> i64 { items.iter().count() as i64 } + pub fn count_collect(items: Array) -> i64 { items.iter().count() as i64 } #[rhai_fn(global, return_raw, name = "collect")] pub fn collect(cursor: Arc>) -> Result> { match Arc::into_inner(cursor) { Some(cursor) => match cursor.collect() { - Ok(items) => to_dynamic::>(items), + Ok(items) => to_dynamic::(items), Err(err) => to_dynamic::(err.to_string()), }, - None => to_dynamic::>(vec![]), + None => to_dynamic::(vec![]), } } - #[rhai_fn(global)] - pub fn drop() -> bool { todo!() } - - #[rhai_fn(global)] - pub fn insert_one() -> bool { todo!() } - - #[rhai_fn(global)] - pub fn insert_many() -> bool { todo!() } + #[rhai_fn(global, name = "drop")] + pub fn drop_collection(collection: Collection) -> bool { + match collection.drop(None) { + Ok(_) => true, + Err(_) => false, + } + } - #[rhai_fn(global)] - pub fn delete_one() -> bool { todo!() } + #[rhai_fn(global, return_raw, name = "drop")] + pub fn drop_database(m: Mongo) -> Result> { + match m.db { + Some(client) => match client.drop(None) { + Ok(_) => Ok(true), + Err(err) => Err(err.to_string().into()), + }, + None => Err("No collection found".into()), + } + } - #[rhai_fn(global)] - pub fn delete_many() -> bool { todo!() } + #[rhai_fn(global, return_raw, name = "insert")] + pub fn insert_one(collection: Collection, map: Dynamic) -> Result> { + match collection.insert_one(map.into_map(), None) { + Ok(res) => to_dynamic::(res), + Err(err) => to_dynamic::(err.to_string()), + } + } - #[rhai_fn(global)] - pub fn replace_one() -> bool { todo!() } + #[rhai_fn(global, return_raw, name = "insert")] + pub fn insert_many(collection: Collection, map: Array) -> Result> { + match collection.insert_many(map.into_vec(), None) { + Ok(res) => to_dynamic::(res), + Err(err) => to_dynamic::(err.to_string()), + } + } - #[rhai_fn(global)] - pub fn find_and_replace() -> bool { todo!() } + #[rhai_fn(global, return_raw)] + pub fn delete(collection: Collection, map: Dynamic) -> Result> { + match collection.delete_one(map.into_doc(), None) { + Ok(res) => to_dynamic::(res), + Err(err) => to_dynamic::(err.to_string()), + } + } - #[rhai_fn(global)] - pub fn find_and_update() -> bool { todo!() } + #[rhai_fn(global, return_raw)] + pub fn delete_many(collection: Collection, map: Dynamic) -> Result> { + match collection.delete_many(map.into_doc(), None) { + Ok(res) => to_dynamic::(res), + Err(err) => to_dynamic::(err.to_string()), + } + } - #[rhai_fn(global)] - pub fn find_and_delete() -> bool { todo!() } + #[rhai_fn(global, return_raw)] + pub fn update(collection: Collection, query: Dynamic, replacement: Dynamic) -> Result> { + let replacement: MongoDynamic = replacement.into(); + match collection.replace_one(query.into_doc(), replacement, None) { + Ok(res) => to_dynamic::(res), + Err(err) => to_dynamic::(err.to_string()), + } + } } #[export_module] @@ -381,8 +451,8 @@ mod kv_db { 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(()), + Err(err) => Err(err.to_string().into()), } } @@ -436,8 +506,8 @@ mod redis_db { pub fn set_string(redis: Redis, key: String, value: String) -> Result<(), Box> { let mut conn = redis.client.unwrap().get_connection().unwrap(); match conn.set::(key, value) { - Err(err) => Err(format!("{}", &err).into()), Ok(_) => Ok(()), + Err(err) => Err(err.to_string().into()), } } @@ -445,8 +515,8 @@ mod redis_db { pub fn set_i64(redis: Redis, key: String, value: i64) -> Result<(), Box> { let mut conn = redis.client.unwrap().get_connection().unwrap(); match conn.set::(key, value) { - Err(err) => Err(format!("{}", &err).into()), Ok(_) => Ok(()), + Err(err) => Err(err.to_string().into()), } } @@ -716,7 +786,7 @@ mod http { let body = str!(res.body.clone().unwrap()); match serde_json::from_str(body) { Ok(map) => Ok(map), - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), } } @@ -728,7 +798,7 @@ mod http { }; match serde_json::from_str(&format!("{{\"message\":{err}}}")) { Ok(msg) => Ok(msg), - Err(err) => Err(format!("{}", &err).into()), + Err(err) => Err(err.to_string().into()), } } } @@ -883,7 +953,7 @@ async fn handler(url: Path, req: HttpRequest, config: Data) -> i ternary!(has_wildcard, R_WILD.as_ref().unwrap().replace_all(&result, "fn _wildcard() {").to_string(), result) }; - let mut ast = match engine.compile(contents) { + let mut ast = match engine.compile(&contents) { Ok(ast) => ast, Err(err) => error(&engine, &path, err), }; @@ -902,6 +972,29 @@ async fn handler(url: Path, req: HttpRequest, config: Data) -> i return HttpResponse::build(status_code).content_type(content_type).body(body); }; + fn extract_context(contents: String, err: String) -> String { + let re = Regex::new(r"line (\d+)").unwrap(); + + if let Some(captures) = re.captures(&err).unwrap() { + if let Some(num) = captures.get(1) { + if let Ok(line_number) = num.as_str().parse::() { + let lines: Vec<&str> = contents.lines().collect(); + let start_line = line_number.saturating_sub(3); + let end_line = (line_number + 4).min(lines.len()); + + return lines[start_line..end_line] + .iter() + .enumerate() + .map(|(i, line)| format!("
{:>4} | {}
", start_line + i + 1, line)) + .collect::>() + .join("\n"); + } + } + } + + "".to_string() + } + for (route, args) in routes { let url = url.clone(); let args: Vec<&str> = args.iter().map(AsRef::as_ref).collect(); @@ -920,7 +1013,281 @@ async fn handler(url: Path, req: HttpRequest, config: Data) -> i return HttpResponse::build(status_code).content_type(content_type).body(body); } Err(err) => { - return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).body(format!("Internal Server Error\n\n{err}")); + return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).content_type(ContentType::html()).body(format!( + r#" + + + + 500: Internal Server Error +
+
+

+ 500: Internal Server Error + {} +

+ +
{}
+
+
+ "#, + err.to_string().replace("\n", "
"), + extract_context(contents, err.to_string()) + )) } } } @@ -938,9 +1305,7 @@ async fn handler(url: Path, req: HttpRequest, config: Data) -> i ); return HttpResponse::build(status_code).content_type(content_type).body(body); } - Err(err) => { - return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).body(format!("Internal Server Error\n\n{err}")); - } + Err(err) => return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).body(format!("Internal Server Error\n\n{err}\n\n{}", extract_context(contents, err.to_string()))), }, None => {} } -- GitLab