소스 검색

Add new AppError for handling errors.

Lee Morgan 1 년 전
부모
커밋
4d39f5f857
6개의 변경된 파일80개의 추가작업 그리고 18개의 파일을 삭제
  1. 1 0
      Cargo.lock
  2. 1 0
      Cargo.toml
  3. 2 2
      bruno/Suma/User/Create.bru
  4. 57 0
      src/app_error.rs
  5. 18 16
      src/controllers/user.rs
  6. 1 0
      src/main.rs

+ 1 - 0
Cargo.lock

@@ -1842,6 +1842,7 @@ dependencies = [
  "mongodb",
  "regex",
  "serde",
+ "thiserror",
  "tokio",
  "uuid",
 ]

+ 1 - 0
Cargo.toml

@@ -12,3 +12,4 @@ mongodb = {version = "2.8.0", features = ["tokio-runtime"]}
 bson = "2.8.0"
 uuid = {version = "1", features = ["v4"]}
 regex = "1"
+thiserror = "1.0"

+ 2 - 2
bruno/Suma/User/Create.bru

@@ -13,7 +13,7 @@ post {
 body:json {
   {
     "email": "lee@leemorgan.dev",
-    "password_hash": "something",
-    "password_salt": "Something else"
+    "password_hash": "leerobertmorgan",
+    "password_salt": "leerobertmorgan"
   }
 }

+ 57 - 0
src/app_error.rs

@@ -0,0 +1,57 @@
+use actix_web::{HttpResponse, ResponseError, http::StatusCode};
+use thiserror::Error;
+use serde::Serialize;
+
+#[derive(Debug, Serialize)]
+struct ErrorBody {
+    error: ErrorInfo
+}
+
+#[derive(Debug, Serialize)]
+struct ErrorInfo {
+    code: u16,
+    message: String
+}
+
+#[derive(Debug, Error)]
+pub enum AppError {
+    #[error("Database error")]
+    Database(#[from] mongodb::error::Error),
+
+    #[error("{0}")]
+    InvalidInput(String),
+
+    #[error("{0}")]
+    InternalError(String)
+}
+
+impl ResponseError for AppError {
+    fn status_code(&self) -> StatusCode {
+        match self {
+            AppError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
+            AppError::InvalidInput(_) => StatusCode::BAD_REQUEST,
+            AppError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR
+        }
+    }
+
+    fn error_response(&self) -> HttpResponse {
+        let body = ErrorBody {
+            error: ErrorInfo {
+                code: self.status_code().as_u16(),
+                message: self.to_string()
+            }
+        };
+
+        HttpResponse::build(self.status_code()).json(body)
+    }
+}
+
+impl AppError {
+    pub fn invalid_input(msg: &str) -> Self {
+        AppError::InvalidInput(msg.to_owned())
+    }
+
+    pub fn internal_error(msg: &str) -> Self {
+        AppError::InternalError(msg.to_owned())
+    }
+}

+ 18 - 16
src/controllers/user.rs

@@ -1,32 +1,26 @@
 use actix_web::{post, web, HttpResponse, Responder, http::StatusCode, cookie::Cookie};
-use mongodb::{bson::doc, Database};
+use mongodb::{bson::doc, Database, Collection};
 use crate::models::user::{User, NewUserInput};
 use regex::Regex;
 use crate::http_error::http_error;
 use crate::dto;
+use crate::app_error::AppError;
 
 #[post("/api/user")]
 pub async fn create(
     db: web::Data<Database>,
     payload: web::Json<NewUserInput>
-) -> impl Responder {
+) -> Result<HttpResponse, AppError> {
     let user_collection = db.collection::<User>("users");
     let email = payload.email.to_lowercase();
-    if !valid_email(&email) {
-        return http_error(StatusCode::BAD_REQUEST, "Invalid email");
-    }
-
-    match user_collection.find_one(doc!{"email": email}, None).await {
-        Ok(None) => {},
-        Ok(Some(_)) => return http_error(StatusCode::BAD_REQUEST, "User with this email already exists"),
-        Err(_) => return http_error(StatusCode::INTERNAL_SERVER_ERROR, "Unable to verify email")
-    };
+    valid_email(&email)?;
+    user_exists(&user_collection, &email).await?;
 
     match User::insert(&user_collection, payload.into_inner()).await {
-        Ok(_) => HttpResponse::Ok().body("{'success': 'true'}"),
+        Ok(_) => Ok(HttpResponse::Ok().body("{'success': 'true'}")),
         Err(e) => {
             eprintln!("{}", e);
-            http_error(StatusCode::INTERNAL_SERVER_ERROR, "Failed to create user")
+            Ok(http_error(StatusCode::INTERNAL_SERVER_ERROR, "Failed to create user"))
         }
     }
 }
@@ -63,14 +57,22 @@ pub async fn login(
         .json(response_user(user))
 }
 
-fn valid_email(email: &str) -> bool {
+fn valid_email(email: &str) -> Result<(), AppError> {
     let email_regex: Regex = Regex::new(r#"^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}(\.[0-9]{1,3}){3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$"#).unwrap();
 
     if email_regex.is_match(email) {
-        return true;
+        return Ok(());
     }
 
-    false
+    Err(AppError::invalid_input("Invalid email address"))
+}
+
+async fn user_exists(collection: &Collection<User>, email: &str) -> Result<(), AppError> {
+    match collection.find_one(doc!{"email": email}, None).await {
+        Ok(None) => Ok(()),
+        Ok(Some(_)) => Err(AppError::invalid_input("User with this email already exists")),
+        Err(_) => Err(AppError::internal_error("Unable to save user data"))
+    }
 }
 
 fn response_user(user: User) -> dto::user::ResponseUser {

+ 1 - 0
src/main.rs

@@ -7,6 +7,7 @@ mod models;
 mod controllers;
 mod http_error;
 mod dto;
+mod app_error;
 
 pub static HTML: OnceLock<String> = OnceLock::new();