From 9d1e3539a8981802a1ca55dc11d76f9da0726b2a Mon Sep 17 00:00:00 2001 From: Lars Jellema Date: Thu, 21 Apr 2022 22:45:55 +0200 Subject: [PATCH] Rework api errors --- backend/src/api.rs | 129 ++++++++++++++++++++++++++++----------------- 1 file changed, 80 insertions(+), 49 deletions(-) diff --git a/backend/src/api.rs b/backend/src/api.rs index 5a6ca54..dda99bc 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -10,12 +10,15 @@ use jsonwebtoken::{EncodingKey, Header}; use rocket::http::Status; use rocket::request::Outcome; use rocket::request::{FromRequest, Request}; +use rocket::response; use rocket::serde::json; use rocket::serde::json::{json, Json}; use rocket::State; use serde::{Deserialize, Serialize}; +use serde::ser::{SerializeStruct, Serializer}; use std::borrow::Cow; -use validator::ValidateArgs; +use std::fmt; +use validator::{ValidateArgs, ValidationErrors}; #[derive(Serialize, Deserialize, Debug)] struct ApiResponse { @@ -36,14 +39,6 @@ impl ApiResponse { jwt: None, }; - fn error(message: String) -> Self { - Self { - result: Self::FAILURE_RESULT, - message: Some(Cow::Owned(message)), - jwt: None, - } - } - fn login_response(jwt: String) -> Self { Self { result: Self::SUCCES_RESULT, @@ -56,6 +51,59 @@ impl ApiResponse { #[derive(Debug)] pub enum ApiError { RequestError(String), + ValidationErrors(ValidationErrors), + Unauthorized, +} + +impl fmt::Display for ApiError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use ApiError::*; + write!(f, "{}", match &self { + RequestError(e) => e, + ValidationErrors(_) => "???", + Unauthorized => "username and password didn't match", + }) + } +} + +impl From for ApiError { + fn from(e: schema::DatabaseError) -> Self { + ApiError::RequestError(e.to_string()) + } +} + +impl From for ApiError { + fn from(e: ValidationErrors) -> Self { + ApiError::ValidationErrors(e) + } +} + +impl Serialize for ApiError { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("ApiError", 2)?; + state.serialize_field("ok", &false)?; + state.serialize_field("message", &self.to_string())?; + state.end() + } +} + +impl<'r> response::Responder<'r, 'static> for ApiError { + fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { + use ApiError::*; + let status = match self { + RequestError(_) => Status::BadRequest, + ValidationErrors(_) => Status::BadRequest, + Unauthorized => Status::Unauthorized, + }; + + response::Response::build() + .merge(json!(self).respond_to(req)?) + .status(status) + .ok() + } } const AUTH_HEADER: &str = "Authorization"; @@ -124,23 +172,14 @@ pub async fn gamenight_post_json( pub async fn register_post_json( conn: DbConn, register_json: Json, -) -> Result { +) -> Result { let register = register_json.into_inner(); let register_clone = register.clone(); - match conn - .run(move |c| register_clone.validate_args((c, c))) - .await - { - Ok(()) => (), - Err(error) => { - return Ok(json!(ApiResponse::error(error.to_string()))) - } - } + conn.run(move |c| register_clone.validate_args((c, c))) + .await?; - match schema::insert_user(conn, register).await { - Ok(_) => Ok(json!(ApiResponse::SUCCES)), - Err(err) => Ok(json!(ApiResponse::error(err.to_string()))), - } + schema::insert_user(conn, register).await?; + Ok(json!(ApiResponse::SUCCES)) } #[derive(Debug, Serialize, Deserialize)] @@ -155,33 +194,25 @@ pub async fn login_post_json( conn: DbConn, config: &State, login_json: Json, -) -> Result { - match schema::login(conn, login_json.into_inner()).await { - Err(err) => Ok(json!(ApiResponse::error(err.to_string()))), - Ok(login_result) => { - if !login_result.result { - return Ok(json!(ApiResponse::error(String::from( - "username and password didn't match" - )))); - } +) -> Result { + let login_result = schema::login(conn, login_json.into_inner()).await?; + if !login_result.result { + return Err(ApiError::Unauthorized); + } - let my_claims = Claims { - exp: Utc::now().timestamp() + chrono::Duration::days(7).num_seconds(), - uid: login_result.id.unwrap(), - role: login_result.role.unwrap(), - }; + let my_claims = Claims { + exp: Utc::now().timestamp() + chrono::Duration::days(7).num_seconds(), + uid: login_result.id.unwrap(), + role: login_result.role.unwrap(), + }; - let secret = &config.inner().jwt_secret; - match encode( - &Header::default(), - &my_claims, - &EncodingKey::from_secret(secret.as_bytes()), - ) { - Ok(token) => Ok(json!(ApiResponse::login_response(token))), - Err(error) => { - Ok(json!(ApiResponse::error(error.to_string()))) - } - } - } + let secret = &config.inner().jwt_secret; + match encode( + &Header::default(), + &my_claims, + &EncodingKey::from_secret(secret.as_bytes()), + ) { + Ok(token) => Ok(json!(ApiResponse::login_response(token))), + Err(error) => Err(ApiError::RequestError(error.to_string())), } }