diff --git a/.env b/.env new file mode 100644 index 0000000..c0c68b1 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +PORT=3000 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index c8d7834..327b573 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +axum = {version = "0.7.4"} serde = { version = "1.0", features = ["derive"] } +tokio = { version = "1.0", features = ["full"] } serde_json = "1.0" +tower-http = { version = "0.4.0", features = ["full"] } +dotenv = "0.15.0" rocksdb = "0.22.0" + +pretty_assertions = "0.7" +select = "0.5" diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..78d6859 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1 @@ +pub mod parameters; \ No newline at end of file diff --git a/src/config/parameters.rs b/src/config/parameters.rs new file mode 100644 index 0000000..b530dac --- /dev/null +++ b/src/config/parameters.rs @@ -0,0 +1,13 @@ +use dotenv; + +// load the env file +pub fn init() { + dotenv::dotenv().ok().expect("Failed to load .env file"); +} + +// get the parameters from the env file and throw errors appropriately +pub fn get(parameter: &str) -> String { + let env_parameter = std::env::var(parameter) + .expect(&format!("{} is not defined in the environment", parameter)); + env_parameter +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..ee0c763 --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,2 @@ +pub mod namespace_handler; +pub mod table_handler; \ No newline at end of file diff --git a/src/handlers/namespace_handler.rs b/src/handlers/namespace_handler.rs new file mode 100644 index 0000000..127dabd --- /dev/null +++ b/src/handlers/namespace_handler.rs @@ -0,0 +1,74 @@ +use axum::{ + extract::{Json, Path}, + http::StatusCode, + response::IntoResponse, +}; + + + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct Namespace { + +} + + +pub async fn list_namespaces() -> Json> { + // Logic to list namespaces + let namespaces: Vec = vec!["accounting".to_string(), "tax".to_string(), "paid".to_string()]; + Json(namespaces) +} + +pub async fn create_namespace(new_namespace: Json) -> Json { + // Logic to create a new namespace + + // Logic to persist the namespace and add properties + new_namespace +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct NamespaceMetadata { + // Define your namespace metadata properties here + // Example: pub metadata_property: String, + data: String, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct NamespaceProperties { + data: String, +} + +pub async fn load_namespace_metadata(Path(namespace): Path) -> Json { + print!("Namespaces: {}", namespace); + // Logic to load metadata properties for a namespace + let metadata = NamespaceMetadata { + data: namespace, + // Populate with actual metadata properties + }; + Json(metadata) +} + +pub async fn namespace_exists(Path(namespace): Path) -> impl IntoResponse { + // Logic to check if a namespace exists + // This route just needs to return a status code, no body required + // Return HTTP status code 200 to indicate namespace exists + StatusCode::FOUND +} + +pub async fn drop_namespace(Path(namespace): Path) -> impl IntoResponse { + // Logic to drop a namespace from the catalog + // Ensure the namespace is empty before dropping + // Return HTTP status code 204 to indicate successful deletion + StatusCode::NO_CONTENT +} + +pub async fn set_namespace_properties(Path(namespace): Path) -> Json { + // Logic to set and/or remove properties on a namespace + // Deserialize request body and process properties + // Return HTTP status code 200 to indicate success + + let prop = NamespaceProperties { + data: "namespace properties".to_string(), + }; + + Json(prop) +} diff --git a/src/handlers/table_handler.rs b/src/handlers/table_handler.rs new file mode 100644 index 0000000..a898129 --- /dev/null +++ b/src/handlers/table_handler.rs @@ -0,0 +1,59 @@ +use axum::{ + extract::{Json, Path}, + http::StatusCode, + response::IntoResponse, +}; + + +pub async fn list_tables(Path(namespace): Path) -> Json> { + // Dummy response for demonstration + let tables: Vec = vec!["accounting".to_string(), "tax".to_string(), "paid".to_string()]; + Json(tables) + +} + +pub async fn create_table(Path(namespace): Path) -> impl IntoResponse { + // Logic to create a table in the given namespace + "Table created".to_string() +} + +pub async fn register_table(Path(namespace): Path) -> impl IntoResponse { + // Logic to register a table in the given namespace using metadata file location + "Table registered".to_string() +} + +pub async fn load_table(Path((namespace, table)): Path<(String, String)>) -> impl IntoResponse { + // Logic to load a table from the catalog + Json(table) +} + +pub async fn delete_table(Path((namespace, table)): Path<(String, String)>) -> impl IntoResponse { + // Logic to drop a table from the catalog + "Table dropped".to_string() +} + +pub async fn table_exists(Path((namespace, table)): Path<(String, String)>) -> impl IntoResponse { + // Logic to check if a table exists within a given namespace + StatusCode::OK +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct MetricsReport { + +} + +// Handler functions +pub async fn rename_table(table_rename: String) -> impl IntoResponse { + // Logic to rename a table from its current name to a new name + "Table renamed".to_string() +} + +pub async fn report_metrics(Path((namespace, table)): Path<(String, String)>) -> impl IntoResponse { + // Logic to process metrics report + Json(table) +} + +pub async fn find_tuple_location(Path((namespace, table, tuple_id)): Path<(String, String, String)>) -> impl IntoResponse { + // Logic to return the physical file location for a given tuple ID + format!("Physical file location for tuple ID {} of table {} in namespace {}.", tuple_id, table, namespace) +} diff --git a/src/main.rs b/src/main.rs index 2fe40a1..11b2b1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,17 @@ -fn main() { - println!("hello world!"); +mod routes; +mod dto; +mod handlers; +mod config; +mod tests; + +use crate::config::parameters; + +#[tokio::main] +async fn main() { + parameters::init(); + let host = format!("0.0.0.0:{}", parameters::get("PORT")); + + let listener = tokio::net::TcpListener::bind(host).await.unwrap(); + let app = routes::root::routes(); + axum::serve(listener, app).await.unwrap(); } diff --git a/src/routes/mod.rs b/src/routes/mod.rs new file mode 100644 index 0000000..136fb5a --- /dev/null +++ b/src/routes/mod.rs @@ -0,0 +1,3 @@ +pub mod namespace; +pub mod table; +pub mod root; \ No newline at end of file diff --git a/src/routes/namespace.rs b/src/routes/namespace.rs new file mode 100644 index 0000000..34e2e46 --- /dev/null +++ b/src/routes/namespace.rs @@ -0,0 +1,13 @@ +use axum::{ routing::{get, post, head, delete}, Router}; +use crate::handlers::namespace_handler; + +pub fn routes() -> Router<> { + let router = Router::new() + .route("/namespaces", get(namespace_handler::list_namespaces)) + .route("/namespaces", post(namespace_handler::create_namespace)) + .route("/namespace/:namespace", get(namespace_handler::load_namespace_metadata)) + .route("/namespace/:namespace", head(namespace_handler::namespace_exists)) + .route("/namespace/:namespace", delete(namespace_handler::drop_namespace)) + .route("/namespace/:namespace/properties", post(namespace_handler::set_namespace_properties)); + return router; +} diff --git a/src/routes/root.rs b/src/routes/root.rs new file mode 100644 index 0000000..34e10c1 --- /dev/null +++ b/src/routes/root.rs @@ -0,0 +1,14 @@ +use crate::routes::{namespace, table}; +use axum::routing::IntoMakeService; +use axum::Router; +use tower_http::trace::TraceLayer; + +pub fn routes() -> Router { + + // merge the 2 routes + let app_router = Router::new() + .nest("/", table::routes()) + .nest("/", namespace::routes()); + + app_router +} diff --git a/src/routes/table.rs b/src/routes/table.rs new file mode 100644 index 0000000..a3ba5ef --- /dev/null +++ b/src/routes/table.rs @@ -0,0 +1,18 @@ +use axum::{ routing::{get, post, head, delete}, Router}; +use crate::handlers::table_handler; + + +pub fn routes() -> Router<> { + let router = Router::new() + .route("/namespaces/:namespace/tables", get(table_handler::list_tables)) + .route("/namespaces/:namespace/tables", post(table_handler::create_table)) + .route("/namespaces/:namespace/register", post(table_handler::register_table)) + .route("/namespaces/:namespace/tables/:table", get(table_handler::load_table)) + .route("/namespaces/:namespace/tables/:table", delete(table_handler::delete_table)) + .route("/namespaces/:namespace/tables/:table", head(table_handler::table_exists)) + .route("/tables/rename", post(table_handler::rename_table)) + .route("/namespaces/:namespace/tables/:table/metrics", post(table_handler::report_metrics)) + .route("/namespaces/:namespace/tables/:table/find/:tuple_id", get(table_handler::find_tuple_location)); + + return router; +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/namespace_test.rs b/src/tests/namespace_test.rs new file mode 100644 index 0000000..3e6e447 --- /dev/null +++ b/src/tests/namespace_test.rs @@ -0,0 +1,43 @@ +use axum::{http::StatusCode, response::Json}; +use axum::extract::Json as JsonExtractor; +use axum::handler::post; +use axum::routing::Router; +use serde_json::json; +use axum::test::extract; + +use crate::{create_namespace, list_namespaces, Namespace}; + +#[tokio::test] +async fn test_list_namespaces() { + // Create a test router with the list_namespaces route + let app = Router::new().route("/namespaces", post(list_namespaces)); + + // Perform a request to the route + let response = axum::test::call(&app, axum::test::request::Request::post("/namespaces").body(()).unwrap()).await; + + // Ensure that the response status code is OK + assert_eq!(response.status(), StatusCode::OK); + + // Ensure that the response body contains the expected JSON data + let body = extract::>>(response.into_body()).await.unwrap(); + assert_eq!(body.0, vec!["accounting", "tax", "paid"]); +} + +#[tokio::test] +async fn test_create_namespace() { + // Create a test router with the create_namespace route + let app = Router::new().route("/namespaces", post(create_namespace)); + + // Create a JSON payload representing a new namespace + let payload = json!({}); + + // Perform a request to the route with the JSON payload + let response = axum::test::call(&app, axum::test::request::Request::post("/namespaces").body(payload.to_string()).unwrap()).await; + + // Ensure that the response status code is OK + assert_eq!(response.status(), StatusCode::OK); + + // Ensure that the response body contains the expected JSON data + let body = extract::>(response.into_body()).await.unwrap(); + assert_eq!(body, Json(Namespace {})); +}