mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 11:06:50 +08:00
fix(router): handle JSON connector response parse error (#1892)
This commit is contained in:
@ -2,7 +2,7 @@ pub mod transformers;
|
|||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, Report, ResultExt};
|
||||||
use masking::PeekInterface;
|
use masking::PeekInterface;
|
||||||
|
|
||||||
use self::transformers as braintree;
|
use self::transformers as braintree;
|
||||||
@ -10,7 +10,7 @@ use crate::{
|
|||||||
configs::settings,
|
configs::settings,
|
||||||
consts,
|
consts,
|
||||||
core::errors::{self, CustomResult},
|
core::errors::{self, CustomResult},
|
||||||
headers,
|
headers, logger,
|
||||||
services::{
|
services::{
|
||||||
self,
|
self,
|
||||||
request::{self, Mask},
|
request::{self, Mask},
|
||||||
@ -46,6 +46,27 @@ impl ConnectorCommon for Braintree {
|
|||||||
auth.auth_header.into_masked(),
|
auth.auth_header.into_masked(),
|
||||||
)])
|
)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_error_response(
|
||||||
|
&self,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
let response: Result<braintree::ErrorResponse, Report<common_utils::errors::ParsingError>> =
|
||||||
|
res.response.parse_struct("Braintree Error Response");
|
||||||
|
|
||||||
|
match response {
|
||||||
|
Ok(response_data) => Ok(types::ErrorResponse {
|
||||||
|
status_code: res.status_code,
|
||||||
|
code: consts::NO_ERROR_CODE.to_string(),
|
||||||
|
message: response_data.api_error_response.message,
|
||||||
|
reason: None,
|
||||||
|
}),
|
||||||
|
Err(error_msg) => {
|
||||||
|
logger::error!(deserialization_error =? error_msg);
|
||||||
|
utils::handle_json_response_deserialization_failure(res, "braintree".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::Payment for Braintree {}
|
impl api::Payment for Braintree {}
|
||||||
@ -140,17 +161,7 @@ impl
|
|||||||
&self,
|
&self,
|
||||||
res: types::Response,
|
res: types::Response,
|
||||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
let response: braintree::ErrorResponse = res
|
self.build_error_response(res)
|
||||||
.response
|
|
||||||
.parse_struct("Error Response")
|
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
|
||||||
|
|
||||||
Ok(types::ErrorResponse {
|
|
||||||
status_code: res.status_code,
|
|
||||||
code: consts::NO_ERROR_CODE.to_string(),
|
|
||||||
message: response.api_error_response.message,
|
|
||||||
reason: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_request_body(
|
fn get_request_body(
|
||||||
@ -291,17 +302,7 @@ impl
|
|||||||
&self,
|
&self,
|
||||||
res: types::Response,
|
res: types::Response,
|
||||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
let response: braintree::ErrorResponse = res
|
self.build_error_response(res)
|
||||||
.response
|
|
||||||
.parse_struct("Braintree Error Response")
|
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
|
||||||
|
|
||||||
Ok(types::ErrorResponse {
|
|
||||||
status_code: res.status_code,
|
|
||||||
code: consts::NO_ERROR_CODE.to_string(),
|
|
||||||
message: response.api_error_response.message,
|
|
||||||
reason: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_request_body(
|
fn get_request_body(
|
||||||
@ -429,16 +430,7 @@ impl
|
|||||||
&self,
|
&self,
|
||||||
res: types::Response,
|
res: types::Response,
|
||||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
let response: braintree::ErrorResponse = res
|
self.build_error_response(res)
|
||||||
.response
|
|
||||||
.parse_struct("Braintree ErrorResponse")
|
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
|
||||||
Ok(types::ErrorResponse {
|
|
||||||
status_code: res.status_code,
|
|
||||||
code: consts::NO_ERROR_CODE.to_string(),
|
|
||||||
message: response.api_error_response.message,
|
|
||||||
reason: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,17 +503,7 @@ impl
|
|||||||
&self,
|
&self,
|
||||||
res: types::Response,
|
res: types::Response,
|
||||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
let response: braintree::ErrorResponse = res
|
self.build_error_response(res)
|
||||||
.response
|
|
||||||
.parse_struct("Braintree ErrorResponse")
|
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
|
||||||
|
|
||||||
Ok(types::ErrorResponse {
|
|
||||||
status_code: res.status_code,
|
|
||||||
code: consts::NO_ERROR_CODE.to_string(),
|
|
||||||
message: response.api_error_response.message,
|
|
||||||
reason: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_request_body(
|
fn get_request_body(
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use std::fmt::Debug;
|
|||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use common_utils::{date_time, ext_traits::StringExt};
|
use common_utils::{date_time, ext_traits::StringExt};
|
||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, Report, ResultExt};
|
||||||
use masking::{ExposeInterface, PeekInterface};
|
use masking::{ExposeInterface, PeekInterface};
|
||||||
use rand::distributions::{Alphanumeric, DistString};
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
use ring::hmac;
|
use ring::hmac;
|
||||||
@ -15,7 +15,7 @@ use crate::{
|
|||||||
consts,
|
consts,
|
||||||
core::errors::{self, CustomResult},
|
core::errors::{self, CustomResult},
|
||||||
db::StorageInterface,
|
db::StorageInterface,
|
||||||
headers,
|
headers, logger,
|
||||||
services::{
|
services::{
|
||||||
self,
|
self,
|
||||||
request::{self, Mask},
|
request::{self, Mask},
|
||||||
@ -82,16 +82,23 @@ impl ConnectorCommon for Rapyd {
|
|||||||
&self,
|
&self,
|
||||||
res: types::Response,
|
res: types::Response,
|
||||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||||
let response: rapyd::RapydPaymentsResponse = res
|
let response: Result<
|
||||||
.response
|
rapyd::RapydPaymentsResponse,
|
||||||
.parse_struct("Rapyd ErrorResponse")
|
Report<common_utils::errors::ParsingError>,
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
> = res.response.parse_struct("Rapyd ErrorResponse");
|
||||||
Ok(ErrorResponse {
|
|
||||||
status_code: res.status_code,
|
match response {
|
||||||
code: response.status.error_code,
|
Ok(response_data) => Ok(ErrorResponse {
|
||||||
message: response.status.status.unwrap_or_default(),
|
status_code: res.status_code,
|
||||||
reason: response.status.message,
|
code: response_data.status.error_code,
|
||||||
})
|
message: response_data.status.status.unwrap_or_default(),
|
||||||
|
reason: response_data.status.message,
|
||||||
|
}),
|
||||||
|
Err(error_msg) => {
|
||||||
|
logger::error!(deserialization_error =? error_msg);
|
||||||
|
utils::handle_json_response_deserialization_failure(res, "rapyd".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use std::fmt::Debug;
|
|||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use common_utils::{crypto, errors::ReportSwitchExt, ext_traits::ByteSliceExt};
|
use common_utils::{crypto, errors::ReportSwitchExt, ext_traits::ByteSliceExt};
|
||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, Report, ResultExt};
|
||||||
use masking::PeekInterface;
|
use masking::PeekInterface;
|
||||||
use transformers as trustpay;
|
use transformers as trustpay;
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ use crate::{
|
|||||||
errors::{self, CustomResult},
|
errors::{self, CustomResult},
|
||||||
payments,
|
payments,
|
||||||
},
|
},
|
||||||
headers,
|
headers, logger,
|
||||||
services::{
|
services::{
|
||||||
self,
|
self,
|
||||||
request::{self, Mask},
|
request::{self, Mask},
|
||||||
@ -104,34 +104,43 @@ impl ConnectorCommon for Trustpay {
|
|||||||
&self,
|
&self,
|
||||||
res: Response,
|
res: Response,
|
||||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||||
let response: trustpay::TrustpayErrorResponse = res
|
let response: Result<
|
||||||
.response
|
trustpay::TrustpayErrorResponse,
|
||||||
.parse_struct("trustpay ErrorResponse")
|
Report<common_utils::errors::ParsingError>,
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
> = res.response.parse_struct("trustpay ErrorResponse");
|
||||||
let error_list = response.errors.clone().unwrap_or(vec![]);
|
|
||||||
let option_error_code_message = get_error_code_error_message_based_on_priority(
|
match response {
|
||||||
self.clone(),
|
Ok(response_data) => {
|
||||||
error_list.into_iter().map(|errors| errors.into()).collect(),
|
let error_list = response_data.errors.clone().unwrap_or(vec![]);
|
||||||
);
|
let option_error_code_message = get_error_code_error_message_based_on_priority(
|
||||||
let reason = response.errors.map(|errors| {
|
self.clone(),
|
||||||
errors
|
error_list.into_iter().map(|errors| errors.into()).collect(),
|
||||||
.iter()
|
);
|
||||||
.map(|error| error.description.clone())
|
let reason = response_data.errors.map(|errors| {
|
||||||
.collect::<Vec<String>>()
|
errors
|
||||||
.join(" & ")
|
.iter()
|
||||||
});
|
.map(|error| error.description.clone())
|
||||||
Ok(ErrorResponse {
|
.collect::<Vec<String>>()
|
||||||
status_code: res.status_code,
|
.join(" & ")
|
||||||
code: option_error_code_message
|
});
|
||||||
.clone()
|
Ok(ErrorResponse {
|
||||||
.map(|error_code_message| error_code_message.error_code)
|
status_code: res.status_code,
|
||||||
.unwrap_or(consts::NO_ERROR_CODE.to_string()),
|
code: option_error_code_message
|
||||||
// message vary for the same code, so relying on code alone as it is unique
|
.clone()
|
||||||
message: option_error_code_message
|
.map(|error_code_message| error_code_message.error_code)
|
||||||
.map(|error_code_message| error_code_message.error_code)
|
.unwrap_or(consts::NO_ERROR_CODE.to_string()),
|
||||||
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
|
// message vary for the same code, so relying on code alone as it is unique
|
||||||
reason: reason.or(response.description),
|
message: option_error_code_message
|
||||||
})
|
.map(|error_code_message| error_code_message.error_code)
|
||||||
|
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
|
||||||
|
reason: reason.or(response_data.description),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(error_msg) => {
|
||||||
|
logger::error!(deserialization_error =? error_msg);
|
||||||
|
utils::handle_json_response_deserialization_failure(res, "trustpay".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,13 +17,16 @@ use image::Luma;
|
|||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use qrcode;
|
use qrcode;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde_json::Value;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub use self::ext_traits::{OptionExt, ValidateCall};
|
pub use self::ext_traits::{OptionExt, ValidateCall};
|
||||||
use crate::{
|
use crate::{
|
||||||
consts,
|
consts,
|
||||||
core::errors::{self, RouterResult},
|
core::errors::{self, CustomResult, RouterResult},
|
||||||
logger, types,
|
logger,
|
||||||
|
routes::metrics,
|
||||||
|
types,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod error_parser {
|
pub mod error_parser {
|
||||||
@ -176,3 +179,36 @@ mod tests {
|
|||||||
assert!(qr_image_data_source_url.is_ok());
|
assert!(qr_image_data_source_url.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate json format for the error
|
||||||
|
pub fn handle_json_response_deserialization_failure(
|
||||||
|
res: types::Response,
|
||||||
|
connector: String,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
metrics::RESPONSE_DESERIALIZATION_FAILURE.add(
|
||||||
|
&metrics::CONTEXT,
|
||||||
|
1,
|
||||||
|
&[metrics::request::add_attributes("connector", connector)],
|
||||||
|
);
|
||||||
|
|
||||||
|
let response_data = String::from_utf8(res.response.to_vec())
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
|
||||||
|
// check for whether the response is in json format
|
||||||
|
match serde_json::from_str::<Value>(&response_data) {
|
||||||
|
// in case of unexpected response but in json format
|
||||||
|
Ok(_) => Err(errors::ConnectorError::ResponseDeserializationFailed)?,
|
||||||
|
// in case of unexpected response but in html or string format
|
||||||
|
Err(error_msg) => {
|
||||||
|
logger::error!(deserialization_error=?error_msg);
|
||||||
|
logger::error!("UNEXPECTED RESPONSE FROM CONNECTOR: {}", response_data);
|
||||||
|
Ok(types::ErrorResponse {
|
||||||
|
status_code: res.status_code,
|
||||||
|
code: consts::NO_ERROR_CODE.to_string(),
|
||||||
|
message: consts::UNSUPPORTED_ERROR_MESSAGE.to_string(),
|
||||||
|
reason: Some(response_data),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user