fix(router): handle JSON connector response parse error (#1892)

This commit is contained in:
AkshayaFoiger
2023-08-09 12:17:59 +05:30
committed by GitHub
parent f8ef52c645
commit 393c2ab94c
4 changed files with 123 additions and 89 deletions

View File

@ -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(

View File

@ -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 {
match response {
Ok(response_data) => Ok(ErrorResponse {
status_code: res.status_code, status_code: res.status_code,
code: response.status.error_code, code: response_data.status.error_code,
message: response.status.status.unwrap_or_default(), message: response_data.status.status.unwrap_or_default(),
reason: response.status.message, reason: response_data.status.message,
}) }),
Err(error_msg) => {
logger::error!(deserialization_error =? error_msg);
utils::handle_json_response_deserialization_failure(res, "rapyd".to_owned())
}
}
} }
} }

View File

@ -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,16 +104,19 @@ 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![]);
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( let option_error_code_message = get_error_code_error_message_based_on_priority(
self.clone(), self.clone(),
error_list.into_iter().map(|errors| errors.into()).collect(), error_list.into_iter().map(|errors| errors.into()).collect(),
); );
let reason = response.errors.map(|errors| { let reason = response_data.errors.map(|errors| {
errors errors
.iter() .iter()
.map(|error| error.description.clone()) .map(|error| error.description.clone())
@ -130,9 +133,15 @@ impl ConnectorCommon for Trustpay {
message: option_error_code_message 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_MESSAGE.to_string()), .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 {} impl api::Payment for Trustpay {}

View File

@ -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),
})
}
}
}