Skip to content

Commit

Permalink
Add no-std compatibility (#26)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
haerdib authored Jul 11, 2023
1 parent c2cb48d commit c616353
Show file tree
Hide file tree
Showing 14 changed files with 293 additions and 82 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,32 @@ jobs:
# we run tests using BitVec<u64,_> 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/[email protected]

- name: Check no_std build
uses: actions-rs/[email protected]
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
Expand Down Expand Up @@ -138,6 +164,12 @@ jobs:
command: test
args: --all-targets --workspace

- name: Cargo test no_std
uses: actions-rs/[email protected]
with:
command: test
args: --all-targets --workspace --no-default-features --features derive,primitive-types

- name: Cargo test docs
uses: actions-rs/[email protected]
with:
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
members = [
"scale-decode",
"scale-decode-derive",
"testing/no_std",
]

[workspace.package]
Expand Down
29 changes: 19 additions & 10 deletions scale-decode-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Option<&str>, _> = fields
let vals: #path_to_scale_decode::BTreeMap<Option<&str>, _> = fields
.map(|res| res.map(|item| (item.name(), item)))
.collect::<Result<_, _>>()?;
Ok(#path_to_type::#variant_ident { #(#field_composite_keyvals),* })
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -205,7 +211,7 @@ fn generate_struct_impl(
return self.visit_tuple(&mut value.as_tuple(), type_id)
}

let vals: ::std::collections::HashMap<Option<&str>, _> =
let vals: #path_to_scale_decode::BTreeMap<Option<&str>, _> =
value.map(|res| res.map(|item| (item.name(), item))).collect::<Result<_, _>>()?;

Ok(#path_to_type { #(#field_composite_keyvals),* })
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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())
)
}

Expand All @@ -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:
Expand Down Expand Up @@ -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()));
}

(
Expand Down
18 changes: 10 additions & 8 deletions scale-decode/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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"] }
6 changes: 3 additions & 3 deletions scale-decode/src/error/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(".")?;
Expand Down
85 changes: 68 additions & 17 deletions scale-decode/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -68,71 +69,121 @@ 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<crate::visitor::DecodeError> for Error {
fn from(err: crate::visitor::DecodeError) -> Error {
impl From<DecodeError> 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,
/// The possible variants that we can decode into.
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,
/// Length fo the type we're trying to decode into
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<DecodeError> for ErrorKind {
fn from(err: DecodeError) -> ErrorKind {
ErrorKind::VisitorDecodeError(err)
}
}

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::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<dyn std::error::Error + Send + Sync + 'static>;

#[cfg(not(feature = "std"))]
type CustomError = Box<dyn core::fmt::Debug + Send + Sync + 'static>;

#[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<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
Loading

0 comments on commit c616353

Please sign in to comment.