diff --git a/src/console/commands/debug/json.rs b/src/console/commands/debug/json.rs new file mode 100644 index 00000000..047faf0f --- /dev/null +++ b/src/console/commands/debug/json.rs @@ -0,0 +1,47 @@ +use crate::configuration::get_configuration; +use actix_web::{rt, post, web, HttpResponse, Result, http::header::ContentType}; + +pub struct JsonCommand { + line: usize, + column: usize, + payload: String +} + +impl JsonCommand { + pub fn new(line: usize, column: usize, payload: String) -> Self { + Self { line, column, payload } + } +} + +impl crate::console::commands::CallableTrait for JsonCommand { + fn call(&self) -> Result<(), Box> { + let payload: String = std::fs::read_to_string(&self.payload)?; + let index = line_column_to_index(payload.as_ref(), self.line, self.column); + let prefix = String::from_utf8(>::as_ref(&payload)[..index].to_vec()).unwrap(); + + println!("{}", prefix); + Ok(()) + } +} + +fn line_column_to_index(u8slice: &[u8], line: usize, column: usize) -> usize { + let mut l = 1; + let mut c = 0; + let mut i = 0; + for ch in u8slice { + i += 1; + match ch { + b'\n' => { + l += 1; + c = 0; + } + _ => { + c += 1; + } + } + if line == l && c == column { + break; + } + } + return i; +} diff --git a/src/console/commands/debug/mod.rs b/src/console/commands/debug/mod.rs new file mode 100644 index 00000000..5f50076c --- /dev/null +++ b/src/console/commands/debug/mod.rs @@ -0,0 +1,3 @@ +mod json; + +pub use json::*; diff --git a/src/console/commands/mod.rs b/src/console/commands/mod.rs index 585347d2..41e5329d 100644 --- a/src/console/commands/mod.rs +++ b/src/console/commands/mod.rs @@ -1,4 +1,5 @@ pub mod appclient; +pub mod debug; mod callable; pub mod mq; diff --git a/src/console/main.rs b/src/console/main.rs index 752e10ae..f02a21ef 100644 --- a/src/console/main.rs +++ b/src/console/main.rs @@ -12,6 +12,10 @@ enum Commands { #[command(subcommand)] command: AppClientCommands, }, + Debug { + #[command(subcommand)] + command: DebugCommands, + }, MQ { #[command(subcommand)] command: AppMqCommands, @@ -26,15 +30,24 @@ enum AppClientCommands { }, } +#[derive(Debug, Subcommand)] +enum DebugCommands { + Json { + #[arg(long)] + line: usize, + #[arg(long)] + column: usize, + #[arg(long)] + payload: String, + }, +} + #[derive(Debug, Subcommand)] enum AppMqCommands { Listen { }, } -//todo add documentation about how to add a new command -//todo the helper from console should have a nicer display - fn main() -> Result<(), Box> { let cli = Cli::parse(); @@ -48,6 +61,11 @@ fn get_command(cli: Cli) -> Result match command { + DebugCommands::Json { line, column, payload } => Ok(Box::new( + stacker::console::commands::debug::JsonCommand::new(line, column, payload), + )), + }, Commands::MQ { command} => match command { AppMqCommands::Listen {} => Ok(Box::new( stacker::console::commands::mq::ListenCommand::new(), diff --git a/src/forms/project/form.rs b/src/forms/project/form.rs index d2498cf4..7d3a3a65 100644 --- a/src/forms/project/form.rs +++ b/src/forms/project/form.rs @@ -52,26 +52,3 @@ impl ProjectForm { Ok(is_active) } } - -pub(crate) async fn body_into_form(body: Bytes) -> actix_web::Result { - let body_bytes = actix_web::body::to_bytes(body).await.unwrap(); - let body_str = str::from_utf8(&body_bytes) - .map_err(|err| JsonResponse::::build().internal_server_error(err.to_string()))?; - let deserializer = &mut serde_json::Deserializer::from_str(body_str); - serde_path_to_error::deserialize(deserializer) - .map_err(|err| { - let msg = format!("{}:{:?}", err.path().to_string(), err); - JsonResponse::::build().bad_request(msg) - }) - .and_then(|mut form: forms::project::ProjectForm| { - if !form.validate().is_ok() { - let errors = form.validate().unwrap_err().to_string(); - let err_msg = format!("Invalid data received {:?}", &errors); - tracing::debug!(err_msg); - - return Err(JsonResponse::::build().form_error(errors)); - } - - Ok(form) - }) -} diff --git a/src/helpers/json.rs b/src/helpers/json.rs index ee209c10..36f8d39f 100644 --- a/src/helpers/json.rs +++ b/src/helpers/json.rs @@ -2,6 +2,7 @@ use actix_web::error::{ErrorBadRequest, ErrorConflict, ErrorInternalServerError, use actix_web::web::Json; use actix_web::Error; use serde_derive::Serialize; +use std::convert::From; #[derive(Serialize)] pub(crate) struct JsonResponse { @@ -109,3 +110,17 @@ where JsonResponseBuilder::default() } } + +impl JsonResponse { + pub fn bad_request>(msg: I) -> Error { + JsonResponse::::build().bad_request( msg.into()) + } + + pub fn internal_server_error>(msg: I) -> Error { + JsonResponse::::build().internal_server_error( msg.into()) + } + + pub fn not_found>(msg: I) -> Error { + JsonResponse::::build().not_found(msg.into()) + } +} diff --git a/src/routes/project/add.rs b/src/routes/project/add.rs index e1b84bc9..683e1d33 100644 --- a/src/routes/project/add.rs +++ b/src/routes/project/add.rs @@ -1,37 +1,35 @@ use crate::db; -use crate::forms; +use crate::forms::project::ProjectForm; use crate::helpers::JsonResponse; use crate::models; use actix_web::{ post, web, - web::{Bytes, Data}, + web::{Data}, Responder, Result, }; use serde_json::Value; use sqlx::PgPool; use std::sync::Arc; -use std::str::FromStr; -use std::str; +use serde_valid::Validate; #[tracing::instrument(name = "Add project.")] #[post("")] pub async fn item( - body: Bytes, + web::Json(request_json): web::Json, user: web::ReqData>, pg_pool: Data, ) -> Result { // @todo ACL - let form = forms::project::form::body_into_form(body.clone()).await?; - let project_name = form.custom.custom_stack_code.clone(); - - let body_bytes = actix_web::body::to_bytes(body).await.unwrap(); - let body_str = str::from_utf8(&body_bytes) - .map_err(|err| JsonResponse::::build().internal_server_error(err.to_string()))?; - let request_json = Value::from_str(body_str).unwrap(); - tracing::debug!("Request json: {:?}", request_json); + let form: ProjectForm= serde_json::from_value(request_json.clone()) + .map_err(|err| JsonResponse::bad_request(err.to_string()))?; + if !form.validate().is_ok() { + let errors = form.validate().unwrap_err(); + return Err(JsonResponse::bad_request(errors.to_string())); + } - let body: Value = serde_json::to_value::(form) - .or(serde_json::to_value::(forms::project::ProjectForm::default())) + let project_name = form.custom.custom_stack_code.clone(); + let body: Value = serde_json::to_value::(form) + .or(serde_json::to_value::(ProjectForm::default())) .unwrap(); let project = models::Project::new( @@ -45,7 +43,6 @@ pub async fn item( .await .map(|project| JsonResponse::build().set_item(project).ok("Ok")) .map_err(|_| { - JsonResponse::::build().internal_server_error("Internal Server Error") + JsonResponse::internal_server_error("Internal Server Error") }) } - diff --git a/src/routes/project/get.rs b/src/routes/project/get.rs index 3cd7fc3e..7c0ca553 100644 --- a/src/routes/project/get.rs +++ b/src/routes/project/get.rs @@ -13,24 +13,23 @@ pub async fn item( path: web::Path<(i32,)>, pg_pool: web::Data, ) -> Result { - /// Get project apps of logged user only - let (id,) = path.into_inner(); + let id = path.0; db::project::fetch(pg_pool.get_ref(), id) .await - .map_err(|err| JsonResponse::::build().internal_server_error(err)) + .map_err(|err| JsonResponse::internal_server_error(err.to_string())) .and_then(|project| match project { Some(project) if project.user_id != user.id => { - Err(JsonResponse::::build().not_found("not found")) + Err(JsonResponse::not_found("not found")) } Some(project) => Ok(JsonResponse::build().set_item(Some(project)).ok("OK")), - None => Err(JsonResponse::::build().not_found("not found")), + None => Err(JsonResponse::not_found("not found")), }) } #[tracing::instrument(name = "Get user's project list.")] #[get("/user/{id}")] -pub async fn list( +pub async fn admin_list( user: web::ReqData>, path: web::Path<(String,)>, pg_pool: web::Data, @@ -42,6 +41,6 @@ pub async fn list( db::project::fetch_by_user(pg_pool.get_ref(), &user_id) .await - .map_err(|err| JsonResponse::::build().internal_server_error(err)) + .map_err(|err| JsonResponse::internal_server_error(err)) .map(|projects| JsonResponse::build().set_list(projects).ok("OK")) } diff --git a/src/routes/project/update.rs b/src/routes/project/update.rs index ecd72b10..312c4226 100644 --- a/src/routes/project/update.rs +++ b/src/routes/project/update.rs @@ -1,14 +1,13 @@ use std::str::FromStr; -use crate::forms; +use crate::forms::project::ProjectForm; use crate::helpers::JsonResponse; use crate::models; use crate::db; -use actix_web::{web, web::Data, Responder, Result, put}; +use actix_web::{web, Responder, Result, put}; use serde_json::Value; use serde_valid::Validate; use sqlx::PgPool; use std::sync::Arc; -use actix_web::web::Bytes; use tracing::Instrument; use std::str; @@ -16,44 +15,37 @@ use std::str; #[put("/{id}")] pub async fn item( path: web::Path<(i32,)>, - body: Bytes, + web::Json(request_json): web::Json, user: web::ReqData>, - pg_pool: Data, + pg_pool: web::Data, ) -> Result { let id = path.0; let mut project = db::project::fetch(pg_pool.get_ref(), id) .await - .map_err(|err| JsonResponse::::build().internal_server_error(err)) + .map_err(JsonResponse::internal_server_error) .and_then(|project| match project { Some(project) if project.user_id != user.id => { - Err(JsonResponse::::build().bad_request("Project not found")) + Err(JsonResponse::bad_request("Project not found")) } Some(project) => Ok(project), - None => Err(JsonResponse::::build().not_found("Project not found")), + None => Err(JsonResponse::not_found("Project not found")), })?; - let body_bytes = actix_web::body::to_bytes(body.clone()).await.unwrap(); - let body_str = str::from_utf8(&body_bytes) - .map_err(|err| JsonResponse::::build().internal_server_error(err.to_string()))?; - let request_json = Value::from_str(body_str)?; - tracing::debug!("Request json: {:?}", request_json); - // @todo ACL - let form = forms::project::form::body_into_form(body.clone()).await?; - tracing::debug!("form data: {:?}", form); - - if let Err(errors) = form.validate() { - return Err(JsonResponse::::build().form_error(errors.to_string())); + let form: ProjectForm= serde_json::from_value(request_json.clone()) + .map_err(|err| JsonResponse::bad_request(err.to_string()))?; + if !form.validate().is_ok() { + let errors = form.validate().unwrap_err(); + return Err(JsonResponse::bad_request(errors.to_string())); } let project_name = form.custom.custom_stack_code.clone(); - if !form.is_readable_docker_image().await.is_ok() { - return Err(JsonResponse::::build().bad_request("Can not access docker image")); + return Err(JsonResponse::bad_request("Can not access docker image")); } - let body: Value = serde_json::to_value::(form) - .or(serde_json::to_value::(forms::project::ProjectForm::default())) + let body: Value = serde_json::to_value::(form) + .or(serde_json::to_value::(ProjectForm::default())) .unwrap(); @@ -61,7 +53,6 @@ pub async fn item( project.body = body; project.request_json = request_json; - db::project::update(pg_pool.get_ref(), project) .await .map(|project| { @@ -71,6 +62,6 @@ pub async fn item( }) .map_err(|err| { tracing::error!("Failed to execute query: {:?}", err); - JsonResponse::::build().internal_server_error("") + JsonResponse::internal_server_error("") }) } diff --git a/src/routes/rating/get.rs b/src/routes/rating/get.rs index 34ef6afd..7071dcc8 100644 --- a/src/routes/rating/get.rs +++ b/src/routes/rating/get.rs @@ -5,11 +5,6 @@ use actix_web::{get, web, Responder, Result}; use sqlx::PgPool; use tracing::Instrument; -// workflow -// add, update, list, get(user_id), ACL, -// ACL - access to func for a user -// ACL - access to objects for a user - #[tracing::instrument(name = "Get rating.")] #[get("/{id}")] pub async fn get_handler( diff --git a/src/routes/test/casbin.rs b/src/routes/test/casbin.rs deleted file mode 100644 index 77e1ec59..00000000 --- a/src/routes/test/casbin.rs +++ /dev/null @@ -1,8 +0,0 @@ -use actix_web::{get, Responder, Result}; -use crate::helpers::JsonResponse; - -#[tracing::instrument(name = "Test casbin.")] -#[get("")] -pub async fn handler() -> Result { - Ok(JsonResponse::::build().ok("success")) -} diff --git a/src/routes/test/mod.rs b/src/routes/test/mod.rs index 2b55b427..40149b14 100644 --- a/src/routes/test/mod.rs +++ b/src/routes/test/mod.rs @@ -1,2 +1 @@ pub mod deploy; -pub mod casbin; diff --git a/src/startup.rs b/src/startup.rs index 12370ef2..56a317f8 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -2,10 +2,16 @@ use crate::configuration::Settings; use crate::helpers; use crate::routes; use actix_cors::Cors; -use actix_web::dev::Server; use actix_web::{ - web::{self}, - App, HttpServer, + dev::Server, + http, + error, + web, + App, + HttpServer, + HttpResponse, + FromRequest, + rt, }; use crate::middleware; use sqlx::{Pool, Postgres}; @@ -24,7 +30,14 @@ pub async fn run( let mq_manager = web::Data::new(mq_manager); let authorization = middleware::authorization::try_new(settings.database.connection_string()).await?; - + let json_config = web::JsonConfig::default() + .error_handler(|err, req| { //todo + let msg: String = match err { + error::JsonPayloadError::Deserialize(err) => format!("{{\"kind\":\"deserialize\",\"line\":{}, \"column\":{}, \"msg\":\"{}\"}}", err.line(), err.column(), err), + _ => format!("{{\"kind\":\"other\",\"msg\":\"{}\"}}", err) + }; + error::InternalError::new(msg, http::StatusCode::BAD_REQUEST).into() + }); let server = HttpServer::new(move || { App::new() .wrap(TracingLogger::default()) @@ -42,16 +55,8 @@ pub async fn run( .service(routes::client::disable_handler), ) .service( - web::scope("/admin/client") - .service(routes::client::admin_enable_handler) - .service(routes::client::admin_update_handler) - .service(routes::client::admin_disable_handler), - ) - .service( - web::scope("/test").service(routes::test::deploy::handler), - ) - .service( - web::scope("/pen/1").service(routes::test::casbin::handler), + web::scope("/test") + .service(routes::test::deploy::handler) ) .service( web::scope("/rating") @@ -65,11 +70,23 @@ pub async fn run( .service(crate::routes::project::compose::add) .service(crate::routes::project::compose::admin) .service(crate::routes::project::get::item) - .service(crate::routes::project::get::list) .service(crate::routes::project::add::item) - .service(crate::routes::project::update::item) + .service(crate::routes::project::update::item) .service(crate::routes::project::delete::item), ) + .service( + web::scope("/admin") + .service( + web::scope("/project") + .service(crate::routes::project::get::admin_list) + ) + .service( + web::scope("/client") + .service(routes::client::admin_enable_handler) + .service(routes::client::admin_update_handler) + .service(routes::client::admin_disable_handler), + ) + ) .service( web::scope("/cloud") .service(crate::routes::cloud::get::item) @@ -86,12 +103,7 @@ pub async fn run( .service(crate::routes::server::update::item) .service(crate::routes::server::delete::item), ) - // @todo stack renamed to project - // .service( - // web::scope("/admin/project") - // .service(routes::project::get::admin_item) - // .service(routes::project::get::admin_list) - // ) + .app_data(json_config.clone()) .app_data(pg_pool.clone()) .app_data(mq_manager.clone()) .app_data(settings.clone())