Compare commits

..

3 Commits
old ... master

Author SHA1 Message Date
Franklin 86e5626f7c Added macros and err lib to local 2023-10-03 08:00:59 -04:00
Franklin b1394af5d7 Updated to fit with err lib 2023-09-30 10:57:41 -04:00
Franklin ea1d463f33 Switched to new Err version & adding macros 2023-09-27 20:29:42 -04:00
16 changed files with 1171 additions and 267 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

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/actix-web-addons.iml" filepath="$PROJECT_DIR$/.idea/actix-web-addons.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

@ -1,111 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="CargoProjects">
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
</component>
<component name="ChangeListManager">
<list default="true" id="85fee226-f66d-4ed1-a14d-358b91c3184b" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/extensions/typed_response.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/extensions/typed_response.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/lib.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/utils/macros.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/utils/macros.rs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Rust File" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="6g6o3427" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3
}</component>
<component name="ProjectId" id="2VqtGkUZMEh8ceSz04JfaC9i9zR" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;last_opened_file_path&quot;: &quot;/Users/franklinblanco/Desktop/Code/rust/libs/actix-web-addons&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;org.rust.cargo.project.model.PROJECT_DISCOVERY&quot;: &quot;true&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;language.rust.cargo.check&quot;
}
}</component>
<component name="RsExternalLinterProjectSettings">
<option name="runOnTheFly" value="true" />
</component>
<component name="RunManager">
<configuration name="Expand actix_web_utils::traits::macro_traits" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="expand --lib --color=always --theme=Dracula --tests traits::macro_traits" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="Cargo.Expand actix_web_utils::traits::macro_traits" />
</list>
</recent_temporary>
</component>
<component name="RustProjectSettings">
<option name="toolchainHomeDirectory" value="$USER_HOME$/.cargo/bin" />
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="85fee226-f66d-4ed1-a14d-358b91c3184b" name="Changes" comment="" />
<created>1695575344959</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1695575344959</updated>
<workItem from="1695575345983" duration="3481000" />
<workItem from="1695658537892" duration="316000" />
<workItem from="1695843204961" duration="6111000" />
<workItem from="1695867333996" duration="3044000" />
<workItem from="1695994882561" duration="611000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="XSLT-Support.FileAssociations.UIState">
<expand />
<select />
</component>
</project>

