diff --git a/CHANGELOG.md b/CHANGELOG.md index 62a0412..3d2caf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. + ## [v0.4.0] - 2023-07-11 - Add support for `no_std` (+alloc) builds ([#11](https://github.com/paritytech/scale-encode/pull/11)). Thankyou @haerdib! diff --git a/Cargo.toml b/Cargo.toml index 9725d3a..b298b51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ ] [workspace.package] -version = "0.4.0" +version = "0.5.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -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" } diff --git a/scale-encode/Cargo.toml b/scale-encode/Cargo.toml index b1f6cd3..6e4f0f1 100644 --- a/scale-encode/Cargo.toml +++ b/scale-encode/Cargo.toml @@ -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 } diff --git a/scale-encode/src/error/mod.rs b/scale-encode/src/error/mod.rs index 43cf0d8..715ff4c 100644 --- a/scale-encode/src/error/mod.rs +++ b/scale-encode/src/error/mod.rs @@ -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 { @@ -37,8 +40,26 @@ impl Error { } } /// Construct a new, custom error. - pub fn custom(error: impl Into) -> 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 { @@ -91,11 +112,13 @@ 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, @@ -103,6 +126,9 @@ pub enum ErrorKind { 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, @@ -110,6 +136,7 @@ pub enum ErrorKind { 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, @@ -117,6 +144,7 @@ pub enum ErrorKind { 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, @@ -124,60 +152,28 @@ pub enum ErrorKind { 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 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), } +/// 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; +impl CustomError for T {} +/// Anything implementing this trait can be used in [`ErrorKind::Custom`]. #[cfg(not(feature = "std"))] -type CustomError = Box; +pub trait CustomError: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static {} +#[cfg(not(feature = "std"))] +impl CustomError for T {} /// The kind of type that we're trying to encode. #[allow(missing_docs)] @@ -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 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: