diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -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 diff --git a/.idea/err.iml b/.idea/err.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/.idea/err.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..6ebd94c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 5498af9..70ef24c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,6 @@ repository = "https://github.com/franklinblanco/err.git" [lib] [dependencies] -serde = { version = "1.0", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0.48" +sqlx = { version = "0.7", features = ["json"] } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 907e7ef..3e51805 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,54 @@ use std::fmt::Display; -use serde::{Serialize, Deserialize}; +use serde::{Serialize, Deserialize, Serializer}; +use serde::ser::SerializeMap; +use thiserror::Error; + +#[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, +} +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Traces(pub Vec); +#[derive(Serialize, Debug)] +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(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 + } + pub fn push_error(mut self, error: Error) -> Self { + self.error_type = ErrorType::Nested(Box::new(error)); + self + } + pub fn key(mut self, key: Option) -> 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 + } +} /// This is for sending errors back from requests conveniently. /// This struct contains an optional key just in @@ -41,27 +90,73 @@ 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), - 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: {0} Actual: {1}")] + UnexpectedStatusCode(u16, u16), + #[error("Serde Error. Attempted to Serialize/Deserialize String: {0}")] + Serde(String), + #[error("Parsing error.")] + Parser, + #[error("Service Error: {0}")] + Service(ServiceError), + #[error("Unspecified Error")] + Unspecified, + #[error("Unexpected Error: {0}")] + Unexpected(String), + #[error("Nested Error: {0:?}")] + Nested(Box), } -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: {0}")] + #[serde(serialize_with = "ser_with")] + DatabaseError(#[from] sqlx::Error), + /// A vec of ValidationErrors + #[error("Validation Errors: {0:?}")] + ValidationErrors(Vec), + /// Something already exists. That something should be {0} + /// Example: "User" "Credential" + #[error("Error {0} Already exists.")] + AlreadyExistsError(String), + /// Example: "User with id X" + #[error("{0} Not found.")] + NotFoundError(String), + /// Used to specify authentication error. + /// Example: Password incorrect for user + #[error("Credential supplied is incorrect. {0}")] + IncorrectCredentialError(String), + #[error("Too many credentials supplied, maximum is 3.")] + TooManyCredentialsError, + /// Used for anything else. + #[error("Unexpected Error: {0}")] + UnexpectedError(String), } -impl std::error::Error for Error {} \ No newline at end of file + +/// 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(id: &sqlx::Error, s: S) -> Result where + S: Serializer, +{ + let mut ser = s.serialize_map(Some(1))?; + ser.serialize_entry("$oid", &id.to_string())?; + ser.end() +} \ No newline at end of file