1014
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,4 +15,4 @@ actix-web = "4.1.0"
serde_json = { version = "1" } serde_json = { version = "1" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
log = { version = "0.4", features = ["serde"] } log = { version = "0.4", features = ["serde"] }
err = { git = "https://git.franklinblanco.dev/franklinblanco/err.git", branch = "old" } err = { path = "/Users/franklinblanco/Desktop/Code/rust/libs/dev-deps/err" }

View File

@ -1,5 +0,0 @@
/// These enums just provide more modularity and readability to Macros defined in this crate.
pub enum ReturnValue {
}

View File

@ -1 +0,0 @@
pub mod macro_enums;

View File

@ -1,3 +1,4 @@
pub mod typed_response; pub mod typed_response;
pub mod logger; pub mod logger;
pub mod generic_error; pub mod generic_error;
pub mod service_response;

View File

@ -0,0 +1,21 @@
use err::Error;
use serde::Serialize;
use crate::extensions::typed_response::TypedResponse;
pub trait IntoResponse<T> where T: Serialize {
fn to_response(self) -> TypedResponse<T>;
}
/// ## Type alias for Result<T, (StatusCode, Error)>.
/// Implements IntoResponse.
/// This is used to indicate the return type from a service function, but it is still usable as is.
pub type ServiceResponse<T> = Result<T, (u16, Error)>;
impl<T> IntoResponse<T> for ServiceResponse<T> where T: Serialize {
fn to_response(self) -> TypedResponse<T> {
match self {
Ok(response) => TypedResponse::std_response(200, response),
Err((status, error)) => TypedResponse::std_error(status, error),
}
}
}

View File

@ -1,74 +1,91 @@
use actix_web::{HttpResponse, http::StatusCode, web, HttpRequest, HttpResponseBuilder, body::BoxBody, Responder}; use actix_web::{HttpResponse, http::StatusCode, web, HttpRequest, HttpResponseBuilder, body::BoxBody, Responder};
use serde::Serialize; use serde::Serialize;
use err::MessageResource; use err::Error;
/// Defines a type for actix web routes. As the current implementation of HttpResponse doesn't let you manually specify a type. /// Defines a type for actix web routes. As the current implementation of HttpResponse doesn't let you manually specify a type.
/// ``` pub struct TypedResponse<B: Serialize = String> {
/// pub response: HttpResponse<Option<web::Json<Result<B, Error>>>>,
/// use actix_web::{web::{Path}, http::StatusCode};
/// use actix_web_utils::extensions::typed_response::TypedHttpResponse;
/// use actix_web_utils::dtos::message::MessageResource;
///
/// //Sample route
/// pub async fn testroute(number: Path<i32>) -> TypedHttpResponse<String> {
/// if(*number > 0){
/// return TypedHttpResponse::return_standard_response(StatusCode::OK, String::from("This is my test response!"));
/// }
/// TypedHttpResponse::return_empty_response(StatusCode::BAD_REQUEST)
/// }
///
/// ```
pub struct TypedHttpResponse<B: Serialize = String> {
pub response: HttpResponse<Option<web::Json<Result<B, Vec<MessageResource>>>>>,
} }
impl<B: Serialize> TypedHttpResponse<B> { impl<B: Serialize> TypedResponse<B> {
/// Returns a response with the json struct you define inside + Status code /// Returns a response with the json struct you define inside + Status code
/// ``` /// ```
/// use actix_web_utils::extensions::typed_response::TypedHttpResponse; /// use actix_web_utils::extensions::typed_response::TypedResponse;
/// ///
/// TypedHttpResponse::return_standard_response(200, String::from("Anything in here")); //Instead of string you can put anything here as long as it is serializable. /// TypedResponse::std_response(200, String::from("Anything in here")); //Instead of string you can put anything here as long as it is serializable.
/// ///
/// ``` /// ```
pub fn return_standard_response(status_code: u16, body: B) -> TypedHttpResponse<B>{ pub fn std_response(status_code: u16, body: B) -> TypedResponse<B>{
TypedHttpResponse { TypedResponse {
response: HttpResponse::with_body(StatusCode::from_u16(u16::from(status_code)).unwrap(), Some(web::Json(Ok(body)))) response: HttpResponse::with_body(StatusCode::from_u16(u16::from(status_code)).unwrap(), Some(web::Json(Ok(body))))
} }
} }
/// Returns a response with the json error list inside + Status code
pub fn return_standard_error_list(status_code: u16, body: Vec<MessageResource>) -> TypedHttpResponse<B>{
TypedHttpResponse {
response: HttpResponse::with_body(StatusCode::from_u16(u16::from(status_code)).unwrap(), Some(web::Json(Err(body))))
}
}
/// This is to return a MessageResource wrapped inside an HttpResponse /// This is to return a MessageResource wrapped inside an HttpResponse
pub fn return_standard_error(status_code: u16, body: MessageResource) -> TypedHttpResponse<B>{ pub fn std_error(status_code: u16, error: Error) -> TypedResponse<B>{
TypedHttpResponse { TypedResponse {
response: HttpResponse::with_body(StatusCode::from_u16(u16::from(status_code)).unwrap(), Some(web::Json(Err(vec![body])))) response: HttpResponse::with_body(StatusCode::from_u16(u16::from(status_code)).unwrap(), Some(web::Json(Err(error))))
} }
} }
/// Returns an empty http response with a status code /// Returns an empty http response with a status code
/// This is a bad practice, but I still left it here /// This is a bad practice, but I still left it here
/// as it is useful for debugging & specific cases /// as it is useful for debugging & specific cases
pub fn return_empty_response(status_code: u16) -> TypedHttpResponse<B>{ pub fn empty_response(status_code: u16) -> TypedResponse<B>{
TypedHttpResponse { TypedResponse {
response: HttpResponse::with_body(StatusCode::from_u16(u16::from(status_code)).unwrap(), None) response: HttpResponse::with_body(StatusCode::from_u16(u16::from(status_code)).unwrap(), None)
} }
} }
} }
impl<T: Serialize> Responder for TypedHttpResponse<T> impl<T: Serialize> Responder for TypedResponse<T> {
{
type Body = BoxBody; type Body = BoxBody;
#[inline] #[inline]
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> { fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
let mut builder = HttpResponseBuilder::new(self.response.status()); let mut builder = HttpResponseBuilder::new(self.response.status());
for header in self.response.headers() {
builder.append_header(header);
}
match self.response.body() { match self.response.body() {
Some(body) => {match body.0.as_ref() { Some(body) => match body.0.as_ref() {
Ok(value) => builder.json(value), Ok(value) => builder.json(value),
Err(errors) => builder.json(errors), Err(errors) => builder.json(errors),
}}, },
None => {builder.finish()} None => builder.finish(),
} }
} }
} }
impl<'a, T: Serialize> Responder for &'a TypedResponse<T> {
type Body = BoxBody;
#[inline]
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
let mut builder = HttpResponseBuilder::new(self.response.status());
for header in self.response.headers() {
builder.append_header(header);
}
match self.response.body() {
Some(body) => match body.0.as_ref() {
Ok(value) => builder.json(value),
Err(errors) => builder.json(errors),
},
None => builder.finish(),
}
}
}
impl<'a, T: Serialize> Responder for &'a mut TypedResponse<T> {
type Body = BoxBody;
#[inline]
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
let mut builder = HttpResponseBuilder::new(self.response.status());
for header in self.response.headers() {
builder.append_header(header);
}
match self.response.body() {
Some(body) => match body.0.as_ref() {
Ok(value) => builder.json(value),
Err(errors) => builder.json(errors),
},
None => builder.finish(),
}
}
}

