Compare commits

...

4 Commits
old ... master

Author SHA1 Message Date
Franklin f2ca8260ab Macros fixed to accomodate err kotlin and ios 2023-10-27 20:43:07 -04:00
Franklin 68dfdca2a7 Adapted err library, dtos and enums to fit Kotlin and Swift data types 2023-10-27 09:31:22 -04:00
Franklin 18cc77b626 added a few macros to help with error propagation 2023-09-30 10:13:03 -04:00
Franklin c59526086f Upgraded Err library 2023-09-27 20:29:03 -04:00
8 changed files with 315 additions and 23 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

11
.idea/err.iml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/err.iml" filepath="$PROJECT_DIR$/.idea/err.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -11,4 +11,10 @@ repository = "https://github.com/franklinblanco/err.git"
[lib]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0.48"
sqlx = { version = "0.7", features = ["json"] }
stdext = "0.3.1"
[dev-dependencies]
serde_json = { version = "1" }

View File

@ -1,5 +1,74 @@
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;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Trace {
pub line: u32,
pub function: String,
pub file: String,
pub service: String,
}
impl Trace {
pub fn set_func_name(mut self, fn_name: impl ToString) -> Trace {
self.function = fn_name.to_string();
self
}
}
#[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
@ -11,11 +80,13 @@ pub struct MessageResource {
pub key: Option<String>,
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() }
@ -41,27 +112,95 @@ impl Default for MessageResource{
}
/// 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(Debug, Clone)]
pub enum Error {
Network(MessageResource),
IO(MessageResource),
Privilege(MessageResource),
UnexpectedStatusCode(u16, u16, Vec<MessageResource>),
Serde(MessageResource),
Parser(MessageResource),
Unspecified
#[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> },
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Network(message) => write!(f, "Error of type Network. MessageResource: {message}"),
Error::IO(message) => write!(f, "Error of type IO. MessageResource: {message}"),
Error::Privilege(message) => write!(f, "Error of type Privilege. MessageResource: {message}"),
Error::UnexpectedStatusCode(expected, actual, messages) => write!(f, "Error of type UnexpectedStatusCode. Expected: {expected}, Actual: {actual}, MessageResources: {:#?}", messages),
Error::Serde(message) => write!(f, "Error of type Serialization/Deserialization. MessageResource: {message}"),
Error::Unspecified => write!(f, "Error of type Unspecified."),
Error::Parser(message) => write!(f, "Error of type Parser. MessageResource: {message}"),
#[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;
fn try_remove(&mut self, index: usize) -> Option<Self::T>;
}
impl<T> VecRemove for Vec<T> {
type T = T;
fn try_remove(&mut self, index: usize) -> Option<Self::T> {
if index < self.len() {
Some(self.remove(index))
} else {
None
}
}
}
impl std::error::Error for Error {}
}

69
src/macros.rs Normal file
View File

@ -0,0 +1,69 @@
/// 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! trace {
() => {
err::Trace {
line: line!(),
function: err::function_name!().into(),
file: file!().into(),
service: env!("CARGO_PKG_NAME").into(),
}
};
}
/// 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.
#[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
View 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())
}
}