Skip to content

Commit

Permalink
packable: add a struct-level verify_with (#71)
Browse files Browse the repository at this point in the history
* Add a struct-level verify_with

* Bump versions

* Update release date

* Nits
  • Loading branch information
thibault-martinez authored Nov 14, 2023
1 parent eab7b46 commit dd2f346
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 35 deletions.
14 changes: 7 additions & 7 deletions packable/packable-derive-test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
[package]
name = "packable-derive-test"
version = "0.7.0"
authors = [ "IOTA Stiftung" ]
version = "0.0.0"
authors = ["IOTA Stiftung"]
edition = "2021"
description = "Test suite for the `packable-derive` crate."
readme = "README.md"
repository = "https://github.com/iotaledger/common-rs"
license = "Apache-2.0"
publish = false
keywords = [ "binary", "no_std", "serialization", "packable" ]
keywords = ["binary", "no_std", "serialization", "packable"]
homepage = "https://www.iota.org"

[[test]]
name = "tests"
path = "tests/lib.rs"

[dev-dependencies]
packable = { version = "=0.8.3", path = "../packable", default-features = false }
packable = { version = "=0.9.0", path = "../packable", default-features = false }

rustversion = { version = "1.0.9", default-features = false }
trybuild = { version = "1.0.71", default-features = false, features = [ "diff" ] }
rustversion = { version = "1.0.14", default-features = false }
trybuild = { version = "1.0.85", default-features = false, features = ["diff"] }

[package.metadata.cargo-udeps.ignore]
development = [ "packable" ]
development = ["packable"]
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0308]: mismatched types
--> tests/fail/invalid_field_type_verify_with.rs:32:10
--> tests/fail/invalid_field_type_verify_with_field.rs:32:10
|
32 | #[derive(Packable)]
| ^^^^^^^^
Expand All @@ -10,7 +10,7 @@ error[E0308]: mismatched types
= note: expected reference `&u64`
found reference `&u8`
note: function defined here
--> tests/fail/invalid_field_type_verify_with.rs:24:4
--> tests/fail/invalid_field_type_verify_with_field.rs:24:4
|
24 | fn verify_value<const VERIFY: bool>(&value: &u64, _: &()) -> Result<(), PickyError> {
| ^^^^^^^^^^^^ ------------
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

#![allow(unused_imports)]

use core::convert::Infallible;

use packable::{
error::{UnknownTagError, UnpackError, UnpackErrorExt},
packer::Packer,
unpacker::Unpacker,
Packable,
};

#[derive(Debug)]
pub struct PickyError(u8);

impl From<Infallible> for PickyError {
fn from(err: Infallible) -> Self {
match err {}
}
}

fn verify<const VERIFY: bool>(&value: &u64, _: &()) -> Result<(), PickyError> {
if !VERIFY || value == 42 {
Ok(())
} else {
Err(PickyError(value as u8))
}
}

#[derive(Packable)]
#[packable(unpack_error = PickyError)]
#[packable(verify_with = verify)]
pub struct Picky(u8);

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error[E0308]: mismatched types
--> tests/fail/invalid_field_type_verify_with_struct.rs:32:10
|
32 | #[derive(Packable)]
| ^^^^^^^^
| |
| expected `&u64`, found `&Picky`
| arguments to this function are incorrect
|
= note: expected reference `&u64`
found reference `&Picky`
note: function defined here
--> tests/fail/invalid_field_type_verify_with_struct.rs:24:4
|
24 | fn verify<const VERIFY: bool>(&value: &u64, _: &()) -> Result<(), PickyError> {
| ^^^^^^ ------------
= note: this error originates in the derive macro `Packable` (in Nightly builds, run with -Z macro-backtrace for more info)
37 changes: 37 additions & 0 deletions packable/packable-derive-test/tests/pass/verify_with_struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

#![allow(unused_imports)]

use core::convert::Infallible;

use packable::{
error::{UnknownTagError, UnpackError, UnpackErrorExt},
packer::Packer,
unpacker::Unpacker,
Packable,
};

#[derive(Debug)]
pub struct PickyError(u8);

impl From<Infallible> for PickyError {
fn from(err: Infallible) -> Self {
match err {}
}
}

fn verify<const VERIFY: bool>(value: &Picky, _: &()) -> Result<(), PickyError> {
if !VERIFY || value.0 == 42 {
Ok(())
} else {
Err(PickyError(value.0))
}
}

#[derive(Packable)]
#[packable(unpack_error = PickyError)]
#[packable(verify_with = verify)]
pub struct Picky(u8);

fn main() {}
6 changes: 6 additions & 0 deletions packable/packable-derive/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security -->

## 0.8.0 - 2023-11-14

### Added

- Struct-level `verify_with` attribute;

## 0.7.0 - 2022-10-10

### Changed
Expand Down
26 changes: 18 additions & 8 deletions packable/packable-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
[package]
name = "packable-derive"
version = "0.7.0"
authors = [ "IOTA Stiftung" ]
version = "0.8.0"
authors = ["IOTA Stiftung"]
edition = "2021"
description = "Derive macro for the `packable` crate."
readme = "README.md"
repository = "https://github.com/iotaledger/common-rs"
license = "Apache-2.0"
keywords = [ "binary", "no_std", "serialization", "packable" ]
keywords = ["binary", "no_std", "serialization", "packable"]
homepage = "https://www.iota.org"

[lib]
proc-macro = true

[dependencies]
proc-macro-crate = { version = "1.2.1", default-features = false }
proc-macro-error = { version = "1.0.4", default-features = false, features = [ "syn-error" ] }
proc-macro2 = { version = "1.0.46", default-features = false }
quote = { version = "1.0.21", default-features = false }
syn = { version = "1.0.102", default-features = false, features = [ "full", "extra-traits", "parsing", "printing", "derive", "proc-macro", "clone-impls" ] }
proc-macro-crate = { version = "1.3.1", default-features = false }
proc-macro-error = { version = "1.0.4", default-features = false, features = [
"syn-error",
] }
proc-macro2 = { version = "1.0.69", default-features = false }
quote = { version = "1.0.33", default-features = false }
syn = { version = "1.0.109", default-features = false, features = [
"full",
"extra-traits",
"parsing",
"printing",
"derive",
"proc-macro",
"clone-impls",
] }
21 changes: 16 additions & 5 deletions packable/packable-derive/src/fragments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;
use syn::{Ident, Path};

use crate::record_info::RecordInfo;

Expand All @@ -12,12 +12,12 @@ pub(crate) struct Fragments {
pub(crate) pattern: TokenStream,
// An expression that packs the record.
pub(crate) pack: TokenStream,
// An expresion that unpacks the record.
// An expression that unpacks the record.
pub(crate) unpack: TokenStream,
}

impl Fragments {
pub(crate) fn new(info: RecordInfo, crate_name: &Ident) -> Self {
pub(crate) fn new(info: RecordInfo, verify_with: Option<Path>, crate_name: &Ident) -> Self {
let RecordInfo {
path,
fields_unpack_error_with,
Expand All @@ -32,6 +32,13 @@ impl Fragments {
None => quote!(),
});

let verify_with = match verify_with {
Some(verify_with) => {
quote!(#verify_with::<VERIFY>(&unpacked, visitor).map_err(#crate_name::error::UnpackError::from_packable)?;)
}
None => quote!(),
};

Self {
pattern: quote!(#path { #(#fields_pattern_ident: #fields_ident),* }),
pack: quote! {
Expand All @@ -44,9 +51,13 @@ impl Fragments {
#fields_verification
)*

Ok(#path {
let unpacked = #path {
#(#fields_pattern_ident: #fields_ident,)*
})
};

#verify_with

Ok(unpacked)
},
}
}
Expand Down
22 changes: 20 additions & 2 deletions packable/packable-derive/src/struct_info.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use syn::{parse_quote, Attribute, Field, Fields, Ident, Path, Result};
use syn::{parse::ParseStream, parse_quote, Attribute, Field, Fields, Ident, Path, Result};

use crate::{
parse::filter_attrs, record_info::RecordInfo, unpack_error_info::UnpackErrorInfo,
parse::{filter_attrs, parse_kv, skip_stream},
record_info::RecordInfo,
unpack_error_info::UnpackErrorInfo,
unpack_visitor_info::UnpackVisitorInfo,
};

pub(crate) struct StructInfo {
pub(crate) unpack_error: UnpackErrorInfo,
pub(crate) verify_with: Option<Path>,
pub(crate) unpack_visitor: UnpackVisitorInfo,
pub(crate) inner: RecordInfo,
}
Expand All @@ -23,6 +26,20 @@ impl StructInfo {
None => parse_quote!(core::convert::Infallible),
})?;

let mut verify_with_opt = None;

for attr in filtered_attrs.clone() {
if let Some(verify_with) = attr.parse_args_with(|stream: ParseStream| {
let opt = parse_kv("verify_with", stream)?;
if opt.is_none() {
skip_stream(stream)?;
}
Ok(opt)
})? {
verify_with_opt = Some(verify_with);
}
}

let unpack_visitor = UnpackVisitorInfo::new(filtered_attrs, || match fields.iter().next() {
Some(Field { ty, .. }) => parse_quote!(<#ty as #crate_name::Packable>::UnpackVisitor),
None => parse_quote!(()),
Expand All @@ -32,6 +49,7 @@ impl StructInfo {

Ok(Self {
unpack_error,
verify_with: verify_with_opt,
unpack_visitor,
inner,
})
Expand Down
4 changes: 2 additions & 2 deletions packable/packable-derive/src/trait_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl TraitImpl {
let unpack_error = info.unpack_error.unpack_error.clone().into_token_stream();
let unpack_visitor = info.unpack_visitor.unpack_visitor.clone().into_token_stream();

let Fragments { pattern, pack, unpack }: Fragments = Fragments::new(info.inner, &crate_name);
let Fragments { pattern, pack, unpack } = Fragments::new(info.inner, info.verify_with, &crate_name);

Ok(Self {
ident: input.ident,
Expand Down Expand Up @@ -65,7 +65,7 @@ impl TraitImpl {
for (index, VariantInfo { tag, inner }) in info.variants_info.into_iter().enumerate() {
let variant_ident = inner.path.segments.last().unwrap().clone();

let Fragments { pattern, pack, unpack } = Fragments::new(inner, &crate_name);
let Fragments { pattern, pack, unpack } = Fragments::new(inner, None, &crate_name);

// @pvdrz: The span here is very important, otherwise the compiler won't detect
// unreachable patterns in the generated code for some reason. I think this is related
Expand Down
6 changes: 6 additions & 0 deletions packable/packable/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security -->

## 0.9.0 - 2023-11-14

### Added

- Struct-level `verify_with` attribute;

## 0.8.3 - 2023-09-18

### Added
Expand Down
20 changes: 11 additions & 9 deletions packable/packable/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
[package]
name = "packable"
version = "0.8.3"
authors = [ "IOTA Stiftung" ]
version = "0.9.0"
authors = ["IOTA Stiftung"]
edition = "2021"
description = "A crate for packing and unpacking binary representations."
readme = "README.md"
repository = "https://github.com/iotaledger/common-rs"
license = "Apache-2.0"
keywords = [ "binary", "no_std", "serialization", "packable" ]
keywords = ["binary", "no_std", "serialization", "packable"]
homepage = "https://www.iota.org"

[features]
io = [ "std" ]
std = [ "serde?/std", "primitive-types?/std" ]
usize = [ ]
io = ["std"]
std = ["serde?/std", "primitive-types?/std"]
usize = []

[build-dependencies]
autocfg = { version = "1.1.0", default-features = false }

[dependencies]
packable-derive = { version = "=0.7.0", path = "../packable-derive", default-features = false }
packable-derive = { version = "=0.8.0", path = "../packable-derive", default-features = false }

primitive-types = { version = "0.12.0", default-features = false, optional = true }
serde = { version = "1.0.145", default-features = false, features = [ "derive" ], optional = true }
primitive-types = { version = "0.12.2", default-features = false, optional = true }
serde = { version = "1.0.192", default-features = false, features = [
"derive",
], optional = true }

0 comments on commit dd2f346

Please sign in to comment.