View File

@ -1,4 +1,5 @@
pub mod extensions; pub mod extensions;
pub mod utils; pub mod utils;
pub mod enums;
pub mod traits; pub use extensions::typed_response::TypedResponse;
pub use extensions::service_response::ServiceResponse;

View File

@ -1,39 +0,0 @@
use std::fmt::Display;
use serde::Serialize;
use err::{Error, MessageResource};
use crate::{extensions::{typed_response::TypedHttpResponse, generic_error::GenericError}};
/// This trait aims to aid macros defined in this crate so that the macro can take any shape of error and
/// do the same thing for all.
pub trait ReturnableErrorShape {
fn convert_to_returnable<T: Serialize> (&self, status_code: u16) -> TypedHttpResponse<T>;
}
impl ReturnableErrorShape for MessageResource {
fn convert_to_returnable<T: Serialize>(&self, status_code: u16) -> TypedHttpResponse<T> {
TypedHttpResponse::return_standard_error(status_code, self.clone())
}
}
impl ReturnableErrorShape for Error {
fn convert_to_returnable<T: Serialize>(&self, status_code: u16) -> TypedHttpResponse<T> {
match self {
Error::Unspecified => TypedHttpResponse::return_standard_error(status_code, MessageResource::from(self)),
Error::Network(message) => TypedHttpResponse::return_standard_error(status_code, message.clone()),
Error::UnexpectedStatusCode(_, actual, messages) => TypedHttpResponse::return_standard_error_list(*actual, messages.clone()),
Error::Serde(message) => TypedHttpResponse::return_standard_error(status_code, message.clone()),
Error::IO(message) => TypedHttpResponse::return_standard_error(status_code, message.clone()),
Error::Privilege(message) => TypedHttpResponse::return_standard_error(status_code, message.clone()),
Error::Parser(message) => TypedHttpResponse::return_standard_error(status_code, message.clone()),
}
}
}
impl ReturnableErrorShape for Vec<MessageResource> {
fn convert_to_returnable<T: Serialize>(&self, status_code: u16) -> TypedHttpResponse<T> {
TypedHttpResponse::return_standard_error_list(status_code, self.to_vec())
}
}
impl<E: Display> ReturnableErrorShape for GenericError<E>{
fn convert_to_returnable<T: Serialize>(&self, status_code: u16) -> TypedHttpResponse<T> {
TypedHttpResponse::return_standard_error(status_code, MessageResource::new_from_str(&self.error.to_string()))
}
}

View File

@ -1 +0,0 @@
pub mod macro_traits;

View File

