Skip to content

Commit

Permalink
Improve error handling + consistency when no_std (#13)
Browse files Browse the repository at this point in the history
* Use derive_more and Debug+Display for no_std errors

* Add Error::custom_string, too

* fix test
  • Loading branch information
jsdw authored Jul 31, 2023
1 parent 797b4ad commit 2697f92
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 64 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ The format is based on [Keep a Changelog].

[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/

## [v0.5.0] - 2023-07-28

- Improve custom error handling: custom errors now require `Debug + Display` on `no_std` or `Error` on `std`.
`Error::custom()` now accepts anything implementing these traits rather than depending on `Into<Error>`.

## [v0.4.0] - 2023-07-11

- Add support for `no_std` (+alloc) builds ([#11](https://github.com/paritytech/scale-encode/pull/11)). Thankyou @haerdib!
Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ members = [
]

[workspace.package]
version = "0.4.0"
version = "0.5.0"
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
license = "Apache-2.0"
Expand All @@ -16,5 +16,5 @@ keywords = ["parity", "scale", "encoding"]
include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]

[workspace.dependencies]
scale-encode = { version = "0.4.0", path = "scale-encode" }
scale-encode-derive = { version = "0.4.0", path = "scale-encode-derive" }
scale-encode = { version = "0.5.0", path = "scale-encode" }
scale-encode-derive = { version = "0.5.0", path = "scale-encode-derive" }
1 change: 1 addition & 0 deletions scale-encode/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ scale-bits = { version = "0.4.0", default-features = false, features = ["scale-i
scale-encode-derive = { workspace = true, optional = true }
primitive-types = { version = "0.12.0", optional = true, default-features = false }
smallvec = "1.10.0"
derive_more = { version = "0.99.17", default-features = false, features = ["from", "display"] }

[dev-dependencies]
bitvec = { version = "1.0.1", default-features = false }
Expand Down
105 changes: 44 additions & 61 deletions scale-encode/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub struct Error {
kind: ErrorKind,
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}

impl Error {
/// Construct a new error given an error kind.
pub fn new(kind: ErrorKind) -> Error {
Expand All @@ -37,8 +40,26 @@ impl Error {
}
}
/// Construct a new, custom error.
pub fn custom(error: impl Into<CustomError>) -> Error {
Error::new(ErrorKind::Custom(error.into()))
pub fn custom(error: impl CustomError) -> Error {
Error::new(ErrorKind::Custom(Box::new(error)))
}
/// Construct a custom error from a static string.
pub fn custom_str(error: &'static str) -> Error {
#[derive(derive_more::Display, Debug)]
pub struct StrError(pub &'static str);
#[cfg(feature = "std")]
impl std::error::Error for StrError {}

Error::new(ErrorKind::Custom(Box::new(StrError(error))))
}
/// Construct a custom error from an owned string.
pub fn custom_string(error: String) -> Error {
#[derive(derive_more::Display, Debug)]
pub struct StringError(String);
#[cfg(feature = "std")]
impl std::error::Error for StringError {}

Error::new(ErrorKind::Custom(Box::new(StringError(error))))
}
/// Retrieve more information about what went wrong.
pub fn kind(&self) -> &ErrorKind {
Expand Down Expand Up @@ -91,93 +112,68 @@ impl Display for Error {
}

/// The underlying nature of the error.
#[derive(Debug)]
#[derive(Debug, derive_more::From, derive_more::Display)]
pub enum ErrorKind {
/// Cannot find a given type.
#[display(fmt = "Cannot find type with ID {_0}")]
TypeNotFound(u32),
/// Cannot encode the actual type given into the target type ID.
#[display(fmt = "Cannot encode {actual:?} into type with ID {expected}")]
WrongShape {
/// The actual kind we have to encode
actual: Kind,
/// ID of the expected type.
expected: u32,
},
/// The types line up, but the expected length of the target type is different from the length of the input value.
#[display(
fmt = "Cannot encode to type; expected length {expected_len} but got length {actual_len}"
)]
WrongLength {
/// Length we have
actual_len: usize,
/// Length expected for type.
expected_len: usize,
},
/// We cannot encode the number given into the target type; it's out of range.
#[display(fmt = "Number {value} is out of range for target type {expected}")]
NumberOutOfRange {
/// A string represenatation of the numeric value that was out of range.
value: String,
/// Id of the expected numeric type that we tried to encode it to.
expected: u32,
},
/// Cannot find a variant with a matching name on the target type.
#[display(fmt = "Variant {name} does not exist on type with ID {expected}")]
CannotFindVariant {
/// Variant name we can't find in the expected type.
name: String,
/// ID of the expected type.
expected: u32,
},
/// Cannot find a field on our source type that's needed for the target type.
#[display(fmt = "Field {name} does not exist in our source struct")]
CannotFindField {
/// Name of the field which was not provided.
name: String,
},
/// A custom error.
Custom(CustomError),
}

impl From<CustomError> for ErrorKind {
fn from(err: CustomError) -> ErrorKind {
ErrorKind::Custom(err)
}
}

impl Display for ErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ErrorKind::TypeNotFound(id) => write!(f, "Cannot find type with ID {id}"),
ErrorKind::WrongShape { actual, expected } => {
write!(f, "Cannot encode {actual:?} into type with ID {expected}")
}
ErrorKind::WrongLength {
actual_len,
expected_len,
} => {
write!(f, "Cannot encode to type; expected length {expected_len} but got length {actual_len}")
}
ErrorKind::NumberOutOfRange { value, expected } => {
write!(
f,
"Number {value} is out of range for target type {expected}"
)
}
ErrorKind::CannotFindVariant { name, expected } => {
write!(
f,
"Variant {name} does not exist on type with ID {expected}"
)
}
ErrorKind::CannotFindField { name } => {
write!(f, "Field {name} does not exist in our source struct")
}
ErrorKind::Custom(custom_error) => {
write!(f, "Custom error: {custom_error:?}")
}
}
}
#[from]
#[display(fmt = "Custom error: {_0}")]
Custom(Box<dyn CustomError>),
}

/// Anything implementing this trait can be used in [`ErrorKind::Custom`].
#[cfg(feature = "std")]
pub trait CustomError: std::error::Error + Send + Sync + 'static {}
#[cfg(feature = "std")]
type CustomError = Box<dyn std::error::Error + Send + Sync + 'static>;
impl<T: std::error::Error + Send + Sync + 'static> CustomError for T {}

/// Anything implementing this trait can be used in [`ErrorKind::Custom`].
#[cfg(not(feature = "std"))]
type CustomError = Box<dyn core::fmt::Debug + Send + Sync + 'static>;
pub trait CustomError: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static {}
#[cfg(not(feature = "std"))]
impl<T: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static> CustomError for T {}

/// The kind of type that we're trying to encode.
#[allow(missing_docs)]
Expand All @@ -198,27 +194,14 @@ pub enum Kind {
mod test {
use super::*;

#[derive(Debug)]
#[derive(Debug, derive_more::Display)]
enum MyError {
Foo,
}

impl Display for MyError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}

#[cfg(feature = "std")]
impl std::error::Error for MyError {}

#[cfg(not(feature = "std"))]
impl Into<CustomError> for MyError {
fn into(self) -> CustomError {
Box::new(self)
}
}

#[test]
fn custom_error() {
// Just a compile-time check that we can ergonomically provide an arbitrary custom error:
Expand Down

0 comments on commit 2697f92

Please sign in to comment.