Add delete handler and json-payload error response

This commit is contained in:
Tobias Reisinger 2021-11-08 13:11:20 +01:00
parent e6278176e4
commit 7254eddc6c
8 changed files with 162 additions and 51 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "emgauwa-core" name = "emgauwa-core"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2018"
authors = ["Tobias Reisinger <tobias@msrg.cc>"] authors = ["Tobias Reisinger <tobias@msrg.cc>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -5,10 +5,10 @@ use diesel::prelude::*;
use diesel_migrations::embed_migrations; use diesel_migrations::embed_migrations;
use dotenv::dotenv; use dotenv::dotenv;
use crate::types::EmgauwaUid;
use errors::DatabaseError; use errors::DatabaseError;
use models::*; use models::*;
use schema::schedules::dsl::*; use schema::schedules::dsl::*;
use crate::types::EmgauwaUid;
pub mod errors; pub mod errors;
pub mod models; pub mod models;
@ -46,13 +46,33 @@ pub fn get_schedule_by_uid(filter_uid: EmgauwaUid) -> Result<Schedule, DatabaseE
Ok(result) Ok(result)
} }
pub fn delete_schedule_by_uid(filter_uid: EmgauwaUid) -> 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<Schedule, DatabaseError> { pub fn create_schedule(new_name: &str, new_periods: &Periods) -> Result<Schedule, DatabaseError> {
let connection = get_connection(); let connection = get_connection();
let new_schedule = NewSchedule { let new_schedule = NewSchedule {
uid: &EmgauwaUid::default(), uid: &EmgauwaUid::default(),
name: new_name, name: new_name,
periods: new_periods periods: new_periods,
}; };
diesel::insert_into(schedules) diesel::insert_into(schedules)

View file

@ -1,17 +1,22 @@
use actix_web::HttpResponse;
use actix_web::http::StatusCode;
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
pub enum DatabaseError { pub enum DatabaseError {
DeleteError,
InsertError, InsertError,
InsertGetError, InsertGetError,
NotFound, NotFound,
Protected
} }
impl DatabaseError { impl DatabaseError {
fn to_code(&self) -> u32 { fn get_code(&self) -> StatusCode {
match self { match self {
DatabaseError::NotFound => 404, DatabaseError::NotFound => StatusCode::NOT_FOUND,
_ => 500 DatabaseError::Protected => StatusCode::FORBIDDEN,
_ => StatusCode::INTERNAL_SERVER_ERROR
} }
} }
} }
@ -23,7 +28,7 @@ impl Serialize for DatabaseError {
{ {
let mut s = serializer.serialize_struct("error", 3)?; let mut s = serializer.serialize_struct("error", 3)?;
s.serialize_field("type", "database-error")?; 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.serialize_field("description", &String::from(self))?;
s.end() s.end()
} }
@ -32,11 +37,19 @@ impl Serialize for DatabaseError {
impl From<&DatabaseError> for String { impl From<&DatabaseError> for String {
fn from(err: &DatabaseError) -> Self { fn from(err: &DatabaseError) -> Self {
match err { match err {
DatabaseError::InsertError => String::from("error inserting into database"), DatabaseError::InsertError => String::from("error on inserting into database"),
DatabaseError::InsertGetError => { 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<DatabaseError> for HttpResponse {
fn from(err: DatabaseError) -> Self {
HttpResponse::build(err.get_code()).json(err)
}
}

View file

@ -1,16 +1,21 @@
use actix_web::http::StatusCode;
use actix_web::HttpResponse;
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
pub enum HandlerError { pub enum HandlerError {
BadUidError, BadUid,
ProtectedSchedule
} }
impl HandlerError { impl HandlerError {
fn to_code(&self) -> u32 { fn get_code(&self) -> StatusCode {
match self { match self {
HandlerError::BadUidError => 400 HandlerError::BadUid => StatusCode::BAD_REQUEST,
HandlerError::ProtectedSchedule => StatusCode::FORBIDDEN,
} }
} }
} }
impl Serialize for HandlerError { impl Serialize for HandlerError {
@ -19,7 +24,7 @@ impl Serialize for HandlerError {
S: Serializer, S: Serializer,
{ {
let mut s = serializer.serialize_struct("error", 2)?; 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.serialize_field("description", &String::from(self))?;
s.end() s.end()
} }
@ -28,7 +33,14 @@ impl Serialize for HandlerError {
impl From<&HandlerError> for String { impl From<&HandlerError> for String {
fn from(err: &HandlerError) -> Self { fn from(err: &HandlerError) -> Self {
match err { 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<HandlerError> for HttpResponse {
fn from(err: HandlerError) -> Self {
HttpResponse::build(err.get_code()).json(err)
}
}

View file

@ -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; pub mod v1;
mod errors;
enum EmgauwaJsonPayLoadError {
Error(error::JsonPayloadError),
}
impl Serialize for EmgauwaJsonPayLoadError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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()
}

View file

@ -1,7 +1,6 @@
use std::str::FromStr; use std::convert::TryFrom;
use actix_web::{HttpResponse, Responder, web, get}; use actix_web::{HttpResponse, Responder, web, get, delete};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use uuid::Uuid;
use crate::db; use crate::db;
use crate::db::models::Periods; use crate::db::models::Periods;
@ -22,26 +21,18 @@ pub async fn index() -> impl Responder {
#[get("/api/v1/schedules/{schedule_id}")] #[get("/api/v1/schedules/{schedule_id}")]
pub async fn show(web::Path((schedule_uid,)): web::Path<(String,)>) -> impl Responder { pub async fn show(web::Path((schedule_uid,)): web::Path<(String,)>) -> impl Responder {
let emgauwa_uid = match schedule_uid.as_str() { let emgauwa_uid = EmgauwaUid::try_from(schedule_uid.as_str()).or(Err(HandlerError::BadUid));
"on" => Ok(EmgauwaUid::On),
"off" => Ok(EmgauwaUid::Off),
any => match Uuid::from_str(any) {
Ok(uuid) => Ok(EmgauwaUid::Any(uuid)),
Err(_) => Err(HandlerError::BadUidError)
}
};
match emgauwa_uid { match emgauwa_uid {
Ok(uid) => { Ok(uid) => {
let schedule = db::get_schedule_by_uid(uid); let schedule = db::get_schedule_by_uid(uid);
match schedule { match schedule {
Ok(ok) => HttpResponse::Ok().json(ok), 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<RequestSchedule>) -> impl Responder { pub async fn add(post: web::Json<RequestSchedule>) -> impl Responder {
@ -49,10 +40,25 @@ pub async fn add(post: web::Json<RequestSchedule>) -> impl Responder {
match new_schedule { match new_schedule {
Ok(ok) => HttpResponse::Created().json(ok), Ok(ok) => HttpResponse::Created().json(ok),
Err(err) => HttpResponse::InternalServerError().json(err), Err(err) => HttpResponse::from(err),
} }
} }
pub async fn delete() -> impl Responder { #[delete("/api/v1/schedules/{schedule_id}")]
"hello from delete schedule" 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)
}
} }

View file

@ -27,6 +27,7 @@ async fn main() -> std::io::Result<()> {
) )
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim)) .wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
.app_data(web::JsonConfig::default().error_handler(handlers::json_error_handler))
.route( .route(
"/api/v1/schedules", "/api/v1/schedules",
web::get().to(handlers::v1::schedules::index), web::get().to(handlers::v1::schedules::index),
@ -36,10 +37,7 @@ async fn main() -> std::io::Result<()> {
web::post().to(handlers::v1::schedules::add), web::post().to(handlers::v1::schedules::add),
) )
.service(handlers::v1::schedules::show) .service(handlers::v1::schedules::show)
.route( .service(handlers::v1::schedules::delete)
"/api/v1/schedules/{id}",
web::delete().to(handlers::v1::schedules::delete),
)
}) })
.bind("127.0.0.1:5000")? .bind("127.0.0.1:5000")?
.run() .run()

View file

@ -1,5 +1,7 @@
use std::convert::TryFrom;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::io::Write; use std::io::Write;
use std::str::FromStr;
use diesel::backend::Backend; use diesel::backend::Backend;
use diesel::deserialize::FromSql; use diesel::deserialize::FromSql;
@ -13,11 +15,20 @@ use uuid::Uuid;
#[derive(AsExpression, FromSqlRow, PartialEq, Clone)] #[derive(AsExpression, FromSqlRow, PartialEq, Clone)]
#[sql_type = "Binary"] #[sql_type = "Binary"]
pub enum EmgauwaUid { pub enum EmgauwaUid {
On,
Off, Off,
On,
Any(Uuid), 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 { impl Default for EmgauwaUid {
fn default() -> Self { fn default() -> Self {
EmgauwaUid::Any(Uuid::new_v4()) EmgauwaUid::Any(Uuid::new_v4())
@ -27,8 +38,8 @@ impl Default for EmgauwaUid {
impl Debug for EmgauwaUid { impl Debug for EmgauwaUid {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
EmgauwaUid::On => "on".fmt(f), EmgauwaUid::Off => EmgauwaUid::OFF_STR.fmt(f),
EmgauwaUid::Off => "off".fmt(f), EmgauwaUid::On => EmgauwaUid::ON_STR.fmt(f),
EmgauwaUid::Any(value) => value.fmt(f), EmgauwaUid::Any(value) => value.fmt(f),
} }
} }
@ -37,9 +48,9 @@ impl Debug for EmgauwaUid {
impl ToSql<Binary, Sqlite> for EmgauwaUid { impl ToSql<Binary, Sqlite> for EmgauwaUid {
fn to_sql<W: Write>(&self, out: &mut Output<W, Sqlite>) -> serialize::Result { fn to_sql<W: Write>(&self, out: &mut Output<W, Sqlite>) -> serialize::Result {
match self { match self {
EmgauwaUid::On => out.write_all(&[1])?, EmgauwaUid::Off => out.write_all(&[EmgauwaUid::OFF_U8])?,
EmgauwaUid::Off => out.write_all(&[0])?, EmgauwaUid::On => out.write_all(&[EmgauwaUid::ON_U8])?,
EmgauwaUid::Any(_) => out.write_all(Uuid::from(self).as_bytes())?, EmgauwaUid::Any(value) => out.write_all(value.as_bytes())?,
} }
Ok(IsNull::No) Ok(IsNull::No)
} }
@ -50,8 +61,8 @@ impl FromSql<Binary, Sqlite> for EmgauwaUid {
match bytes { match bytes {
None => Ok(EmgauwaUid::default()), None => Ok(EmgauwaUid::default()),
Some(value) => match value.read_blob() { Some(value) => match value.read_blob() {
[0] => Ok(EmgauwaUid::Off), [EmgauwaUid::OFF_U8] => Ok(EmgauwaUid::Off),
[1] => Ok(EmgauwaUid::On), [EmgauwaUid::ON_U8] => Ok(EmgauwaUid::On),
value_bytes => Ok(EmgauwaUid::Any(Uuid::from_slice(value_bytes).unwrap())), value_bytes => Ok(EmgauwaUid::Any(Uuid::from_slice(value_bytes).unwrap())),
}, },
} }
@ -70,18 +81,33 @@ impl Serialize for EmgauwaUid {
impl From<Uuid> for EmgauwaUid { impl From<Uuid> for EmgauwaUid {
fn from(uid: Uuid) -> EmgauwaUid { fn from(uid: Uuid) -> EmgauwaUid {
match uid.as_u128() { match uid.as_u128() {
0 => EmgauwaUid::Off, EmgauwaUid::OFF_U128 => EmgauwaUid::Off,
1 => EmgauwaUid::On, EmgauwaUid::ON_U128 => EmgauwaUid::On,
_ => EmgauwaUid::Any(uid), _ => EmgauwaUid::Any(uid),
} }
} }
} }
impl TryFrom<&str> for EmgauwaUid {
type Error = uuid::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
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 { impl From<&EmgauwaUid> for Uuid {
fn from(emgauwa_uid: &EmgauwaUid) -> Uuid { fn from(emgauwa_uid: &EmgauwaUid) -> Uuid {
match emgauwa_uid { match emgauwa_uid {
EmgauwaUid::On => uuid::Uuid::from_u128(1), EmgauwaUid::Off => uuid::Uuid::from_u128(EmgauwaUid::OFF_U128),
EmgauwaUid::Off => uuid::Uuid::from_u128(0), EmgauwaUid::On => uuid::Uuid::from_u128(EmgauwaUid::ON_U128),
EmgauwaUid::Any(value) => *value, EmgauwaUid::Any(value) => *value,
} }
} }
@ -90,8 +116,8 @@ impl From<&EmgauwaUid> for Uuid {
impl From<&EmgauwaUid> for String { impl From<&EmgauwaUid> for String {
fn from(emgauwa_uid: &EmgauwaUid) -> String { fn from(emgauwa_uid: &EmgauwaUid) -> String {
match emgauwa_uid { match emgauwa_uid {
EmgauwaUid::Off => String::from("off"), EmgauwaUid::Off => String::from(EmgauwaUid::OFF_STR),
EmgauwaUid::On => String::from("on"), EmgauwaUid::On => String::from(EmgauwaUid::ON_STR),
EmgauwaUid::Any(value) => value.to_hyphenated().to_string(), EmgauwaUid::Any(value) => value.to_hyphenated().to_string(),
} }
} }