use alloc::string::{String, ToString};
use core::fmt;
use super::{impl_from_err, BT};
use crate::errors::{RecoverPubkeyError, VerificationError};
#[derive(Debug, thiserror::Error)]
pub enum StdError {
#[error("Verification error: {source}")]
VerificationErr {
source: VerificationError,
backtrace: BT,
},
#[error("Recover pubkey error: {source}")]
RecoverPubkeyErr {
source: RecoverPubkeyError,
backtrace: BT,
},
#[error("Generic error: {msg}")]
GenericErr { msg: String, backtrace: BT },
#[error("Invalid Base64 string: {msg}")]
InvalidBase64 { msg: String, backtrace: BT },
#[error("Invalid data size: expected={expected} actual={actual}")]
InvalidDataSize {
expected: u64,
actual: u64,
backtrace: BT,
},
#[error("Invalid hex string: {msg}")]
InvalidHex { msg: String, backtrace: BT },
#[error("Cannot decode UTF8 bytes into string: {msg}")]
InvalidUtf8 { msg: String, backtrace: BT },
#[error("{kind} not found")]
NotFound { kind: String, backtrace: BT },
#[error("Error parsing into type {target_type}: {msg}")]
ParseErr {
target_type: String,
msg: String,
backtrace: BT,
},
#[error("Error serializing type {source_type}: {msg}")]
SerializeErr {
source_type: String,
msg: String,
backtrace: BT,
},
#[error("Overflow: {source}")]
Overflow {
source: OverflowError,
backtrace: BT,
},
#[error("Divide by zero: {source}")]
DivideByZero {
source: DivideByZeroError,
backtrace: BT,
},
#[error("Conversion error: ")]
ConversionOverflow {
source: ConversionOverflowError,
backtrace: BT,
},
}
impl_from_err!(
ConversionOverflowError,
StdError,
StdError::ConversionOverflow
);
impl StdError {
pub fn verification_err(source: VerificationError) -> Self {
StdError::VerificationErr {
source,
backtrace: BT::capture(),
}
}
pub fn recover_pubkey_err(source: RecoverPubkeyError) -> Self {
StdError::RecoverPubkeyErr {
source,
backtrace: BT::capture(),
}
}
pub fn generic_err(msg: impl Into<String>) -> Self {
StdError::GenericErr {
msg: msg.into(),
backtrace: BT::capture(),
}
}
pub fn invalid_base64(msg: impl ToString) -> Self {
StdError::InvalidBase64 {
msg: msg.to_string(),
backtrace: BT::capture(),
}
}
pub fn invalid_data_size(expected: usize, actual: usize) -> Self {
StdError::InvalidDataSize {
expected: expected as u64,
actual: actual as u64,
backtrace: BT::capture(),
}
}
pub fn invalid_hex(msg: impl ToString) -> Self {
StdError::InvalidHex {
msg: msg.to_string(),
backtrace: BT::capture(),
}
}
pub fn invalid_utf8(msg: impl ToString) -> Self {
StdError::InvalidUtf8 {
msg: msg.to_string(),
backtrace: BT::capture(),
}
}
pub fn not_found(kind: impl Into<String>) -> Self {
StdError::NotFound {
kind: kind.into(),
backtrace: BT::capture(),
}
}
pub fn parse_err(target: impl Into<String>, msg: impl ToString) -> Self {
StdError::ParseErr {
target_type: target.into(),
msg: msg.to_string(),
backtrace: BT::capture(),
}
}
pub fn serialize_err(source: impl Into<String>, msg: impl ToString) -> Self {
StdError::SerializeErr {
source_type: source.into(),
msg: msg.to_string(),
backtrace: BT::capture(),
}
}
pub fn overflow(source: OverflowError) -> Self {
StdError::Overflow {
source,
backtrace: BT::capture(),
}
}
pub fn divide_by_zero(source: DivideByZeroError) -> Self {
StdError::DivideByZero {
source,
backtrace: BT::capture(),
}
}
}
impl PartialEq<StdError> for StdError {
fn eq(&self, rhs: &StdError) -> bool {
match self {
StdError::VerificationErr {
source,
backtrace: _,
} => {
if let StdError::VerificationErr {
source: rhs_source,
backtrace: _,
} = rhs
{
source == rhs_source
} else {
false
}
}
StdError::RecoverPubkeyErr {
source,
backtrace: _,
} => {
if let StdError::RecoverPubkeyErr {
source: rhs_source,
backtrace: _,
} = rhs
{
source == rhs_source
} else {
false
}
}
StdError::GenericErr { msg, backtrace: _ } => {
if let StdError::GenericErr {
msg: rhs_msg,
backtrace: _,
} = rhs
{
msg == rhs_msg
} else {
false
}
}
StdError::InvalidBase64 { msg, backtrace: _ } => {
if let StdError::InvalidBase64 {
msg: rhs_msg,
backtrace: _,
} = rhs
{
msg == rhs_msg
} else {
false
}
}
StdError::InvalidDataSize {
expected,
actual,
backtrace: _,
} => {
if let StdError::InvalidDataSize {
expected: rhs_expected,
actual: rhs_actual,
backtrace: _,
} = rhs
{
expected == rhs_expected && actual == rhs_actual
} else {
false
}
}
StdError::InvalidHex { msg, backtrace: _ } => {
if let StdError::InvalidHex {
msg: rhs_msg,
backtrace: _,
} = rhs
{
msg == rhs_msg
} else {
false
}
}
StdError::InvalidUtf8 { msg, backtrace: _ } => {
if let StdError::InvalidUtf8 {
msg: rhs_msg,
backtrace: _,
} = rhs
{
msg == rhs_msg
} else {
false
}
}
StdError::NotFound { kind, backtrace: _ } => {
if let StdError::NotFound {
kind: rhs_kind,
backtrace: _,
} = rhs
{
kind == rhs_kind
} else {
false
}
}
StdError::ParseErr {
target_type,
msg,
backtrace: _,
} => {
if let StdError::ParseErr {
target_type: rhs_target_type,
msg: rhs_msg,
backtrace: _,
} = rhs
{
target_type == rhs_target_type && msg == rhs_msg
} else {
false
}
}
StdError::SerializeErr {
source_type,
msg,
backtrace: _,
} => {
if let StdError::SerializeErr {
source_type: rhs_source_type,
msg: rhs_msg,
backtrace: _,
} = rhs
{
source_type == rhs_source_type && msg == rhs_msg
} else {
false
}
}
StdError::Overflow {
source,
backtrace: _,
} => {
if let StdError::Overflow {
source: rhs_source,
backtrace: _,
} = rhs
{
source == rhs_source
} else {
false
}
}
StdError::DivideByZero {
source,
backtrace: _,
} => {
if let StdError::DivideByZero {
source: rhs_source,
backtrace: _,
} = rhs
{
source == rhs_source
} else {
false
}
}
StdError::ConversionOverflow {
source,
backtrace: _,
} => {
if let StdError::ConversionOverflow {
source: rhs_source,
backtrace: _,
} = rhs
{
source == rhs_source
} else {
false
}
}
}
}
}
impl From<core::str::Utf8Error> for StdError {
fn from(source: core::str::Utf8Error) -> Self {
Self::invalid_utf8(source)
}
}
impl From<alloc::string::FromUtf8Error> for StdError {
fn from(source: alloc::string::FromUtf8Error) -> Self {
Self::invalid_utf8(source)
}
}
impl From<VerificationError> for StdError {
fn from(source: VerificationError) -> Self {
Self::verification_err(source)
}
}
impl From<RecoverPubkeyError> for StdError {
fn from(source: RecoverPubkeyError) -> Self {
Self::recover_pubkey_err(source)
}
}
impl From<OverflowError> for StdError {
fn from(source: OverflowError) -> Self {
Self::overflow(source)
}
}
impl From<DivideByZeroError> for StdError {
fn from(source: DivideByZeroError) -> Self {
Self::divide_by_zero(source)
}
}
pub type StdResult<T> = core::result::Result<T, StdError>;
#[derive(Debug, PartialEq, Eq)]
pub enum OverflowOperation {
Add,
Sub,
Mul,
Pow,
Shr,
Shl,
}
impl fmt::Display for OverflowOperation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("Cannot {operation} with given operands")]
pub struct OverflowError {
pub operation: OverflowOperation,
}
impl OverflowError {
pub fn new(operation: OverflowOperation) -> Self {
Self { operation }
}
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("Error converting {source_type} to {target_type}")]
pub struct ConversionOverflowError {
pub source_type: &'static str,
pub target_type: &'static str,
}
impl ConversionOverflowError {
pub fn new(source_type: &'static str, target_type: &'static str) -> Self {
Self {
source_type,
target_type,
}
}
}
#[derive(Debug, Default, PartialEq, Eq, thiserror::Error)]
#[error("Cannot divide by zero")]
pub struct DivideByZeroError;
impl DivideByZeroError {
pub fn new() -> Self {
Self
}
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum DivisionError {
#[error("Divide by zero")]
DivideByZero,
#[error("Overflow in division")]
Overflow,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum CheckedMultiplyFractionError {
#[error("{_0}")]
DivideByZero(#[from] DivideByZeroError),
#[error("{_0}")]
ConversionOverflow(#[from] ConversionOverflowError),
#[error("{_0}")]
Overflow(#[from] OverflowError),
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum CheckedMultiplyRatioError {
#[error("Denominator must not be zero")]
DivideByZero,
#[error("Multiplication overflow")]
Overflow,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum CheckedFromRatioError {
#[error("Denominator must not be zero")]
DivideByZero,
#[error("Overflow")]
Overflow,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("Round up operation failed because of overflow")]
pub struct RoundUpOverflowError;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("Round down operation failed because of overflow")]
pub struct RoundDownOverflowError;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum CoinsError {
#[error("Duplicate denom")]
DuplicateDenom,
}
impl From<CoinsError> for StdError {
fn from(value: CoinsError) -> Self {
Self::generic_err(format!("Creating Coins: {value}"))
}
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum CoinFromStrError {
#[error("Missing denominator")]
MissingDenom,
#[error("Missing amount or non-digit characters in amount")]
MissingAmount,
#[error("Invalid amount: {_0}")]
InvalidAmount(core::num::ParseIntError),
}
impl From<core::num::ParseIntError> for CoinFromStrError {
fn from(value: core::num::ParseIntError) -> Self {
Self::InvalidAmount(value)
}
}
impl From<CoinFromStrError> for StdError {
fn from(value: CoinFromStrError) -> Self {
Self::generic_err(format!("Parsing Coin: {value}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::str;
#[test]
fn generic_err_owned() {
let guess = 7;
let error = StdError::generic_err(format!("{guess} is too low"));
match error {
StdError::GenericErr { msg, .. } => {
assert_eq!(msg, String::from("7 is too low"));
}
e => panic!("unexpected error, {e:?}"),
}
}
#[test]
fn generic_err_ref() {
let error = StdError::generic_err("not implemented");
match error {
StdError::GenericErr { msg, .. } => assert_eq!(msg, "not implemented"),
e => panic!("unexpected error, {e:?}"),
}
}
#[test]
fn invalid_base64_works_for_strings() {
let error = StdError::invalid_base64("my text");
match error {
StdError::InvalidBase64 { msg, .. } => {
assert_eq!(msg, "my text");
}
_ => panic!("expect different error"),
}
}
#[test]
fn invalid_base64_works_for_errors() {
let original = base64::DecodeError::InvalidLength(10);
let error = StdError::invalid_base64(original);
match error {
StdError::InvalidBase64 { msg, .. } => {
assert_eq!(msg, "Invalid input length: 10");
}
_ => panic!("expect different error"),
}
}
#[test]
fn invalid_data_size_works() {
let error = StdError::invalid_data_size(31, 14);
match error {
StdError::InvalidDataSize {
expected, actual, ..
} => {
assert_eq!(expected, 31);
assert_eq!(actual, 14);
}
_ => panic!("expect different error"),
}
}
#[test]
fn invalid_hex_works_for_strings() {
let error = StdError::invalid_hex("my text");
match error {
StdError::InvalidHex { msg, .. } => {
assert_eq!(msg, "my text");
}
_ => panic!("expect different error"),
}
}
#[test]
fn invalid_hex_works_for_errors() {
let original = hex::FromHexError::OddLength;
let error = StdError::invalid_hex(original);
match error {
StdError::InvalidHex { msg, .. } => {
assert_eq!(msg, "Odd number of digits");
}
_ => panic!("expect different error"),
}
}
#[test]
fn invalid_utf8_works_for_strings() {
let error = StdError::invalid_utf8("my text");
match error {
StdError::InvalidUtf8 { msg, .. } => {
assert_eq!(msg, "my text");
}
_ => panic!("expect different error"),
}
}
#[test]
fn invalid_utf8_works_for_errors() {
let original = String::from_utf8(vec![0x80]).unwrap_err();
let error = StdError::invalid_utf8(original);
match error {
StdError::InvalidUtf8 { msg, .. } => {
assert_eq!(msg, "invalid utf-8 sequence of 1 bytes from index 0");
}
_ => panic!("expect different error"),
}
}
#[test]
fn not_found_works() {
let error = StdError::not_found("gold");
match error {
StdError::NotFound { kind, .. } => assert_eq!(kind, "gold"),
_ => panic!("expect different error"),
}
}
#[test]
fn parse_err_works() {
let error = StdError::parse_err("Book", "Missing field: title");
match error {
StdError::ParseErr {
target_type, msg, ..
} => {
assert_eq!(target_type, "Book");
assert_eq!(msg, "Missing field: title");
}
_ => panic!("expect different error"),
}
}
#[test]
fn serialize_err_works() {
let error = StdError::serialize_err("Book", "Content too long");
match error {
StdError::SerializeErr {
source_type, msg, ..
} => {
assert_eq!(source_type, "Book");
assert_eq!(msg, "Content too long");
}
_ => panic!("expect different error"),
}
}
#[test]
fn underflow_works_for_u128() {
let error = StdError::overflow(OverflowError::new(OverflowOperation::Sub));
assert!(matches!(
error,
StdError::Overflow {
source: OverflowError {
operation: OverflowOperation::Sub
},
..
}
));
}
#[test]
fn overflow_works_for_i64() {
let error = StdError::overflow(OverflowError::new(OverflowOperation::Sub));
assert!(matches!(
error,
StdError::Overflow {
source: OverflowError {
operation: OverflowOperation::Sub
},
..
}
));
}
#[test]
fn divide_by_zero_works() {
let error = StdError::divide_by_zero(DivideByZeroError);
assert!(matches!(
error,
StdError::DivideByZero {
source: DivideByZeroError,
..
}
));
}
#[test]
fn implements_debug() {
let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub));
let embedded = format!("Debug: {error:?}");
assert!(embedded
.starts_with("Debug: Overflow { source: OverflowError { operation: Sub }, backtrace:"));
}
#[test]
fn implements_display() {
let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub));
let embedded = format!("Display: {error}");
assert_eq!(
embedded,
"Display: Overflow: Cannot Sub with given operands"
);
}
#[test]
fn implements_partial_eq() {
let u1 = StdError::from(OverflowError::new(OverflowOperation::Sub));
let u2 = StdError::from(OverflowError::new(OverflowOperation::Sub));
let s1 = StdError::serialize_err("Book", "Content too long");
let s2 = StdError::serialize_err("Book", "Content too long");
let s3 = StdError::serialize_err("Book", "Title too long");
assert_eq!(u1, u2);
assert_ne!(u1, s1);
assert_eq!(s1, s2);
assert_ne!(s1, s3);
}
#[test]
fn from_std_str_utf8error_works() {
let broken = Vec::from(b"Hello \xF0\x90\x80World" as &[u8]);
let error: StdError = str::from_utf8(&broken).unwrap_err().into();
match error {
StdError::InvalidUtf8 { msg, .. } => {
assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6")
}
err => panic!("Unexpected error: {err:?}"),
}
}
#[test]
fn from_std_string_from_utf8error_works() {
let error: StdError = String::from_utf8(b"Hello \xF0\x90\x80World".to_vec())
.unwrap_err()
.into();
match error {
StdError::InvalidUtf8 { msg, .. } => {
assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6")
}
err => panic!("Unexpected error: {err:?}"),
}
}
}