use rocket::http::Status; use rocket::request::Request; use rocket::response::status; use rocket::response::{self, Responder}; use rocket_contrib::templates::Template; use std::collections::HashMap; use std::convert::Into; use thiserror::Error; #[derive(Error, Debug)] pub enum Error { #[error("resource not found")] NotFound, #[error("you must be logged in")] NotLoggedIn, #[error("you do not have permission to access this")] NoPermission, #[error("invalid input")] InvalidInput, #[error("validation error: {0}")] ValidationError(#[from] crate::models::convert::ValidationError), #[error("serialization error: {0}")] SerializationError(#[from] prost::EncodeError), #[error("deserialization error: {0}")] DeserializationError(#[from] prost::DecodeError), #[error("i/o error: {0}")] IoError(#[from] std::io::Error), #[error("query error: {0}")] QueryError(#[from] sqlx::Error), #[error("rocket error: {0}")] RocketError(#[from] rocket::error::Error), } impl Error { fn is_sensitive(&self) -> bool { use Error::*; match self { QueryError(_) => true, IoError(_) => true, _ => false, } } fn message(&self) -> String { if self.is_sensitive() { "internal error".to_string() } else { self.to_string() } } } impl From for tonic::Status { fn from(err: Error) -> tonic::Status { use tonic::{Code, Status}; use Error::*; match err { NotFound => Status::new(Code::NotFound, err.message()), NotLoggedIn => Status::new(Code::Unauthenticated, err.message()), NoPermission => Status::new(Code::PermissionDenied, err.message()), InvalidInput => Status::new(Code::InvalidArgument, err.message()), _ => Status::new(Code::Internal, err.message()), } } } #[rocket::async_trait] impl<'r> Responder<'r, 'static> for Error { fn respond_to(self, req: &Request) -> response::Result<'static> { log::error!("error: {0}", self.to_string()); //Hide sensitive error information let message: String = if self.is_sensitive() { "internal error".into() } else { self.to_string() }; let mut context = HashMap::new(); context.insert("message", message); let resp = Template::render("error", context).respond_to(req)?; use Error::*; match self { NotFound => status::NotFound(resp).respond_to(req), NotLoggedIn => status::Forbidden(Some(resp)).respond_to(req), NoPermission => status::Forbidden(Some(resp)).respond_to(req), _ => status::Custom(Status::InternalServerError, resp).respond_to(req), } } }