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 error_stack::{IntoReport, ResultExt};
|
||||
use error_stack::{IntoReport, Report, ResultExt};
|
||||
use masking::PeekInterface;
|
||||
|
||||
use self::transformers as braintree;
|
||||
@ -10,7 +10,7 @@ use crate::{
|
||||
configs::settings,
|
||||
consts,
|
||||
core::errors::{self, CustomResult},
|
||||
headers,
|
||||
headers, logger,
|
||||
services::{
|
||||
self,
|
||||
request::{self, Mask},
|
||||
@ -46,6 +46,27 @@ impl ConnectorCommon for Braintree {
|
||||
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 {}
|
||||
@ -140,17 +161,7 @@ impl
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
let response: braintree::ErrorResponse = 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,
|
||||
})
|
||||
self.build_error_response(res)
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -291,17 +302,7 @@ impl
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
let response: braintree::ErrorResponse = 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,
|
||||
})
|
||||
self.build_error_response(res)
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -429,16 +430,7 @@ impl
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
let response: braintree::ErrorResponse = 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,
|
||||
})
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
@ -511,17 +503,7 @@ impl
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
let response: braintree::ErrorResponse = 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,
|
||||
})
|
||||
self.build_error_response(res)
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
|
||||
@ -3,7 +3,7 @@ use std::fmt::Debug;
|
||||
|
||||
use base64::Engine;
|
||||
use common_utils::{date_time, ext_traits::StringExt};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use error_stack::{IntoReport, Report, ResultExt};
|
||||
use masking::{ExposeInterface, PeekInterface};
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use ring::hmac;
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
consts,
|
||||
core::errors::{self, CustomResult},
|
||||
db::StorageInterface,
|
||||
headers,
|
||||
headers, logger,
|
||||
services::{
|
||||
self,
|
||||
request::{self, Mask},
|
||||
@ -82,16 +82,23 @@ impl ConnectorCommon for Rapyd {
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: rapyd::RapydPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Rapyd ErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(ErrorResponse {
|
||||
let response: Result<
|
||||
rapyd::RapydPaymentsResponse,
|
||||
Report<common_utils::errors::ParsingError>,
|
||||
> = res.response.parse_struct("Rapyd ErrorResponse");
|
||||
|
||||
match response {
|
||||
Ok(response_data) => Ok(ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: response.status.error_code,
|
||||
message: response.status.status.unwrap_or_default(),
|
||||
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 common_utils::{crypto, errors::ReportSwitchExt, ext_traits::ByteSliceExt};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use error_stack::{IntoReport, Report, ResultExt};
|
||||
use masking::PeekInterface;
|
||||
use transformers as trustpay;
|
||||
|
||||
@ -19,7 +19,7 @@ use crate::{
|
||||
errors::{self, CustomResult},
|
||||
payments,
|
||||
},
|
||||
headers,
|
||||
headers, logger,
|
||||
services::{
|
||||
self,
|
||||
request::{self, Mask},
|
||||
@ -104,16 +104,19 @@ impl ConnectorCommon for Trustpay {
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: trustpay::TrustpayErrorResponse = res
|
||||
.response
|
||||
.parse_struct("trustpay ErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
let error_list = response.errors.clone().unwrap_or(vec![]);
|
||||
let response: Result<
|
||||
trustpay::TrustpayErrorResponse,
|
||||
Report<common_utils::errors::ParsingError>,
|
||||
> = res.response.parse_struct("trustpay ErrorResponse");
|
||||
|
||||
match response {
|
||||
Ok(response_data) => {
|
||||
let error_list = response_data.errors.clone().unwrap_or(vec![]);
|
||||
let option_error_code_message = get_error_code_error_message_based_on_priority(
|
||||
self.clone(),
|
||||
error_list.into_iter().map(|errors| errors.into()).collect(),
|
||||
);
|
||||
let reason = response.errors.map(|errors| {
|
||||
let reason = response_data.errors.map(|errors| {
|
||||
errors
|
||||
.iter()
|
||||
.map(|error| error.description.clone())
|
||||
@ -130,9 +133,15 @@ impl ConnectorCommon for Trustpay {
|
||||
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.description),
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl api::Payment for Trustpay {}
|
||||
|
||||
@ -17,13 +17,16 @@ use image::Luma;
|
||||
use nanoid::nanoid;
|
||||
use qrcode;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use self::ext_traits::{OptionExt, ValidateCall};
|
||||
use crate::{
|
||||
consts,
|
||||
core::errors::{self, RouterResult},
|
||||
logger, types,
|
||||
core::errors::{self, CustomResult, RouterResult},
|
||||
logger,
|
||||
routes::metrics,
|
||||
types,
|
||||
};
|
||||
|
||||
pub mod error_parser {
|
||||
@ -176,3 +179,36 @@ mod tests {
|
||||
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