From 7254eddc6c4c51387fbd3fb1072e5794443943ae Mon Sep 17 00:00:00 2001 From: Tobias Reisinger Date: Mon, 8 Nov 2021 13:11:20 +0100 Subject: [PATCH] Add delete handler and json-payload error response --- Cargo.toml | 2 +- src/db.rs | 24 ++++++++++++++-- src/db/errors.rs | 27 +++++++++++++----- src/handlers/errors.rs | 22 +++++++++++---- src/handlers/mod.rs | 38 ++++++++++++++++++++++++- src/handlers/v1/schedules.rs | 40 ++++++++++++++------------ src/main.rs | 6 ++-- src/types.rs | 54 ++++++++++++++++++++++++++---------- 8 files changed, 162 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 21bb0d3..756d5cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "emgauwa-core" version = "0.1.0" -edition = "2021" +edition = "2018" authors = ["Tobias Reisinger "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/db.rs b/src/db.rs index 263b0b1..46d1f56 100644 --- a/src/db.rs +++ b/src/db.rs @@ -5,10 +5,10 @@ use diesel::prelude::*; use diesel_migrations::embed_migrations; use dotenv::dotenv; +use crate::types::EmgauwaUid; use errors::DatabaseError; use models::*; use schema::schedules::dsl::*; -use crate::types::EmgauwaUid; pub mod errors; pub mod models; @@ -46,13 +46,33 @@ pub fn get_schedule_by_uid(filter_uid: EmgauwaUid) -> Result Result<(), DatabaseError> { + let filter_uid = match filter_uid { + EmgauwaUid::Off => Err(DatabaseError::Protected), + EmgauwaUid::On => Err(DatabaseError::Protected), + EmgauwaUid::Any(_) => Ok(filter_uid) + }?; + + let connection = get_connection(); + match diesel::delete(schedules.filter(uid.eq(filter_uid))).execute(&connection) { + Ok(rows) => { + if rows != 0 { + Ok(()) + } else { + Err(DatabaseError::DeleteError) + } + } + Err(_) => Err(DatabaseError::DeleteError), + } +} + pub fn create_schedule(new_name: &str, new_periods: &Periods) -> Result { let connection = get_connection(); let new_schedule = NewSchedule { uid: &EmgauwaUid::default(), name: new_name, - periods: new_periods + periods: new_periods, }; diesel::insert_into(schedules) diff --git a/src/db/errors.rs b/src/db/errors.rs index ca2a8cd..422ce8a 100644 --- a/src/db/errors.rs +++ b/src/db/errors.rs @@ -1,17 +1,22 @@ +use actix_web::HttpResponse; +use actix_web::http::StatusCode; use serde::ser::SerializeStruct; use serde::{Serialize, Serializer}; pub enum DatabaseError { + DeleteError, InsertError, InsertGetError, NotFound, + Protected } impl DatabaseError { - fn to_code(&self) -> u32 { + fn get_code(&self) -> StatusCode { match self { - DatabaseError::NotFound => 404, - _ => 500 + DatabaseError::NotFound => StatusCode::NOT_FOUND, + DatabaseError::Protected => StatusCode::FORBIDDEN, + _ => StatusCode::INTERNAL_SERVER_ERROR } } } @@ -23,7 +28,7 @@ impl Serialize for DatabaseError { { let mut s = serializer.serialize_struct("error", 3)?; s.serialize_field("type", "database-error")?; - s.serialize_field("code", &self.to_code())?; + s.serialize_field("code", &self.get_code().as_u16())?; s.serialize_field("description", &String::from(self))?; s.end() } @@ -32,11 +37,19 @@ impl Serialize for DatabaseError { impl From<&DatabaseError> for String { fn from(err: &DatabaseError) -> Self { match err { - DatabaseError::InsertError => String::from("error inserting into database"), + DatabaseError::InsertError => String::from("error on inserting into database"), DatabaseError::InsertGetError => { - String::from("error retrieving new entry from database (your entry was saved)") + String::from("error on retrieving new entry from database (your entry was saved)") } - DatabaseError::NotFound => String::from("model was not found in database") + DatabaseError::NotFound => String::from("model was not found in database"), + DatabaseError::DeleteError => String::from("error on deleting from database"), + DatabaseError::Protected => String::from("model is protected"), } } } + +impl From for HttpResponse { + fn from(err: DatabaseError) -> Self { + HttpResponse::build(err.get_code()).json(err) + } +} diff --git a/src/handlers/errors.rs b/src/handlers/errors.rs index b79e10e..132531b 100644 --- a/src/handlers/errors.rs +++ b/src/handlers/errors.rs @@ -1,16 +1,21 @@ +use actix_web::http::StatusCode; +use actix_web::HttpResponse; use serde::ser::SerializeStruct; use serde::{Serialize, Serializer}; pub enum HandlerError { - BadUidError, + BadUid, + ProtectedSchedule } impl HandlerError { - fn to_code(&self) -> u32 { + fn get_code(&self) -> StatusCode { match self { - HandlerError::BadUidError => 400 + HandlerError::BadUid => StatusCode::BAD_REQUEST, + HandlerError::ProtectedSchedule => StatusCode::FORBIDDEN, } } + } impl Serialize for HandlerError { @@ -19,7 +24,7 @@ impl Serialize for HandlerError { S: Serializer, { let mut s = serializer.serialize_struct("error", 2)?; - s.serialize_field("code", &self.to_code())?; + s.serialize_field("code", &self.get_code().as_u16())?; s.serialize_field("description", &String::from(self))?; s.end() } @@ -28,7 +33,14 @@ impl Serialize for HandlerError { impl From<&HandlerError> for String { fn from(err: &HandlerError) -> Self { match err { - HandlerError::BadUidError => String::from("the uid is in a bad format"), + HandlerError::BadUid => String::from("the uid is in a bad format"), + HandlerError::ProtectedSchedule => String::from("the targeted schedule is protected"), } } } + +impl From for HttpResponse { + fn from(err: HandlerError) -> Self { + HttpResponse::build(err.get_code()).json(err) + } +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 628befb..e4c867d 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,2 +1,38 @@ +use actix_web::{error, Error, HttpRequest, HttpResponse}; +use serde::ser::SerializeStruct; +use serde::{Serialize, Serializer}; + +pub(crate) mod errors; pub mod v1; -mod errors; + +enum EmgauwaJsonPayLoadError { + Error(error::JsonPayloadError), +} + +impl Serialize for EmgauwaJsonPayLoadError { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_struct("error", 3)?; + s.serialize_field("type", "json-payload-error")?; + s.serialize_field("code", &400)?; + s.serialize_field( + "description", + &match self { + EmgauwaJsonPayLoadError::Error(err) => format!("{}", err), + } + )?; + s.end() + } +} + +pub fn json_error_handler(err: error::JsonPayloadError, _: &HttpRequest) -> Error { + error::InternalError::from_response( + "", + HttpResponse::BadRequest() + .content_type("application/json") + .json(EmgauwaJsonPayLoadError::Error(err)), + ) + .into() +} diff --git a/src/handlers/v1/schedules.rs b/src/handlers/v1/schedules.rs index 9a3289d..c491f2e 100644 --- a/src/handlers/v1/schedules.rs +++ b/src/handlers/v1/schedules.rs @@ -1,7 +1,6 @@ -use std::str::FromStr; -use actix_web::{HttpResponse, Responder, web, get}; +use std::convert::TryFrom; +use actix_web::{HttpResponse, Responder, web, get, delete}; use serde::{Serialize, Deserialize}; -use uuid::Uuid; use crate::db; use crate::db::models::Periods; @@ -22,26 +21,18 @@ pub async fn index() -> impl Responder { #[get("/api/v1/schedules/{schedule_id}")] pub async fn show(web::Path((schedule_uid,)): web::Path<(String,)>) -> impl Responder { - let emgauwa_uid = match schedule_uid.as_str() { - "on" => Ok(EmgauwaUid::On), - "off" => Ok(EmgauwaUid::Off), - any => match Uuid::from_str(any) { - Ok(uuid) => Ok(EmgauwaUid::Any(uuid)), - Err(_) => Err(HandlerError::BadUidError) - } - }; + let emgauwa_uid = EmgauwaUid::try_from(schedule_uid.as_str()).or(Err(HandlerError::BadUid)); match emgauwa_uid { Ok(uid) => { let schedule = db::get_schedule_by_uid(uid); match schedule { Ok(ok) => HttpResponse::Ok().json(ok), - Err(err) => HttpResponse::NotFound().json(err), + Err(err) => HttpResponse::from(err), } }, - Err(err) => HttpResponse::BadRequest().json(err) + Err(err) => HttpResponse::from(err) } - } pub async fn add(post: web::Json) -> impl Responder { @@ -49,10 +40,25 @@ pub async fn add(post: web::Json) -> impl Responder { match new_schedule { Ok(ok) => HttpResponse::Created().json(ok), - Err(err) => HttpResponse::InternalServerError().json(err), + Err(err) => HttpResponse::from(err), } } -pub async fn delete() -> impl Responder { - "hello from delete schedule" +#[delete("/api/v1/schedules/{schedule_id}")] +pub async fn delete(web::Path((schedule_uid,)): web::Path<(String,)>) -> impl Responder { + let emgauwa_uid = EmgauwaUid::try_from(schedule_uid.as_str()).or(Err(HandlerError::BadUid)); + + match emgauwa_uid { + Ok(uid) => match uid { + EmgauwaUid::Off => HttpResponse::from(HandlerError::ProtectedSchedule), + EmgauwaUid::On => HttpResponse::from(HandlerError::ProtectedSchedule), + EmgauwaUid::Any(_) => { + match db::delete_schedule_by_uid(uid) { + Ok(_) => HttpResponse::Ok().json("schedule got deleted"), + Err(err) => HttpResponse::from(err) + } + } + }, + Err(err) => HttpResponse::from(err) + } } diff --git a/src/main.rs b/src/main.rs index d5e5575..15e165a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ async fn main() -> std::io::Result<()> { ) .wrap(middleware::Logger::default()) .wrap(middleware::NormalizePath::new(TrailingSlash::Trim)) + .app_data(web::JsonConfig::default().error_handler(handlers::json_error_handler)) .route( "/api/v1/schedules", web::get().to(handlers::v1::schedules::index), @@ -36,10 +37,7 @@ async fn main() -> std::io::Result<()> { web::post().to(handlers::v1::schedules::add), ) .service(handlers::v1::schedules::show) - .route( - "/api/v1/schedules/{id}", - web::delete().to(handlers::v1::schedules::delete), - ) + .service(handlers::v1::schedules::delete) }) .bind("127.0.0.1:5000")? .run() diff --git a/src/types.rs b/src/types.rs index 2d7ade2..a67ff65 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,7 @@ +use std::convert::TryFrom; use std::fmt::{Debug, Formatter}; use std::io::Write; +use std::str::FromStr; use diesel::backend::Backend; use diesel::deserialize::FromSql; @@ -13,11 +15,20 @@ use uuid::Uuid; #[derive(AsExpression, FromSqlRow, PartialEq, Clone)] #[sql_type = "Binary"] pub enum EmgauwaUid { - On, Off, + On, Any(Uuid), } +impl EmgauwaUid { + const OFF_STR: &'static str = "off"; + const ON_STR: &'static str = "on"; + const OFF_U8: u8 = 0; + const ON_U8: u8 = 1; + const OFF_U128: u128 = 0; + const ON_U128: u128 = 1; +} + impl Default for EmgauwaUid { fn default() -> Self { EmgauwaUid::Any(Uuid::new_v4()) @@ -27,8 +38,8 @@ impl Default for EmgauwaUid { impl Debug for EmgauwaUid { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - EmgauwaUid::On => "on".fmt(f), - EmgauwaUid::Off => "off".fmt(f), + EmgauwaUid::Off => EmgauwaUid::OFF_STR.fmt(f), + EmgauwaUid::On => EmgauwaUid::ON_STR.fmt(f), EmgauwaUid::Any(value) => value.fmt(f), } } @@ -37,9 +48,9 @@ impl Debug for EmgauwaUid { impl ToSql for EmgauwaUid { fn to_sql(&self, out: &mut Output) -> serialize::Result { match self { - EmgauwaUid::On => out.write_all(&[1])?, - EmgauwaUid::Off => out.write_all(&[0])?, - EmgauwaUid::Any(_) => out.write_all(Uuid::from(self).as_bytes())?, + EmgauwaUid::Off => out.write_all(&[EmgauwaUid::OFF_U8])?, + EmgauwaUid::On => out.write_all(&[EmgauwaUid::ON_U8])?, + EmgauwaUid::Any(value) => out.write_all(value.as_bytes())?, } Ok(IsNull::No) } @@ -50,8 +61,8 @@ impl FromSql for EmgauwaUid { match bytes { None => Ok(EmgauwaUid::default()), Some(value) => match value.read_blob() { - [0] => Ok(EmgauwaUid::Off), - [1] => Ok(EmgauwaUid::On), + [EmgauwaUid::OFF_U8] => Ok(EmgauwaUid::Off), + [EmgauwaUid::ON_U8] => Ok(EmgauwaUid::On), value_bytes => Ok(EmgauwaUid::Any(Uuid::from_slice(value_bytes).unwrap())), }, } @@ -70,18 +81,33 @@ impl Serialize for EmgauwaUid { impl From for EmgauwaUid { fn from(uid: Uuid) -> EmgauwaUid { match uid.as_u128() { - 0 => EmgauwaUid::Off, - 1 => EmgauwaUid::On, + EmgauwaUid::OFF_U128 => EmgauwaUid::Off, + EmgauwaUid::ON_U128 => EmgauwaUid::On, _ => EmgauwaUid::Any(uid), } } } +impl TryFrom<&str> for EmgauwaUid { + type Error = uuid::Error; + + fn try_from(value: &str) -> Result { + match value { + EmgauwaUid::OFF_STR => Ok(EmgauwaUid::Off), + EmgauwaUid::ON_STR => Ok(EmgauwaUid::On), + any => match Uuid::from_str(any) { + Ok(uuid) => Ok(EmgauwaUid::Any(uuid)), + Err(err) => Err(err), + }, + } + } +} + impl From<&EmgauwaUid> for Uuid { fn from(emgauwa_uid: &EmgauwaUid) -> Uuid { match emgauwa_uid { - EmgauwaUid::On => uuid::Uuid::from_u128(1), - EmgauwaUid::Off => uuid::Uuid::from_u128(0), + EmgauwaUid::Off => uuid::Uuid::from_u128(EmgauwaUid::OFF_U128), + EmgauwaUid::On => uuid::Uuid::from_u128(EmgauwaUid::ON_U128), EmgauwaUid::Any(value) => *value, } } @@ -90,8 +116,8 @@ impl From<&EmgauwaUid> for Uuid { impl From<&EmgauwaUid> for String { fn from(emgauwa_uid: &EmgauwaUid) -> String { match emgauwa_uid { - EmgauwaUid::Off => String::from("off"), - EmgauwaUid::On => String::from("on"), + EmgauwaUid::Off => String::from(EmgauwaUid::OFF_STR), + EmgauwaUid::On => String::from(EmgauwaUid::ON_STR), EmgauwaUid::Any(value) => value.to_hyphenated().to_string(), } }