diff --git a/Cargo.toml b/Cargo.toml index c0bf3e7..d96aa08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,7 @@ repository = "https://github.com/franklinblanco/err.git" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0.48" sqlx = { version = "0.7", features = ["json"] } -stdext = "0.3.1" \ No newline at end of file +stdext = "0.3.1" + +[dev-dependencies] +serde_json = { version = "1" } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9584798..a2b5f31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ mod macros; +mod tests; use std::fmt::Display; use serde::{Serialize, Deserialize, Serializer}; @@ -7,6 +8,7 @@ use thiserror::Error; pub use stdext::function_name; + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct Trace { pub line: u32, @@ -21,7 +23,7 @@ impl Trace { } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Traces(pub Vec); +pub struct Traces { pub traces: Vec } #[derive(Serialize, Debug, Error)] @@ -37,17 +39,18 @@ pub struct Error { impl Error { pub fn new(trace: Trace) -> Self { Self { - trace: Traces(Vec::from([trace])), + 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.0.push(trace); + self.trace.traces.push(trace); self } + pub fn push_error(mut self, error: Error) -> Self { - self.error_type = ErrorType::Nested(Box::new(error)); + self.error_type = ErrorType::Nested { nested: Box::new(error) }; self } pub fn key(mut self, key: Option) -> Self { @@ -77,11 +80,13 @@ pub struct MessageResource { pub key: Option, pub message: String, } + impl Display for MessageResource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "MessageResource Key: {:#?}, Message: {}", self.key, self.message) } } + impl MessageResource { pub fn new(key: &str, msg: &str) -> Self { Self { key: Some(key.to_string()), message: msg.to_string() } @@ -115,20 +120,20 @@ pub enum ErrorType { IO, #[error("Privilege Error")] Privilege, - #[error("Unexpected Status Code. Expected: {0} Actual: {1}")] - UnexpectedStatusCode(u16, u16), - #[error("Serde Error. Attempted to Serialize/Deserialize String: {0}")] - Serde(String), + #[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: {0}")] - Service(ServiceError), + #[error("Service Error: {error}")] + Service { error: ServiceError }, #[error("Unspecified Error")] Unspecified, - #[error("Unexpected Error: {0}")] - Unexpected(String), - #[error("Nested Error: {0:?}")] - Nested(Box), + #[error("Unexpected Error: {message}")] + Unexpected { message: String }, + #[error("Nested Error: {nested}")] + Nested { nested: Box }, } #[derive(Error, Serialize, Debug)] @@ -137,28 +142,33 @@ pub enum ServiceError { #[error("Error parsing string into value")] FromStrError, /// Every error that is returned from a DAO operation. - #[error("Error from the Database: {0}")] + #[error("Error from the Database: {error}")] #[serde(serialize_with = "ser_with")] - DatabaseError(#[from] sqlx::Error), + DatabaseError { + #[from] + error: sqlx::Error + }, /// A vec of ValidationErrors - #[error("Validation Errors: {0:?}")] - ValidationErrors(Vec), + #[error("Error Operation Not Allowed: {message}")] + NotAllowed { message: String }, + #[error("Validation Errors: {errors:?}")] + ValidationErrors { errors: Vec }, /// Something already exists. That something should be {0} /// Example: "User" "Credential" - #[error("Error {0} Already exists.")] - AlreadyExistsError(String), + #[error("Error {message} Already exists.")] + AlreadyExistsError { message: String }, /// Example: "User with id X" - #[error("{0} Not found.")] - NotFoundError(String), + #[error("{message} Not found.")] + NotFoundError { message: String }, /// Used to specify authentication error. /// Example: Password incorrect for user - #[error("Credential supplied is incorrect. {0}")] - IncorrectCredentialError(String), + #[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: {0}")] - UnexpectedError(String), + #[error("Unexpected Error: {message}")] + UnexpectedError { message: String }, } /// Any string validation error such as Phone number validation or email, etc... @@ -176,4 +186,21 @@ pub fn ser_with(id: &sqlx::Error, s: S) -> Result where let mut ser = s.serialize_map(Some(1))?; ser.serialize_entry("$oid", &id.to_string())?; ser.end() +} + +pub trait VecRemove { + type T; + fn try_remove(&mut self, index: usize) -> Option; +} + +impl VecRemove for Vec { + type T = T; + + fn try_remove(&mut self, index: usize) -> Option { + if index < self.len() { + Some(self.remove(index)) + } else { + None + } + } } \ No newline at end of file diff --git a/src/macros.rs b/src/macros.rs index 4ae8207..45ed882 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -12,6 +12,20 @@ macro_rules! trace { }; } +/// Macro used to generate the trace object, must be called from the place where it originates, don't call from another function. +#[allow(unused_macros)] +#[macro_export] +macro_rules! test_trace { + () => { + crate::Trace { + line: line!(), + function: crate::function_name!().into(), + file: file!().into(), + 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. diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..2424030 --- /dev/null +++ b/src/tests.rs @@ -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()) + } +} \ No newline at end of file