@ -1,40 +1,106 @@
/// This is to minimize the amount of matches made in the code
/// Give it a Result<Whatever_you_want_to_return, Error> and the type of the success and it'll
/// Basically unwrap the result if its there and if it isn't it'll return a handled error inside a TypedHttpResponse. /// # Unwrap result or return service response
/// Default status code is InternalServerError, if you want something different pass it as the first argument as a u16. /// Unwraps or turns Result<T, Error> -> ServiceResponse error variant
/// If you want to also return the success result, then pass a valid status code u16 as a second argument
/// Sorry for defining the error status code first, it's to maintain uniform order.
#[allow(unused_macros)] #[allow(unused_macros)]
#[macro_export] #[macro_export]
macro_rules! unwrap_or_return_handled_error { macro_rules! u_res_or_sr {
( $e:expr, $type_of_resp:ty ) => { ( $e:expr ) => {
match $e { match $e {
Ok(value) => value, Ok(result) => result,
Err(error) => return actix_web_utils::traits::macro_traits::ReturnableErrorShape::convert_to_returnable::<$type_of_resp>(&error, 500) Err(error) => return Err((500, error.push_trace(err::trace!())))
} }
}; };
( $error_status_code:literal, $e:expr, $type_of_resp:ty ) => { ( $e:expr, $status:expr ) => {
match $e { match $e {
Ok(value) => value, Ok(result) => result,
Err(error) => return actix_web_utils::traits::macro_traits::ReturnableErrorShape::convert_to_returnable::<$type_of_resp>(&error, $error_status_code) Err(mut error) => return Err(($status, error.push_trace(macros::trace!())))
} }
}; };
( $error_status_code:literal, $success_status_code:literal, $e:expr, $type_of_resp:ty) => { ( $e:expr, $status:expr, $err:expr ) => {
match $e { match $e {
Ok(value) => return actix_web_utils::extensions::typed_response::TypedHttpResponse::return_standard_response($success_status_code, value), Ok(result) => result,
Err(error) => return actix_web_utils::traits::macro_traits::ReturnableErrorShape::convert_to_returnable::<$type_of_resp>(&error, $error_status_code) Err(mut error) => return Err(($status, error.push_error($err)))
}
} }
};
} }
/// Takes whatever error you supply to it and wraps it in a GenericError<E> if err /// # Unwrap option or return service response
/// Unwraps or turns Option<T> -> ServiceResponse error variant
#[allow(unused_macros)] #[allow(unused_macros)]
#[macro_export] #[macro_export]
macro_rules! wrap_generic_error_in_wrapper { macro_rules! u_opt_or_sr {
( $e:expr ) => {
match $e {
Some(value) => value,
None => return Err((500, err::Error::new(actix_web_utils::trace!()).message("Option variant that was expected to be Option::Some(T) was Option::None.")))
}
};
}
/// 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(actix_web_utils::trace!()))
}
};
}
/// Macro used to 'unwrap' a result that returns an unknown error type
#[allow(unused_macros)]
#[macro_export]
macro_rules! x_u_res_or_sr {
( $e:expr, $se:expr ) => {
match $e {
Ok(result) => result,
Err(error) => return Err((400, err::Error::new(err::trace!()).error_type(err::ErrorType::Service($se))))
}
};
( $e:expr, $se:expr, $status:expr ) => {
match $e {
Ok(result) => result,
Err(error) => return Err($status, err::Error::new(err::trace!()).typed(err::error_type(err::ErrorType::Service($se))))
}
};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! x_e_res_or_db {
( $e:expr ) => { ( $e:expr ) => {
match $e { match $e {
Ok(value) => Ok(value), Ok(value) => Ok(value),
Err(error) => Err(actix_web_utils::extensions::generic_error::GenericError::wrap(error)), Err(error) => {
return Err(RBError::new("database.on-read-write", macros::trace!()).typed(dtos::errors::error::RBErrorType::Database).message(error.to_string()).log_debug())
} }
} }
};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! x_u_res_db_or_sr {
( $e:expr ) => {
match $e {
Ok(value) => value,
Err(error) => {
return Err((500, err::Error::new(err::trace!()).error_type(err::ErrorType::Service(err::ServiceError::DatabaseError(error)))))
}
}
};
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! service_error {
( $status:expr, $errtype:expr ) => {
Err(($status, err::Error::new(err::trace!()).error_type(err::ErrorType::Service($errtype))))
};
} }