From c6163537f217120fca7250b6d394da66a1b0156e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bigna=20H=C3=A4rdi?= Date: Tue, 11 Jul 2023 17:02:10 +0200 Subject: [PATCH] Add no-std compatibility (#26) * make it compile * add rust toolchain * fix CI * fix ci * no need to check tests * fix primitive types * add From * fix scale -bits * clean up * fix CI * remove derive from no_std * fix CI * fix derive feature * do not check tests * add test no std crate * update CI * improve Display * make test no_std compatible * no yet compiling: make derive no_std compatible * fix derive * fix cargo check * fix erive * add doc[hidden] * remove extra vec * fix comment . * add re-export of primitive types * add Display impl to DecodeError * fix unexpected display * udpate testing crate --- .github/workflows/rust.yml | 32 +++++++++ Cargo.toml | 1 + scale-decode-derive/src/lib.rs | 29 +++++--- scale-decode/Cargo.toml | 18 ++--- scale-decode/src/error/context.rs | 6 +- scale-decode/src/error/mod.rs | 85 +++++++++++++++++----- scale-decode/src/impls/mod.rs | 48 +++++++------ scale-decode/src/impls/primitive_types.rs | 4 +- scale-decode/src/lib.rs | 16 +++++ scale-decode/src/visitor/mod.rs | 88 +++++++++++++++++------ scale-decode/src/visitor/types/str.rs | 2 +- testing/no_std/Cargo.toml | 16 +++++ testing/no_std/README.md | 1 + testing/no_std/src/lib.rs | 29 ++++++++ 14 files changed, 293 insertions(+), 82 deletions(-) create mode 100644 testing/no_std/Cargo.toml create mode 100644 testing/no_std/README.md create mode 100644 testing/no_std/src/lib.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7ae6012..1b9ac5f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -71,6 +71,32 @@ jobs: # we run tests using BitVec which doesn't. args: --all-features --target wasm32-unknown-unknown + no_std: + name: Check no_std build + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install Rust stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: aarch64-unknown-none + override: true + + - name: Rust Cache + uses: Swatinem/rust-cache@v1.3.0 + + - name: Check no_std build + uses: actions-rs/cargo@v1.0.3 + with: + command: check + # The aarch64-unknown-none doesn't support `std`, so this + # will fail if the crate is not no_std compatible. + args: --no-default-features --target aarch64-unknown-none --features primitive-types,derive + fmt: name: Cargo fmt runs-on: ubuntu-latest @@ -138,6 +164,12 @@ jobs: command: test args: --all-targets --workspace + - name: Cargo test no_std + uses: actions-rs/cargo@v1.0.3 + with: + command: test + args: --all-targets --workspace --no-default-features --features derive,primitive-types + - name: Cargo test docs uses: actions-rs/cargo@v1.0.3 with: diff --git a/Cargo.toml b/Cargo.toml index d127377..73a40fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "scale-decode", "scale-decode-derive", + "testing/no_std", ] [workspace.package] diff --git a/scale-decode-derive/src/lib.rs b/scale-decode-derive/src/lib.rs index 23f1d24..d51be1c 100644 --- a/scale-decode-derive/src/lib.rs +++ b/scale-decode-derive/src/lib.rs @@ -13,6 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +extern crate alloc; + +use alloc::string::ToString; use darling::FromAttributes; use proc_macro2::TokenStream as TokenStream2; use quote::quote; @@ -86,7 +89,7 @@ fn generate_enum_impl( let vals = fields; Ok(#path_to_type::#variant_ident { #(#field_tuple_keyvals),* }) } else { - let vals: ::std::collections::HashMap, _> = fields + let vals: #path_to_scale_decode::BTreeMap, _> = fields .map(|res| res.map(|item| (item.name(), item))) .collect::>()?; Ok(#path_to_type::#variant_ident { #(#field_composite_keyvals),* }) @@ -128,13 +131,16 @@ fn generate_enum_impl( quote!( const _: () = { #visibility struct Visitor #impl_generics ( - ::std::marker::PhantomData<#phantomdata_type> + ::core::marker::PhantomData<#phantomdata_type> ); + use #path_to_scale_decode::vec; + use #path_to_scale_decode::ToString; + impl #impl_generics #path_to_scale_decode::IntoVisitor for #path_to_type #ty_generics #where_clause { type Visitor = Visitor #ty_generics; fn into_visitor() -> Self::Visitor { - Visitor(::std::marker::PhantomData) + Visitor(::core::marker::PhantomData) } } @@ -205,7 +211,7 @@ fn generate_struct_impl( return self.visit_tuple(&mut value.as_tuple(), type_id) } - let vals: ::std::collections::HashMap, _> = + let vals: #path_to_scale_decode::BTreeMap, _> = value.map(|res| res.map(|item| (item.name(), item))).collect::>()?; Ok(#path_to_type { #(#field_composite_keyvals),* }) @@ -255,13 +261,16 @@ fn generate_struct_impl( quote!( const _: () = { #visibility struct Visitor #impl_generics ( - ::std::marker::PhantomData<#phantomdata_type> + ::core::marker::PhantomData<#phantomdata_type> ); + use #path_to_scale_decode::vec; + use #path_to_scale_decode::ToString; + impl #impl_generics #path_to_scale_decode::IntoVisitor for #path_to_type #ty_generics #where_clause { type Visitor = Visitor #ty_generics; fn into_visitor() -> Self::Visitor { - Visitor(::std::marker::PhantomData) + Visitor(::core::marker::PhantomData) } } @@ -320,8 +329,8 @@ fn named_field_keyvals<'f>( if skip_field { return ( false, - quote!(#field_ident: ::std::default::Default::default()), - quote!(#field_ident: ::std::default::Default::default()) + quote!(#field_ident: ::core::default::Default::default()), + quote!(#field_ident: ::core::default::Default::default()) ) } @@ -332,7 +341,7 @@ fn named_field_keyvals<'f>( quote!(#field_ident: { let val = *vals .get(&Some(#field_name)) - .ok_or_else(|| #path_to_scale_decode::Error::new(#path_to_scale_decode::error::ErrorKind::CannotFindField { name: #field_name.to_owned() }))?; + .ok_or_else(|| #path_to_scale_decode::Error::new(#path_to_scale_decode::error::ErrorKind::CannotFindField { name: #field_name.to_string() }))?; val.decode_as_type().map_err(|e| e.at_field(#field_name))? }), // For turning named fields in scale typeinfo into unnamed fields on tuple like type: @@ -362,7 +371,7 @@ fn unnamed_field_vals<'f>( // If a field is skipped, we expect it to have a Default impl to use to populate it instead. if skip_field { - return (false, quote!(::std::default::Default::default())); + return (false, quote!(::core::default::Default::default())); } ( diff --git a/scale-decode/Cargo.toml b/scale-decode/Cargo.toml index 43e4782..8d57486 100644 --- a/scale-decode/Cargo.toml +++ b/scale-decode/Cargo.toml @@ -14,7 +14,10 @@ keywords.workspace = true include.workspace = true [features] -default = ["derive", "primitive-types"] +default = ["std", "derive", "primitive-types"] + +# Activates std feature. +std = ["scale-info/std"] # Impls for primitive-types. primitive-types = ["dep:primitive-types"] @@ -23,18 +26,17 @@ primitive-types = ["dep:primitive-types"] derive = ["dep:scale-decode-derive"] [dependencies] -scale-info = { version = "2.7.0", default-features = false, features = ["bit-vec", "std"] } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full"] } -thiserror = "1.0.24" -scale-bits = { version = "0.3.0", default-features = false, features = ["scale-info"] } +scale-info = { version = "2.7.0", default-features = false, features = ["bit-vec"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-bits = { version = "0.4.0", default-features = false, features = ["scale-info"] } scale-decode-derive = { workspace = true, optional = true } primitive-types = { version = "0.12.0", optional = true, default-features = false } smallvec = "1.10.0" [dev-dependencies] -scale-info = { version = "2.7.0", default-features = false, features = ["bit-vec", "std", "derive"] } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] } -bitvec = "1" +scale-info = { version = "2.7.0", default-features = false, features = ["bit-vec", "derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "bit-vec"] } +bitvec = { version = "1.0.1", default-features = false } trybuild = "1.0.72" # Enable the scale-info feature for testing. primitive-types = { version = "0.12.0", default-features = false, features = ["scale-info"] } diff --git a/scale-decode/src/error/context.rs b/scale-decode/src/error/context.rs index 8b4d89e..5dbb440 100644 --- a/scale-decode/src/error/context.rs +++ b/scale-decode/src/error/context.rs @@ -16,7 +16,7 @@ //! This module provides a [`Context`] type, which tracks the path //! that we're attempting to encode to aid in error reporting. -use std::borrow::Cow; +use alloc::{borrow::Cow, vec::Vec}; /// A cheaply clonable opaque context which allows us to track the current /// location into a type that we're trying to encode, to aid in @@ -55,8 +55,8 @@ impl<'a> Path<'a> { } } -impl<'a> std::fmt::Display for Path<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl<'a> core::fmt::Display for Path<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { for (idx, loc) in self.0.iter().enumerate() { if idx != 0 { f.write_str(".")?; diff --git a/scale-decode/src/error/mod.rs b/scale-decode/src/error/mod.rs index 45efdbb..7a810f6 100644 --- a/scale-decode/src/error/mod.rs +++ b/scale-decode/src/error/mod.rs @@ -16,13 +16,14 @@ //! An error that is emitted whenever some decoding fails. mod context; -use std::borrow::Cow; -use std::fmt::Display; - pub use context::{Context, Location}; +use crate::visitor::DecodeError; +use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec}; +use core::fmt::Display; + /// An error produced while attempting to decode some type. -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub struct Error { context: Context, kind: ErrorKind, @@ -68,34 +69,31 @@ impl Error { } impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let path = self.context.path(); let kind = &self.kind; write!(f, "Error at {path}: {kind}") } } -impl From for Error { - fn from(err: crate::visitor::DecodeError) -> Error { +impl From for Error { + fn from(err: DecodeError) -> Error { Error::new(err.into()) } } /// The underlying nature of the error. -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub enum ErrorKind { /// Something went wrong decoding the bytes based on the type /// and type registry provided. - #[error("Error decoding bytes given the type ID and registry provided: {0}")] - VisitorDecodeError(#[from] crate::visitor::DecodeError), + VisitorDecodeError(DecodeError), /// We cannot decode the number seen into the target type; it's out of range. - #[error("Number {value} is out of range")] NumberOutOfRange { /// A string representation of the numeric value that was out of range. value: String, }, /// We cannot find the variant we're trying to decode from in the target type. - #[error("Cannot find variant {got}; expects one of {expected:?}")] CannotFindVariant { /// The variant that we are given back from the encoded bytes. got: String, @@ -103,7 +101,6 @@ pub enum ErrorKind { expected: Vec<&'static str>, }, /// The types line up, but the expected length of the target type is different from the length of the input value. - #[error("Cannot decode from type; expected length {expected_len} but got length {actual_len}")] WrongLength { /// Length of the type we are trying to decode from actual_len: usize, @@ -111,28 +108,82 @@ pub enum ErrorKind { expected_len: usize, }, /// Cannot find a field that we need to decode to our target type - #[error("Field {name} does not exist in our encoded data")] CannotFindField { /// Name of the field which was not provided. name: String, }, /// A custom error. - #[error("Custom error: {0}")] Custom(CustomError), } +impl From for ErrorKind { + fn from(err: DecodeError) -> ErrorKind { + ErrorKind::VisitorDecodeError(err) + } +} + +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::VisitorDecodeError(decode_error) => { + write!(f, "Error decoding bytes given the type ID and registry provided: {decode_error:?}") + } + ErrorKind::NumberOutOfRange { value } => { + write!(f, "Number {value} is out of range") + } + ErrorKind::CannotFindVariant { got, expected } => { + write!(f, "Cannot find variant {got}; expects one of {expected:?}") + } + ErrorKind::WrongLength { actual_len, expected_len } => { + write!(f, "Cannot decode from type; expected length {expected_len} but got length {actual_len}") + } + ErrorKind::CannotFindField { name } => { + write!(f, "Field {name} does not exist in our encoded data") + } + ErrorKind::Custom(custom_error) => { + write!(f, "Custom error: {custom_error:?}") + } + } + } +} + +#[cfg(feature = "std")] type CustomError = Box; +#[cfg(not(feature = "std"))] +type CustomError = Box; + #[cfg(test)] mod test { use super::*; - #[derive(thiserror::Error, Debug)] + #[derive(Debug)] enum MyError { - #[error("Foo!")] 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: diff --git a/scale-decode/src/impls/mod.rs b/scale-decode/src/impls/mod.rs index 58748fb..d039d64 100644 --- a/scale-decode/src/impls/mod.rs +++ b/scale-decode/src/impls/mod.rs @@ -24,24 +24,30 @@ use crate::{ }, DecodeAsFields, FieldIter, IntoVisitor, }; +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque}, + rc::Rc, + string::{String, ToString}, + sync::Arc, + vec, + vec::Vec, +}; use codec::Compact; use core::num::{ NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, }; -use scale_bits::Bits; -use std::ops::{Range, RangeInclusive}; -use std::rc::Rc; -use std::sync::Arc; -use std::time::Duration; -use std::{ - borrow::Cow, - collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque}, +use core::{ marker::PhantomData, + ops::{Range, RangeInclusive}, + time::Duration, }; +use scale_bits::Bits; pub struct BasicVisitor { - _marker: std::marker::PhantomData, + _marker: core::marker::PhantomData, } /// Generate an [`IntoVisitor`] impl for basic types `T` where `BasicVisitor` impls `Visitor`. @@ -54,7 +60,7 @@ macro_rules! impl_into_visitor { { type Visitor = BasicVisitor<$ty $(< $($lt,)* $($param),* >)?>; fn into_visitor() -> Self::Visitor { - BasicVisitor { _marker: std::marker::PhantomData } + BasicVisitor { _marker: core::marker::PhantomData } } } }; @@ -247,7 +253,7 @@ where { type Visitor = BasicVisitor>; fn into_visitor() -> Self::Visitor { - BasicVisitor { _marker: std::marker::PhantomData } + BasicVisitor { _marker: core::marker::PhantomData } } } @@ -332,7 +338,7 @@ where { type Visitor = BasicVisitor<[T; N]>; fn into_visitor() -> Self::Visitor { - BasicVisitor { _marker: std::marker::PhantomData } + BasicVisitor { _marker: core::marker::PhantomData } } } @@ -621,7 +627,7 @@ macro_rules! impl_decode_tuple { { type Visitor = BasicVisitor<($($t,)*)>; fn into_visitor() -> Self::Visitor { - BasicVisitor { _marker: std::marker::PhantomData } + BasicVisitor { _marker: core::marker::PhantomData } } } @@ -676,7 +682,7 @@ where D: DecodeItemIterator<'scale, 'info>, { let mut idx = 0; - std::iter::from_fn(move || { + core::iter::from_fn(move || { let item = decoder .decode_item(T::into_visitor()) .map(|res| res.map_err(|e| Error::from(e).at_idx(idx))); @@ -706,7 +712,7 @@ mod test { fn assert_encode_decode_to_with(a: &A, b: &B) where A: Encode, - B: DecodeAsType + PartialEq + std::fmt::Debug, + B: DecodeAsType + PartialEq + core::fmt::Debug, T: scale_info::TypeInfo + 'static, { let (type_id, types) = make_type::(); @@ -720,7 +726,7 @@ mod test { fn assert_encode_decode_to(a: &A, b: &B) where A: Encode + scale_info::TypeInfo + 'static, - B: DecodeAsType + PartialEq + std::fmt::Debug, + B: DecodeAsType + PartialEq + core::fmt::Debug, { assert_encode_decode_to_with::(a, b); } @@ -728,7 +734,7 @@ mod test { // Most of the time we'll just make sure that we can encode and decode back to the same type. fn assert_encode_decode_with(a: &A) where - A: Encode + DecodeAsType + PartialEq + std::fmt::Debug, + A: Encode + DecodeAsType + PartialEq + core::fmt::Debug, T: scale_info::TypeInfo + 'static, { assert_encode_decode_to_with::(a, a) @@ -737,7 +743,7 @@ mod test { // Most of the time we'll just make sure that we can encode and decode back to the same type. fn assert_encode_decode(a: &A) where - A: Encode + scale_info::TypeInfo + 'static + DecodeAsType + PartialEq + std::fmt::Debug, + A: Encode + scale_info::TypeInfo + 'static + DecodeAsType + PartialEq + core::fmt::Debug, { assert_encode_decode_to(a, a) } @@ -748,7 +754,7 @@ mod test { Foo: scale_info::TypeInfo + DecodeAsFields + PartialEq - + std::fmt::Debug + + core::fmt::Debug + codec::Encode + 'static, { @@ -870,7 +876,7 @@ mod test { + 'static + DecodeAsType + PartialEq - + std::fmt::Debug + + core::fmt::Debug + Clone, { let tup = ((a.clone(),),); @@ -1079,7 +1085,7 @@ mod test { #[test] fn decode_as_fields_works() { - use std::fmt::Debug; + use core::fmt::Debug; #[derive(DecodeAsType, codec::Encode, PartialEq, Debug, scale_info::TypeInfo)] #[decode_as_type(crate_path = "crate")] diff --git a/scale-decode/src/impls/primitive_types.rs b/scale-decode/src/impls/primitive_types.rs index f55cc77..5bd8b6e 100644 --- a/scale-decode/src/impls/primitive_types.rs +++ b/scale-decode/src/impls/primitive_types.rs @@ -40,7 +40,7 @@ macro_rules! impl_visitor { input, type_id.0, types, - BasicVisitor::<[u8; $len / 8]> { _marker: std::marker::PhantomData }, + BasicVisitor::<[u8; $len / 8]> { _marker: core::marker::PhantomData }, ) .map(|res| <$ty>::from(res)); DecodeAsTypeResult::Decoded(res) @@ -50,7 +50,7 @@ macro_rules! impl_visitor { impl IntoVisitor for $ty { type Visitor = BasicVisitor<$ty>; fn into_visitor() -> Self::Visitor { - BasicVisitor { _marker: std::marker::PhantomData } + BasicVisitor { _marker: core::marker::PhantomData } } } }; diff --git a/scale-decode/src/lib.rs b/scale-decode/src/lib.rs index a9cb35e..645d4c9 100644 --- a/scale-decode/src/lib.rs +++ b/scale-decode/src/lib.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![cfg_attr(not(feature = "std"), no_std)] + /*! `parity-scale-codec` provides a `Decode` trait which allows bytes to be scale decoded into types based on the shape of those types. This crate builds on this, and allows bytes to be decoded into types based on [`scale_info`] type information, rather than the shape @@ -135,6 +137,8 @@ for efficient type based decoding. */ #![deny(missing_docs)] +extern crate alloc; + mod impls; pub mod error; @@ -147,6 +151,18 @@ pub use visitor::Visitor; use scale_info::form::PortableForm; pub use scale_info::PortableRegistry; +// This is exported for generated derive code to use, to be compatible with std or no-std as needed. +#[doc(hidden)] +pub use alloc::{collections::BTreeMap, string::ToString, vec}; + +/// Re-exports of external crates. +pub mod ext { + #[cfg(feature = "primitive-types")] + pub use primitive_types; +} + +use alloc::vec::Vec; + /// This trait is implemented for any type `T` where `T` implements [`IntoVisitor`] and the errors returned /// from this [`Visitor`] can be converted into [`Error`]. It's essentially a convenience wrapper around /// [`visitor::decode_with_visitor`] that mirrors `scale-encode`'s `EncodeAsType`. diff --git a/scale-decode/src/visitor/mod.rs b/scale-decode/src/visitor/mod.rs index b2ca911..2815f70 100644 --- a/scale-decode/src/visitor/mod.rs +++ b/scale-decode/src/visitor/mod.rs @@ -18,6 +18,7 @@ mod decode; pub mod types; +use core::fmt::Display; use scale_info::form::PortableForm; use types::*; @@ -278,37 +279,80 @@ pub trait Visitor: Sized { } /// An error decoding SCALE bytes. -#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DecodeError { /// We ran into an error trying to decode a bit sequence. - #[error("Cannot decode bit sequence: {0}")] - BitSequenceError(#[from] BitSequenceError), + BitSequenceError(BitSequenceError), /// The type we're trying to decode is supposed to be compact encoded, but that is not possible. - #[error("Could not decode compact encoded type into {0:?}")] CannotDecodeCompactIntoType(scale_info::Type), /// Failure to decode bytes into a string. - #[error("Could not decode string: {0}")] - InvalidStr(#[from] std::str::Utf8Error), + InvalidStr(alloc::str::Utf8Error), /// We could not convert the [`u32`] that we found into a valid [`char`]. - #[error("{0} is expected to be a valid char, but is not")] InvalidChar(u32), /// We expected more bytes to finish decoding, but could not find them. - #[error("Ran out of data during decoding")] NotEnoughInput, /// We found a variant that does not match with any in the type we're trying to decode from. - #[error("Could not find variant with index {0} in {1:?}")] VariantNotFound(u8, scale_info::TypeDefVariant), /// Some error emitted from a [`codec::Decode`] impl. - #[error("{0}")] - CodecError(#[from] codec::Error), + CodecError(codec::Error), /// We could not find the type given in the type registry provided. - #[error("Cannot find type with ID {0}")] TypeIdNotFound(u32), /// This is returned by default if a visitor function is not implemented. - #[error("Unexpected type {0}")] Unexpected(Unexpected), } +impl From for DecodeError { + fn from(err: codec::Error) -> Self { + Self::CodecError(err) + } +} + +impl From for DecodeError { + fn from(err: BitSequenceError) -> Self { + Self::BitSequenceError(err) + } +} + +impl From for DecodeError { + fn from(err: alloc::str::Utf8Error) -> Self { + Self::InvalidStr(err) + } +} + +impl Display for DecodeError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + DecodeError::BitSequenceError(error) => { + write!(f, "Cannot decode bit sequence: {error:?}") + } + DecodeError::CannotDecodeCompactIntoType(portable_type) => { + write!(f, "Could not decode compact encoded type into {portable_type:?}") + } + DecodeError::InvalidStr(error) => { + write!(f, "Could not decode string: {error:?}") + } + DecodeError::InvalidChar(char) => { + write!(f, "{char} is expected to be a valid char, but is not") + } + DecodeError::NotEnoughInput => { + write!(f, "Ran out of data during decoding") + } + DecodeError::VariantNotFound(index, type_def_variant) => { + write!(f, "Could not find variant with index {index} in {type_def_variant:?}") + } + DecodeError::CodecError(error) => { + write!(f, "{error}") + } + DecodeError::TypeIdNotFound(id) => { + write!(f, "Cannot find type with ID {id}") + } + DecodeError::Unexpected(unexpected) => { + write!(f, "Unexpected type {unexpected}") + } + } + } +} + /// This is returned by default when a visitor function isn't implemented. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(missing_docs)] @@ -336,8 +380,8 @@ pub enum Unexpected { Bitsequence, } -impl std::fmt::Display for Unexpected { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Unexpected { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let s = match self { Unexpected::Bool => "bool", Unexpected::Char => "char", @@ -410,6 +454,10 @@ mod test { use crate::visitor::TypeId; use super::*; + use alloc::borrow::ToOwned; + use alloc::string::{String, ToString}; + use alloc::vec; + use alloc::vec::Vec; use codec::{self, Encode}; use scale_info::PortableRegistry; @@ -975,7 +1023,7 @@ mod test { #[test] fn zero_copy_using_info_and_scale_lifetimes() { - use std::collections::BTreeMap; + use alloc::collections::BTreeMap; #[derive(codec::Encode, scale_info::TypeInfo)] struct Foo { @@ -1004,7 +1052,7 @@ mod test { // This zero-copy decodes a composite into map of strings: struct ZeroCopyMapVisitor; impl Visitor for ZeroCopyMapVisitor { - type Value<'scale, 'info> = std::collections::BTreeMap<&'info str, &'scale str>; + type Value<'scale, 'info> = alloc::collections::BTreeMap<&'info str, &'scale str>; type Error = DecodeError; fn visit_composite<'scale, 'info>( @@ -1012,7 +1060,7 @@ mod test { value: &mut Composite<'scale, 'info>, _type_id: TypeId, ) -> Result, Self::Error> { - let mut vals = std::collections::BTreeMap::<&'info str, &'scale str>::new(); + let mut vals = alloc::collections::BTreeMap::<&'info str, &'scale str>::new(); for item in value { let item = item?; let Some(key) = item.name() else { continue }; @@ -1060,7 +1108,7 @@ mod test { // We can also use this functionality to "fall-back" to a Decode impl // (though obviously with the caveat that this may be incorrect). - struct CodecDecodeVisitor(std::marker::PhantomData); + struct CodecDecodeVisitor(core::marker::PhantomData); impl Visitor for CodecDecodeVisitor { type Value<'scale, 'info> = T; type Error = DecodeError; @@ -1080,7 +1128,7 @@ mod test { &mut &*input_encoded, ty_id, &types, - CodecDecodeVisitor(std::marker::PhantomData), + CodecDecodeVisitor(core::marker::PhantomData), ) .unwrap(); assert_eq!(decoded, ("hello".to_string(), "world".to_string())); diff --git a/scale-decode/src/visitor/types/str.rs b/scale-decode/src/visitor/types/str.rs index 20c9502..2e9d725 100644 --- a/scale-decode/src/visitor/types/str.rs +++ b/scale-decode/src/visitor/types/str.rs @@ -56,6 +56,6 @@ impl<'scale> Str<'scale> { pub fn as_str(&self) -> Result<&'scale str, DecodeError> { let start = self.compact_len; let end = start + self.len; - std::str::from_utf8(&self.bytes[start..end]).map_err(DecodeError::InvalidStr) + alloc::str::from_utf8(&self.bytes[start..end]).map_err(DecodeError::InvalidStr) } } diff --git a/testing/no_std/Cargo.toml b/testing/no_std/Cargo.toml new file mode 100644 index 0000000..fca44bd --- /dev/null +++ b/testing/no_std/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "test-no-std" +description = "Testing no_std build of scale-decode" +readme = "README.md" + +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +keywords.workspace = true +include.workspace = true + +[dependencies] +scale-decode = { path = "../../scale-decode", default-features = false, features = ["primitive-types", "derive"] } diff --git a/testing/no_std/README.md b/testing/no_std/README.md new file mode 100644 index 0000000..a13815d --- /dev/null +++ b/testing/no_std/README.md @@ -0,0 +1 @@ +test no_std build of scale-decode and scale-decode-derive diff --git a/testing/no_std/src/lib.rs b/testing/no_std/src/lib.rs new file mode 100644 index 0000000..5d702d7 --- /dev/null +++ b/testing/no_std/src/lib.rs @@ -0,0 +1,29 @@ +#![no_std] +extern crate alloc; + +use scale_decode::DecodeAsType; + +pub struct NotDecodeAsType; + +// Enums with generic params and even lifetimes can impl DecodeAsType. +#[derive(DecodeAsType)] +pub enum Bar<'a, T, U, V> { + Wibble(bool, T, U, V), + Wobble, + Boo(alloc::borrow::Cow<'a, str>), +} + +// This impls DecodeAsType ok; we set no default trait bounds. +#[derive(DecodeAsType)] +#[decode_as_type(trait_bounds = "")] +pub enum NoTraitBounds { + Wibble(core::marker::PhantomData), +} + +// Structs (and const bounds) impl DecodeAsType OK. +#[derive(DecodeAsType)] +pub struct MyStruct { + pub array: [Bar; V], +} + +pub fn can_decode_as_type() {}