diff --git a/Cargo.lock b/Cargo.lock index 0e45915379..fa1ee72716 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,11 +304,14 @@ checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" name = "api_models" version = "0.1.0" dependencies = [ + "actix-web", "common_utils", "error-stack", "frunk", "frunk_core", "masking", + "mime", + "reqwest", "router_derive", "serde", "serde_json", @@ -2050,6 +2053,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + [[package]] name = "libmimalloc-sys" version = "0.1.30" @@ -2304,6 +2313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2713,9 +2723,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70" dependencies = [ "bit-set", "bitflags", @@ -2729,6 +2739,7 @@ dependencies = [ "regex-syntax", "rusty-fork", "tempfile", + "unarray", ] [[package]] @@ -3072,7 +3083,7 @@ dependencies = [ "thiserror", "time", "tokio", - "toml 0.7.0", + "toml 0.7.2", "url", "utoipa", "uuid", @@ -3327,9 +3338,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c68e921cef53841b8925c2abadd27c9b891d9613bdc43d6b823062866df38e8" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" dependencies = [ "serde", ] @@ -3781,9 +3792,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f560bc7fb3eb31f5eee1340c68a2160cad39605b7b9c9ec32045ddbdee13b85" +checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" dependencies = [ "serde", "serde_spanned", @@ -3793,18 +3804,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "886f31a9b85b6182cabd4d8b07df3b451afcc216563748201490940d2a28ed36" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.0" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d8716cdc5d20ec88a18a839edaf545edc71efa4a5ff700ef4a102c26cd8fa" +checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5" dependencies = [ "indexmap", "nom8", @@ -4039,6 +4050,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-bidi" version = "0.3.8" diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index fd1b8f6908..6575cf2a23 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -6,9 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +actix-web = "4.3.0" error-stack = "0.2.4" frunk = "0.4.1" frunk_core = "0.4.1" +mime = "0.3.16" +reqwest = "0.11.14" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.91" strum = { version = "0.24.1", features = ["derive"] } diff --git a/crates/api_models/src/errors/actix.rs b/crates/api_models/src/errors/actix.rs new file mode 100644 index 0000000000..94d0bf1cc9 --- /dev/null +++ b/crates/api_models/src/errors/actix.rs @@ -0,0 +1,28 @@ +use super::types::ApiErrorResponse; + +impl actix_web::ResponseError for ApiErrorResponse { + fn status_code(&self) -> reqwest::StatusCode { + use reqwest::StatusCode; + + match self { + Self::Unauthorized(_) => StatusCode::UNAUTHORIZED, + Self::ForbiddenCommonResource(_) => StatusCode::FORBIDDEN, + Self::ForbiddenPrivateResource(_) => StatusCode::NOT_FOUND, + Self::Conflict(_) => StatusCode::CONFLICT, + Self::Gone(_) => StatusCode::GONE, + Self::Unprocessable(_) => StatusCode::UNPROCESSABLE_ENTITY, + Self::InternalServerError(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::NotImplemented(_) => StatusCode::NOT_IMPLEMENTED, + Self::ConnectorError(_, code) => *code, + } + } + + fn error_response(&self) -> actix_web::HttpResponse { + use actix_web::http::header; + + actix_web::HttpResponseBuilder::new(self.status_code()) + .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)) + .insert_header((header::VIA, "Juspay_Router")) + .body(self.to_string()) + } +} diff --git a/crates/api_models/src/errors/mod.rs b/crates/api_models/src/errors/mod.rs new file mode 100644 index 0000000000..9ce485e652 --- /dev/null +++ b/crates/api_models/src/errors/mod.rs @@ -0,0 +1,3 @@ +pub mod actix; +pub mod serde; +pub mod types; diff --git a/crates/api_models/src/errors/serde.rs b/crates/api_models/src/errors/serde.rs new file mode 100644 index 0000000000..4a3ba0fbb8 --- /dev/null +++ b/crates/api_models/src/errors/serde.rs @@ -0,0 +1,23 @@ +use serde::{ser::SerializeMap, Serialize}; + +use super::types::ApiErrorResponse; + +impl Serialize for ApiErrorResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(Some(3))?; + map.serialize_entry("error_type", self.error_type())?; + map.serialize_entry( + "error_code", + &format!( + "{}_{}", + self.get_internal_error().sub_code, + self.get_internal_error().error_identifier + ), + )?; + map.serialize_entry("error_message", self.get_internal_error().error_message)?; + map.end() + } +} diff --git a/crates/api_models/src/errors/types.rs b/crates/api_models/src/errors/types.rs new file mode 100644 index 0000000000..c317e7b565 --- /dev/null +++ b/crates/api_models/src/errors/types.rs @@ -0,0 +1,68 @@ +use reqwest::StatusCode; + +pub enum ErrorType { + InvalidRequestError, + RouterError, + ConnectorError, +} + +#[derive(Debug, serde::Serialize)] +pub struct ApiError { + pub sub_code: &'static str, + pub error_identifier: u8, + pub error_message: &'static str, +} + +#[derive(Debug)] +pub enum ApiErrorResponse { + Unauthorized(ApiError), + ForbiddenCommonResource(ApiError), + ForbiddenPrivateResource(ApiError), + Conflict(ApiError), + Gone(ApiError), + Unprocessable(ApiError), + InternalServerError(ApiError), + NotImplemented(ApiError), + ConnectorError(ApiError, StatusCode), +} + +impl ::core::fmt::Display for ApiErrorResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + r#"{{"error":{}}}"#, + serde_json::to_string(self.get_internal_error()) + .unwrap_or_else(|_| "API error response".to_string()) + ) + } +} + +impl ApiErrorResponse { + pub(crate) fn get_internal_error(&self) -> &ApiError { + match self { + Self::Unauthorized(i) + | Self::ForbiddenCommonResource(i) + | Self::ForbiddenPrivateResource(i) + | Self::Conflict(i) + | Self::Gone(i) + | Self::Unprocessable(i) + | Self::InternalServerError(i) + | Self::NotImplemented(i) + | Self::ConnectorError(i, _) => i, + } + } + + pub(crate) fn error_type(&self) -> &str { + match self { + Self::Unauthorized(_) + | Self::ForbiddenCommonResource(_) + | Self::ForbiddenPrivateResource(_) + | Self::Conflict(_) + | Self::Gone(_) + | Self::Unprocessable(_) + | Self::NotImplemented(_) => "invalid_request", + Self::InternalServerError(_) => "api", + Self::ConnectorError(_, _) => "connector", + } + } +} diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index e7f77552c3..18d607d715 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -6,6 +6,7 @@ pub mod cards; pub mod customers; pub mod disputes; pub mod enums; +pub mod errors; pub mod files; pub mod mandates; pub mod payment_methods; diff --git a/crates/common_utils/src/errors.rs b/crates/common_utils/src/errors.rs index 5ae75da347..e666fe4755 100644 --- a/crates/common_utils/src/errors.rs +++ b/crates/common_utils/src/errors.rs @@ -71,3 +71,35 @@ pub enum CryptoError { #[error("Failed to verify signature")] SignatureVerificationFailed, } + +/// Allows [error_stack::Report] to change between error contexts +/// using the dependent [ErrorSwitch] trait to define relations & mappings between traits +pub trait ReportSwitchExt { + /// Switch to the intended report by calling switch + /// requires error switch to be already implemented on the error type + fn switch(self) -> Result>; +} + +impl ReportSwitchExt for Result> +where + V: ErrorSwitch + error_stack::Context, + U: error_stack::Context, +{ + fn switch(self) -> Result> { + match self { + Ok(i) => Ok(i), + Err(er) => { + let new_c = er.current_context().switch(); + Err(er.change_context(new_c)) + } + } + } +} + +/// Allow [error_stack::Report] to convert between error types +/// This autoimplements [ReportSwitchExt] for the corresponding errors +pub trait ErrorSwitch { + /// Get the next error type that the source error can be escalated into + /// This does not consume the source error since we need to keep it in context + fn switch(&self) -> T; +}