use std::fmt::Display; use actix_web::ResponseError; use common_utils::errors::ErrorSwitch; use config::ConfigError; use data_models::errors::StorageError as DataStorageError; use http::StatusCode; pub use redis_interface::errors::RedisError; use router_env::opentelemetry::metrics::MetricsError; use crate::{errors as storage_errors, store::errors::DatabaseError}; pub type ApplicationResult = Result; macro_rules! impl_error_display { ($st: ident, $arg: tt) => { impl Display for $st { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( fmt, "{{ error_type: {:?}, error_description: {} }}", self, $arg ) } } }; } macro_rules! impl_error_type { ($name: ident, $arg: tt) => { #[derive(Debug)] pub struct $name; impl_error_display!($name, $arg); impl std::error::Error for $name {} }; } #[derive(Debug, thiserror::Error)] pub enum StorageError { #[error("DatabaseError: {0:?}")] DatabaseError(error_stack::Report), #[error("ValueNotFound: {0}")] ValueNotFound(String), #[error("DuplicateValue: {entity} already exists {key:?}")] DuplicateValue { entity: &'static str, key: Option, }, #[error("Timed out while trying to connect to the database")] DatabaseConnectionError, #[error("KV error")] KVError, #[error("Serialization failure")] SerializationFailed, #[error("MockDb error")] MockDbError, #[error("Customer with this id is Redacted")] CustomerRedacted, #[error("Deserialization failure")] DeserializationFailed, #[error("Error while encrypting data")] EncryptionError, #[error("Error while decrypting data from database")] DecryptionError, #[error("RedisError: {0:?}")] RedisError(error_stack::Report), } impl ErrorSwitch for StorageError { fn switch(&self) -> DataStorageError { self.into() } } #[allow(clippy::from_over_into)] impl Into for &StorageError { fn into(self) -> DataStorageError { match self { StorageError::DatabaseError(i) => match i.current_context() { storage_errors::DatabaseError::DatabaseConnectionError => { DataStorageError::DatabaseConnectionError } // TODO: Update this error type to encompass & propagate the missing type (instead of generic `db value not found`) storage_errors::DatabaseError::NotFound => { DataStorageError::ValueNotFound(String::from("db value not found")) } // TODO: Update this error type to encompass & propagate the duplicate type (instead of generic `db value not found`) storage_errors::DatabaseError::UniqueViolation => { DataStorageError::DuplicateValue { entity: "db entity", key: None, } } storage_errors::DatabaseError::NoFieldsToUpdate => { DataStorageError::DatabaseError("No fields to update".to_string()) } storage_errors::DatabaseError::QueryGenerationFailed => { DataStorageError::DatabaseError("Query generation failed".to_string()) } storage_errors::DatabaseError::Others => { DataStorageError::DatabaseError("Unknown database error".to_string()) } }, StorageError::ValueNotFound(i) => DataStorageError::ValueNotFound(i.clone()), StorageError::DuplicateValue { entity, key } => DataStorageError::DuplicateValue { entity, key: key.clone(), }, StorageError::DatabaseConnectionError => DataStorageError::DatabaseConnectionError, StorageError::KVError => DataStorageError::KVError, StorageError::SerializationFailed => DataStorageError::SerializationFailed, StorageError::MockDbError => DataStorageError::MockDbError, StorageError::CustomerRedacted => DataStorageError::CustomerRedacted, StorageError::DeserializationFailed => DataStorageError::DeserializationFailed, StorageError::EncryptionError => DataStorageError::EncryptionError, StorageError::DecryptionError => DataStorageError::DecryptionError, StorageError::RedisError(i) => match i.current_context() { // TODO: Update this error type to encompass & propagate the missing type (instead of generic `redis value not found`) RedisError::NotFound => { DataStorageError::ValueNotFound("redis value not found".to_string()) } RedisError::JsonSerializationFailed => DataStorageError::SerializationFailed, RedisError::JsonDeserializationFailed => DataStorageError::DeserializationFailed, i => DataStorageError::RedisError(format!("{:?}", i)), }, } } } impl From> for StorageError { fn from(err: error_stack::Report) -> Self { Self::RedisError(err) } } impl From> for StorageError { fn from(err: error_stack::Report) -> Self { Self::DatabaseError(err) } } impl StorageError { pub fn is_db_not_found(&self) -> bool { match self { Self::DatabaseError(err) => matches!(err.current_context(), DatabaseError::NotFound), _ => false, } } pub fn is_db_unique_violation(&self) -> bool { match self { Self::DatabaseError(err) => { matches!(err.current_context(), DatabaseError::UniqueViolation,) } _ => false, } } } impl_error_type!(EncryptionError, "Encryption error"); #[derive(Debug, thiserror::Error)] pub enum ApplicationError { // Display's impl can be overridden by the attribute error marco. // Don't use Debug here, Debug gives error stack in response. #[error("Application configuration error: {0}")] ConfigurationError(ConfigError), #[error("Invalid configuration value provided: {0}")] InvalidConfigurationValueError(String), #[error("Metrics error: {0}")] MetricsError(MetricsError), #[error("I/O: {0}")] IoError(std::io::Error), #[error("Error while constructing api client: {0}")] ApiClientError(ApiClientError), } impl From for ApplicationError { fn from(err: MetricsError) -> Self { Self::MetricsError(err) } } impl From for ApplicationError { fn from(err: std::io::Error) -> Self { Self::IoError(err) } } impl From for EncryptionError { fn from(_: ring::error::Unspecified) -> Self { Self } } impl From for ApplicationError { fn from(err: ConfigError) -> Self { Self::ConfigurationError(err) } } fn error_response(err: &T) -> actix_web::HttpResponse { actix_web::HttpResponse::BadRequest() .content_type(mime::APPLICATION_JSON) .body(format!(r#"{{ "error": {{ "message": "{err}" }} }}"#)) } impl ResponseError for ApplicationError { fn status_code(&self) -> StatusCode { match self { Self::MetricsError(_) | Self::IoError(_) | Self::ConfigurationError(_) | Self::InvalidConfigurationValueError(_) | Self::ApiClientError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } fn error_response(&self) -> actix_web::HttpResponse { error_response(self) } } #[derive(Debug, thiserror::Error, PartialEq, Clone)] pub enum ApiClientError { #[error("Header map construction failed")] HeaderMapConstructionFailed, #[error("Invalid proxy configuration")] InvalidProxyConfiguration, #[error("Client construction failed")] ClientConstructionFailed, #[error("Certificate decode failed")] CertificateDecodeFailed, #[error("Request body serialization failed")] BodySerializationFailed, #[error("Unexpected state reached/Invariants conflicted")] UnexpectedState, #[error("URL encoding of request payload failed")] UrlEncodingFailed, #[error("Failed to send request to connector {0}")] RequestNotSent(String), #[error("Failed to decode response")] ResponseDecodingFailed, #[error("Server responded with Request Timeout")] RequestTimeoutReceived, #[error("connection closed before a message could complete")] ConnectionClosed, #[error("Server responded with Internal Server Error")] InternalServerErrorReceived, #[error("Server responded with Bad Gateway")] BadGatewayReceived, #[error("Server responded with Service Unavailable")] ServiceUnavailableReceived, #[error("Server responded with Gateway Timeout")] GatewayTimeoutReceived, #[error("Server responded with unexpected response")] UnexpectedServerResponse, } impl ApiClientError { pub fn is_upstream_timeout(&self) -> bool { self == &Self::RequestTimeoutReceived } pub fn is_connection_closed(&self) -> bool { self == &Self::ConnectionClosed } } #[derive(Debug, thiserror::Error, PartialEq)] pub enum ConnectorError { #[error("Error while obtaining URL for the integration")] FailedToObtainIntegrationUrl, #[error("Failed to encode connector request")] RequestEncodingFailed, #[error("Request encoding failed : {0}")] RequestEncodingFailedWithReason(String), #[error("Parsing failed")] ParsingFailed, #[error("Failed to deserialize connector response")] ResponseDeserializationFailed, #[error("Failed to execute a processing step: {0:?}")] ProcessingStepFailed(Option), #[error("The connector returned an unexpected response: {0:?}")] UnexpectedResponseError(bytes::Bytes), #[error("Failed to parse custom routing rules from merchant account")] RoutingRulesParsingError, #[error("Failed to obtain preferred connector from merchant account")] FailedToObtainPreferredConnector, #[error("An invalid connector name was provided")] InvalidConnectorName, #[error("An invalid Wallet was used")] InvalidWallet, #[error("Failed to handle connector response")] ResponseHandlingFailed, #[error("Missing required field: {field_name}")] MissingRequiredField { field_name: &'static str }, #[error("Missing required fields: {field_names:?}")] MissingRequiredFields { field_names: Vec<&'static str> }, #[error("Failed to obtain authentication type")] FailedToObtainAuthType, #[error("Failed to obtain certificate")] FailedToObtainCertificate, #[error("Connector meta data not found")] NoConnectorMetaData, #[error("Failed to obtain certificate key")] FailedToObtainCertificateKey, #[error("This step has not been implemented for: {0}")] NotImplemented(String), #[error("{message} is not supported by {connector}")] NotSupported { message: String, connector: &'static str, payment_experience: String, }, #[error("{flow} flow not supported by {connector} connector")] FlowNotSupported { flow: String, connector: String }, #[error("Capture method not supported")] CaptureMethodNotSupported, #[error("Missing connector transaction ID")] MissingConnectorTransactionID, #[error("Missing connector refund ID")] MissingConnectorRefundID, #[error("Webhooks not implemented for this connector")] WebhooksNotImplemented, #[error("Failed to decode webhook event body")] WebhookBodyDecodingFailed, #[error("Signature not found for incoming webhook")] WebhookSignatureNotFound, #[error("Failed to verify webhook source")] WebhookSourceVerificationFailed, #[error("Could not find merchant secret in DB for incoming webhook source verification")] WebhookVerificationSecretNotFound, #[error("Incoming webhook object reference ID not found")] WebhookReferenceIdNotFound, #[error("Incoming webhook event type not found")] WebhookEventTypeNotFound, #[error("Incoming webhook event resource object not found")] WebhookResourceObjectNotFound, #[error("Could not respond to the incoming webhook event")] WebhookResponseEncodingFailed, #[error("Invalid Date/time format")] InvalidDateFormat, #[error("Date Formatting Failed")] DateFormattingFailed, #[error("Invalid Data format")] InvalidDataFormat { field_name: &'static str }, #[error("Payment Method data / Payment Method Type / Payment Experience Mismatch ")] MismatchedPaymentData, #[error("Failed to parse Wallet token")] InvalidWalletToken, #[error("Missing Connector Related Transaction ID")] MissingConnectorRelatedTransactionID { id: String }, #[error("File Validation failed")] FileValidationFailed { reason: String }, #[error("Missing 3DS redirection payload: {field_name}")] MissingConnectorRedirectionPayload { field_name: &'static str }, }