Compare commits
No commits in common. "noErrors" and "master" have entirely different histories.
@ -13,7 +13,7 @@ repository = "https://github.com/franklinblanco/err.git"
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0.48"
|
||||
sqlx = { version = "0.8", features = ["json"] }
|
||||
sqlx = { version = "0.7", features = ["json"] }
|
||||
stdext = "0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
127
src/lib.rs
127
src/lib.rs
@ -1,7 +1,10 @@
|
||||
mod macros;
|
||||
mod tests;
|
||||
|
||||
use std::fmt::Display;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Serialize, Deserialize, Serializer};
|
||||
use serde::ser::SerializeMap;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use stdext::function_name;
|
||||
|
||||
@ -22,6 +25,51 @@ impl Trace {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Traces { pub traces: Vec<Trace> }
|
||||
|
||||
|
||||
#[derive(Serialize, Debug, Error)]
|
||||
#[error("MessageResource: {message_resource} ErrorType: {error_type} Trace: {trace:#?}")]
|
||||
pub struct Error {
|
||||
pub trace: Traces,
|
||||
#[serde(rename = "messageResource")]
|
||||
pub message_resource: MessageResource,
|
||||
#[serde(rename = "errorType")]
|
||||
pub error_type: ErrorType,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(trace: Trace) -> Self {
|
||||
Self {
|
||||
trace: Traces { traces: Vec::from([trace]) },
|
||||
message_resource: MessageResource::new("errors.backend.common.default", "We still don't have an error defined for this."),
|
||||
error_type: ErrorType::Unspecified,
|
||||
}
|
||||
}
|
||||
pub fn push_trace(mut self, trace: Trace) -> Self {
|
||||
self.trace.traces.push(trace);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn push_error(mut self, error: Error) -> Self {
|
||||
self.error_type = ErrorType::Nested { nested: Box::new(error) };
|
||||
self
|
||||
}
|
||||
pub fn key(mut self, key: Option<impl ToString>) -> Self {
|
||||
self.message_resource.key = match key {
|
||||
None => None,
|
||||
Some(key) => Some(key.to_string()),
|
||||
};
|
||||
self
|
||||
}
|
||||
pub fn message(mut self, message: impl ToString) -> Self {
|
||||
self.message_resource.message = message.to_string();
|
||||
self
|
||||
}
|
||||
pub fn error_type(mut self, error_type: ErrorType) -> Self {
|
||||
self.error_type = error_type;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// This is for sending errors back from requests conveniently.
|
||||
/// This struct contains an optional key just in
|
||||
/// case you want to deal with internationalization.
|
||||
@ -62,6 +110,83 @@ impl Default for MessageResource{
|
||||
Self { key: None, message: "".to_string() }
|
||||
}
|
||||
}
|
||||
/// This is supposed to be used whenever you have an error in your code and want to be more specific about it.
|
||||
/// Fits in with most CRUD web apps. What you send back to the client is a MessageResource, not the error itself!
|
||||
#[derive(Serialize, Debug, Error)]
|
||||
pub enum ErrorType {
|
||||
#[error("Network Error")]
|
||||
Network,
|
||||
#[error("IO error")]
|
||||
IO,
|
||||
#[error("Privilege Error")]
|
||||
Privilege,
|
||||
#[error("Unexpected Status Code. Expected: {expected} Actual: {actual}")]
|
||||
UnexpectedStatusCode { expected: u16, actual: u16 },
|
||||
#[error("Serde Error. Attempted to Serialize/Deserialize String: {text}")]
|
||||
Serde { text: String },
|
||||
#[error("Parsing error.")]
|
||||
Parser,
|
||||
#[error("Service Error: {error}")]
|
||||
Service { error: ServiceError },
|
||||
#[error("Unspecified Error")]
|
||||
Unspecified,
|
||||
#[error("Unexpected Error: {message}")]
|
||||
Unexpected { message: String },
|
||||
#[error("Nested Error: {nested}")]
|
||||
Nested { nested: Box<Error> },
|
||||
}
|
||||
|
||||
#[derive(Error, Serialize, Debug)]
|
||||
pub enum ServiceError {
|
||||
/// Used to return a simple error from FromStr implementations.
|
||||
#[error("Error parsing string into value")]
|
||||
FromStrError,
|
||||
/// Every error that is returned from a DAO operation.
|
||||
#[error("Error from the Database: {error}")]
|
||||
#[serde(serialize_with = "ser_with")]
|
||||
DatabaseError {
|
||||
#[from]
|
||||
error: sqlx::Error
|
||||
},
|
||||
/// A vec of ValidationErrors
|
||||
#[error("Error Operation Not Allowed: {message}")]
|
||||
NotAllowed { message: String },
|
||||
#[error("Validation Errors: {errors:?}")]
|
||||
ValidationErrors { errors: Vec<ValidationError> },
|
||||
/// Something already exists. That something should be {0}
|
||||
/// Example: "User" "Credential"
|
||||
#[error("Error {message} Already exists.")]
|
||||
AlreadyExistsError { message: String },
|
||||
/// Example: "User with id X"
|
||||
#[error("{message} Not found.")]
|
||||
NotFoundError { message: String },
|
||||
/// Used to specify authentication error.
|
||||
/// Example: Password incorrect for user
|
||||
#[error("Credential supplied is incorrect. {message}")]
|
||||
IncorrectCredentialError { message: String },
|
||||
#[error("Too many credentials supplied, maximum is 3.")]
|
||||
TooManyCredentialsError,
|
||||
/// Used for anything else.
|
||||
#[error("Unexpected Error: {message}")]
|
||||
UnexpectedError { message: String },
|
||||
}
|
||||
|
||||
/// Any string validation error such as Phone number validation or email, etc...
|
||||
/// Reason should be a Key for internationalization
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)]
|
||||
#[error("Error validating `{what}`. Reason: {reason}")]
|
||||
pub struct ValidationError {
|
||||
pub what: String,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
pub fn ser_with<S>(id: &sqlx::Error, s: S) -> Result<S::Ok, S::Error> where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut ser = s.serialize_map(Some(1))?;
|
||||
ser.serialize_entry("$oid", &id.to_string())?;
|
||||
ser.end()
|
||||
}
|
||||
|
||||
pub trait VecRemove {
|
||||
type T;
|
||||
|
@ -24,4 +24,46 @@ macro_rules! test_trace {
|
||||
service: env!("CARGO_PKG_NAME").into(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro used to 'unwrap' a result that returns a Error
|
||||
///
|
||||
/// If there's an error returns the generated Error and push a trace on it.
|
||||
#[allow(unused_macros)]
|
||||
#[macro_export]
|
||||
macro_rules! u_res_or_res {
|
||||
( $e:expr ) => {
|
||||
match $e {
|
||||
Ok(result) => result,
|
||||
Err(mut error) => return Err(error.push_trace(err::trace!()))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro used to 'unwrap' a result that returns a Error
|
||||
///
|
||||
/// If there's an error returns the generated Error and push a trace on it.
|
||||
#[allow(unused_macros)]
|
||||
#[macro_export]
|
||||
macro_rules! x_u_res_or_res {
|
||||
( $e:expr, $t:expr ) => {
|
||||
match $e {
|
||||
Ok(result) => result,
|
||||
Err(error) => return Err(err::Error::new(err::trace!()).message(error.to_string()).error_type($t))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro used to 'unwrap' a result that returns a Error
|
||||
///
|
||||
/// If there's an error returns the generated Error and push a trace on it.
|
||||
#[allow(unused_macros)]
|
||||
#[macro_export]
|
||||
macro_rules! x_u_res_db_or_res {
|
||||
( $e:expr ) => {
|
||||
match $e {
|
||||
Ok(result) => result,
|
||||
Err(error) => return Err(err::Error::new(err::trace!()).message(error.to_string()).error_type(err::ErrorType::Service { error: err::ServiceError::DatabaseError{error: error} } ))
|
||||
}
|
||||
};
|
||||
}
|
45
src/tests.rs
Normal file
45
src/tests.rs
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Error, ErrorType, ServiceError, test_trace};
|
||||
|
||||
#[test]
|
||||
fn print_json() {
|
||||
let error = Error::new(test_trace!())
|
||||
.message("Hi there g")
|
||||
.push_trace(test_trace!())
|
||||
.key(Some("This key"))
|
||||
.push_error(
|
||||
Error::new(test_trace!())
|
||||
.error_type(ErrorType::Service { error: ServiceError::AlreadyExistsError { message: String::from("Hey now") } })
|
||||
.message("Hi there g")
|
||||
.push_trace(test_trace!())
|
||||
.key(Some("key for nested err"))
|
||||
);
|
||||
println!("Object in rust: {:#?}", error);
|
||||
println!("");
|
||||
println!("#############################################");
|
||||
println!("");
|
||||
println!("Object in json: {}", serde_json::to_string_pretty(&error).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_json_with_db_error_variant() {
|
||||
let error = Error::new(test_trace!())
|
||||
.message("Hi there g")
|
||||
.push_trace(test_trace!())
|
||||
.key(Some("This key"))
|
||||
.push_error(
|
||||
Error::new(test_trace!())
|
||||
.error_type(ErrorType::Service { error: ServiceError::DatabaseError { error: sqlx::Error::Protocol("AAAA".into()) } })
|
||||
.message("Hi there g")
|
||||
.push_trace(test_trace!())
|
||||
.key(Some("key for nested err"))
|
||||
);
|
||||
println!("Object in rust: {:#?}", error);
|
||||
println!("");
|
||||
println!("#############################################");
|
||||
println!("");
|
||||
println!("Object in json: {}", serde_json::to_string_pretty(&error).unwrap())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user