diff --git a/packable/packable-derive-test/Cargo.toml b/packable/packable-derive-test/Cargo.toml index aeb43fb..023a507 100644 --- a/packable/packable-derive-test/Cargo.toml +++ b/packable/packable-derive-test/Cargo.toml @@ -1,14 +1,14 @@ [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]] @@ -16,10 +16,10 @@ 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"] diff --git a/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with.rs b/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_field.rs similarity index 100% rename from packable/packable-derive-test/tests/fail/invalid_field_type_verify_with.rs rename to packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_field.rs diff --git a/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with.stderr b/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_field.stderr similarity index 82% rename from packable/packable-derive-test/tests/fail/invalid_field_type_verify_with.stderr rename to packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_field.stderr index 3e9e59d..f80bf8d 100644 --- a/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with.stderr +++ b/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_field.stderr @@ -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)] | ^^^^^^^^ @@ -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(&value: &u64, _: &()) -> Result<(), PickyError> { | ^^^^^^^^^^^^ ------------ diff --git a/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_struct.rs b/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_struct.rs new file mode 100644 index 0000000..47737c2 --- /dev/null +++ b/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_struct.rs @@ -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 for PickyError { + fn from(err: Infallible) -> Self { + match err {} + } +} + +fn verify(&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() {} diff --git a/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_struct.stderr b/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_struct.stderr new file mode 100644 index 0000000..a88eb5d --- /dev/null +++ b/packable/packable-derive-test/tests/fail/invalid_field_type_verify_with_struct.stderr @@ -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(&value: &u64, _: &()) -> Result<(), PickyError> { + | ^^^^^^ ------------ + = note: this error originates in the derive macro `Packable` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/packable/packable-derive-test/tests/pass/verify_with.rs b/packable/packable-derive-test/tests/pass/verify_with_field.rs similarity index 100% rename from packable/packable-derive-test/tests/pass/verify_with.rs rename to packable/packable-derive-test/tests/pass/verify_with_field.rs diff --git a/packable/packable-derive-test/tests/pass/verify_with_struct.rs b/packable/packable-derive-test/tests/pass/verify_with_struct.rs new file mode 100644 index 0000000..3695e66 --- /dev/null +++ b/packable/packable-derive-test/tests/pass/verify_with_struct.rs @@ -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 for PickyError { + fn from(err: Infallible) -> Self { + match err {} + } +} + +fn verify(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() {} diff --git a/packable/packable-derive/CHANGELOG.md b/packable/packable-derive/CHANGELOG.md index 11a94d0..1de7422 100644 --- a/packable/packable-derive/CHANGELOG.md +++ b/packable/packable-derive/CHANGELOG.md @@ -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 diff --git a/packable/packable-derive/Cargo.toml b/packable/packable-derive/Cargo.toml index f55a936..6b60ac5 100644 --- a/packable/packable-derive/Cargo.toml +++ b/packable/packable-derive/Cargo.toml @@ -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", +] } diff --git a/packable/packable-derive/src/fragments.rs b/packable/packable-derive/src/fragments.rs index e557465..4188b88 100644 --- a/packable/packable-derive/src/fragments.rs +++ b/packable/packable-derive/src/fragments.rs @@ -3,7 +3,7 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::Ident; +use syn::{Ident, Path}; use crate::record_info::RecordInfo; @@ -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, crate_name: &Ident) -> Self { let RecordInfo { path, fields_unpack_error_with, @@ -32,6 +32,13 @@ impl Fragments { None => quote!(), }); + let verify_with = match verify_with { + Some(verify_with) => { + quote!(#verify_with::(&unpacked, visitor).map_err(#crate_name::error::UnpackError::from_packable)?;) + } + None => quote!(), + }; + Self { pattern: quote!(#path { #(#fields_pattern_ident: #fields_ident),* }), pack: quote! { @@ -44,9 +51,13 @@ impl Fragments { #fields_verification )* - Ok(#path { + let unpacked = #path { #(#fields_pattern_ident: #fields_ident,)* - }) + }; + + #verify_with + + Ok(unpacked) }, } } diff --git a/packable/packable-derive/src/struct_info.rs b/packable/packable-derive/src/struct_info.rs index 9775a02..8102532 100644 --- a/packable/packable-derive/src/struct_info.rs +++ b/packable/packable-derive/src/struct_info.rs @@ -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, pub(crate) unpack_visitor: UnpackVisitorInfo, pub(crate) inner: RecordInfo, } @@ -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!(()), @@ -32,6 +49,7 @@ impl StructInfo { Ok(Self { unpack_error, + verify_with: verify_with_opt, unpack_visitor, inner, }) diff --git a/packable/packable-derive/src/trait_impl.rs b/packable/packable-derive/src/trait_impl.rs index ad7465d..b0464de 100644 --- a/packable/packable-derive/src/trait_impl.rs +++ b/packable/packable-derive/src/trait_impl.rs @@ -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, @@ -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 diff --git a/packable/packable/CHANGELOG.md b/packable/packable/CHANGELOG.md index 1594fa4..5e53248 100644 --- a/packable/packable/CHANGELOG.md +++ b/packable/packable/CHANGELOG.md @@ -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 diff --git a/packable/packable/Cargo.toml b/packable/packable/Cargo.toml index 003d0e3..4e0835c 100644 --- a/packable/packable/Cargo.toml +++ b/packable/packable/Cargo.toml @@ -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 }