use thiserror::Error;
use crate::{cancellation::Cancelled, config::CowEnv, redaction::Redacted};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ValidationError {
#[error("{field} must not be empty")]
EmptyField {
field: &'static str,
},
#[error("{field} must be a valid HTTP header value")]
InvalidHttpHeaderValue {
field: &'static str,
},
#[error("{field} must be 0x-prefixed hexadecimal data")]
InvalidHexPrefix {
field: &'static str,
},
#[error("{field} must contain exactly {expected} hex characters")]
InvalidHexLength {
field: &'static str,
expected: usize,
},
#[error("{field} contains non-hex characters")]
InvalidHexCharacters {
field: &'static str,
},
#[error("{field} must be a non-negative integer quantity")]
InvalidNumeric {
field: &'static str,
},
#[error("{field} exceeds uint256 bounds")]
NumericOverflow {
field: &'static str,
},
#[error("unsupported chain id {chain_id}")]
UnsupportedChain {
chain_id: u64,
},
#[error("valid_to {actual_seconds} exceeds the protocol u32 epoch ceiling")]
ValidToOutOfRange {
actual_seconds: u64,
},
#[error("decimals scale {actual} exceeds the maximum representable value {max}")]
DecimalsOutOfRange {
actual: u8,
max: u8,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum CoreError {
#[error("validation error: {0}")]
Validation(#[from] ValidationError),
#[error(
"missing API base URL for chain id {chain_id} in {env} environment (partner_api={partner_api})"
)]
MissingBaseUrl {
chain_id: u64,
env: CowEnv,
partner_api: bool,
},
#[error("serialization error: {0}")]
Serialization(Redacted<String>),
#[error("transport contract violation: {0}")]
TransportContract(Redacted<String>),
#[error("operation was cancelled")]
Cancelled,
}
impl From<Cancelled> for CoreError {
fn from(_: Cancelled) -> Self {
Self::Cancelled
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorClass {
Validation,
Transport,
Remote,
RateLimited,
Signing,
Cancelled,
Internal,
}
impl ErrorClass {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Validation => "validation",
Self::Transport => "transport",
Self::Remote => "remote",
Self::RateLimited => "rate-limited",
Self::Signing => "signing",
Self::Cancelled => "cancelled",
Self::Internal => "internal",
}
}
}
impl core::fmt::Display for ErrorClass {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[must_use]
pub fn serialization_error_category(error: &serde_json::Error) -> &'static str {
match error.classify() {
serde_json::error::Category::Io => "io",
serde_json::error::Category::Syntax => "syntax",
serde_json::error::Category::Data => "data",
serde_json::error::Category::Eof => "eof",
}
}
impl CoreError {
#[must_use]
pub const fn class(&self) -> ErrorClass {
match self {
Self::Validation(_) | Self::MissingBaseUrl { .. } => ErrorClass::Validation,
Self::Cancelled => ErrorClass::Cancelled,
_ => ErrorClass::Internal,
}
}
}
#[cfg(test)]
mod tests {
use super::ErrorClass;
#[test]
fn error_class_labels_are_stable_and_display_matches_as_str() {
for (class, label) in [
(ErrorClass::Validation, "validation"),
(ErrorClass::Transport, "transport"),
(ErrorClass::Remote, "remote"),
(ErrorClass::RateLimited, "rate-limited"),
(ErrorClass::Signing, "signing"),
(ErrorClass::Cancelled, "cancelled"),
(ErrorClass::Internal, "internal"),
] {
assert_eq!(class.as_str(), label);
assert_eq!(class.to_string(), label);
}
}
}