Skip to content

Commit 9fe2802

Browse files
authored
Add Serde Support (#118)
1 parent beb8ee8 commit 9fe2802

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1125
-377
lines changed

.config/topic.dic

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
19
1+
21
22
≥1
33
APIs
44
Changelog
@@ -11,6 +11,8 @@ FQS
1111
invariants
1212
io
1313
MSRV
14+
Serde
15+
Serde's
1416
std's
1517
struct/S
1618
TODO

.github/workflows/lint.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ jobs:
2828
features: --features safe
2929
- channel: nightly
3030
features: --features nightly
31+
- channel: stable
32+
features: --features serde
3133
- channel: stable
3234
features: --features zeroize
3335
- channel: stable
3436
features: --features zeroize-on-drop
3537
- channel: nightly
3638
features: --features safe,nightly
39+
- channel: stable
40+
features: --features safe,serde
3741
- channel: stable
3842
features: --features safe,zeroize
3943
- channel: stable
@@ -42,6 +46,12 @@ jobs:
4246
features: --features nightly,zeroize
4347
- channel: nightly
4448
features: --features nightly,zeroize-on-drop
49+
- channel: nightly
50+
features: --features nightly,serde
51+
- channel: stable
52+
features: --features serde,zeroize
53+
- channel: stable
54+
features: --features serde,zeroize-on-drop
4555
- channel: nightly
4656
features: --all-features
4757

.github/workflows/test.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,23 @@ jobs:
2828
features:
2929
- ""
3030
- --features safe
31+
- --features serde
3132
- --features zeroize
3233
- --features zeroize-on-drop
34+
- --features safe,serde
3335
- --features safe,zeroize
3436
- --features safe,zeroize-on-drop
37+
- --features serde,zeroize
38+
- --features serde,zeroize-on-drop
3539
include:
3640
- rust: 1.57.0
3741
msrv: true
3842
- rust: nightly
3943
features: --features nightly
4044
- rust: nightly
4145
features: --features safe,nightly
46+
- rust: nightly
47+
features: --features nightly,serde
4248
- rust: nightly
4349
features: --features nightly,zeroize
4450
- rust: nightly
@@ -83,17 +89,23 @@ jobs:
8389
features:
8490
- ""
8591
- --features safe
92+
- --features serde
8693
- --features zeroize
8794
- --features zeroize-on-drop
95+
- --features safe,serde
8896
- --features safe,zeroize
8997
- --features safe,zeroize-on-drop
98+
- --features serde,zeroize
99+
- --features serde,zeroize-on-drop
90100
include:
91101
- rust: 1.57.0
92102
msrv: true
93103
- rust: nightly
94104
features: --features nightly
95105
- rust: nightly
96106
features: --features safe,nightly
107+
- rust: nightly
108+
features: --features nightly,serde
97109
- rust: nightly
98110
features: --features nightly,zeroize
99111
- rust: nightly
@@ -127,9 +139,9 @@ jobs:
127139
matrix:
128140
rust:
129141
- version: 1.57.0
130-
features: safe,zeroize-on-drop
142+
features: safe,serde_,zeroize-on-drop
131143
- version: stable
132-
features: safe,zeroize-on-drop
144+
features: safe,serde_,zeroize-on-drop
133145

134146
steps:
135147
- name: Checkout

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111
- `no_drop` item-level option to `ZeroizeOnDrop` which does not implement
1212
`Drop` but instead only asserts that every field implements `ZeroizeOnDrop`.
13+
- Support deriving Serde's `Deserialize` and `Serialize` trait via Serde's own
14+
proc-macro.
15+
16+
### Changed
17+
- Error messages now point to crate features instead of reporting traits as
18+
unsupported.
1319

1420
### Changed
1521
- Remove unnecessary validation of the default discriminant type for enums.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ proc-macro = true
2323
[features]
2424
nightly = []
2525
safe = []
26+
serde = []
2627
zeroize = []
2728
zeroize-on-drop = ["zeroize"]
2829

@@ -41,8 +42,10 @@ syn = { version = "2", default-features = false, features = [
4142
[dev-dependencies]
4243
pretty_assertions = "1"
4344
rustversion = "1"
45+
serde_ = { package = "serde", version = "1", default-features = false, features = ["derive"] }
46+
serde_test = "1"
4447
trybuild = { version = "1.0.18", default-features = false }
45-
zeroize_ = { version = "1.5", package = "zeroize", default-features = false }
48+
zeroize_ = { package = "zeroize", version = "1.5", default-features = false }
4649

4750
[package.metadata.docs.rs]
4851
all-features = true

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ assert!(!(StructExample <= StructExample));
200200
Note that it is not possible to use `incomparable` with [`Eq`] or [`Ord`] as
201201
that would break their invariants.
202202

203+
### Serde `Deserialize` and `Serialize`
204+
205+
Deriving [`Deserialize`] and [`Serialize`] works as expected. While
206+
derive-where does not offer any attribute options, regular `serde` attributes
207+
can be used. Derive-where will respect
208+
[`#[serde(crate = "...")]`](https://serde.rs/container-attrs.html#crate).
209+
203210
### `Zeroize` options
204211

205212
`Zeroize` has two options:
@@ -259,11 +266,13 @@ The following traits can be derived with derive-where:
259266
- [`Copy`]
260267
- [`Debug`]
261268
- [`Default`]
269+
- [`Deserialize`]: Only available with the `serde` crate feature.
262270
- [`Eq`]
263271
- [`Hash`]
264272
- [`Ord`]
265273
- [`PartialEq`]
266274
- [`PartialOrd`]
275+
- [`Serialize`]: Only available with the `serde` crate feature.
267276
- [`Zeroize`]: Only available with the `zeroize` crate feature.
268277
- [`ZeroizeOnDrop`]: Only available with the `zeroize` crate feature. If the
269278
`zeroize-on-drop` feature is enabled, it implements [`ZeroizeOnDrop`],
@@ -347,12 +356,14 @@ conditions.
347356
[`core::intrinsics::discriminant_value`]: https://doc.rust-lang.org/core/intrinsics/fn.discriminant_value.html
348357
[`derive_where`]: https://docs.rs/derive-where/latest/derive_where/attr.derive_where.html
349358
[`Discriminant`]: https://doc.rust-lang.org/core/mem/struct.Discriminant.html
359+
[`Deserialize`]: https://docs.rs/serde/latest/serde/derive.Deserialize.html
350360
[`Drop`]: https://doc.rust-lang.org/core/ops/trait.Drop.html
351361
[`Eq`]: https://doc.rust-lang.org/core/cmp/trait.Eq.html
352362
[`i32`]: https://doc.rust-lang.org/core/primitive.i32.html
353363
[`isize`]: https://doc.rust-lang.org/core/primitive.isize.html
354364
[`Ord`]: https://doc.rust-lang.org/core/cmp/trait.Ord.html
355365
[`PartialEq`]: https://doc.rust-lang.org/core/cmp/trait.PartialEq.html
356366
[`PartialOrd`]: https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html
367+
[`Serialize`]: https://docs.rs/serde/latest/serde/derive.Serialize.html
357368
[`transmute`]: https://doc.rust-lang.org/core/mem/fn.transmute.html
358369
[`unreachable`]: https://doc.rust-lang.org/core/macro.unreachable.html

src/attr.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! [`Attribute`](syn::Attribute) parsing for items, variants and fields.
22
3+
mod crate_;
34
mod default;
45
mod field;
56
mod incomparable;
@@ -12,6 +13,7 @@ mod zeroize_fqs;
1213
#[cfg(feature = "zeroize")]
1314
pub use self::zeroize_fqs::ZeroizeFqs;
1415
pub use self::{
16+
crate_::parse_crate,
1517
default::Default,
1618
field::FieldAttr,
1719
incomparable::Incomparable,

src/attr/crate_.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//! Parsing implementation for `#[derive_where(crate = ...)]`.
2+
3+
use proc_macro2::Span;
4+
use syn::{spanned::Spanned, Expr, ExprLit, ExprPath, Lit, Meta, Path, Result};
5+
6+
use crate::{util, Error, DERIVE_WHERE};
7+
8+
/// Parses `#[derive_where(crate = ...)]`.
9+
pub fn parse_crate(meta: Meta) -> Result<(Path, Span)> {
10+
if let Meta::NameValue(name_value) = meta {
11+
let path = match &name_value.value {
12+
Expr::Lit(ExprLit {
13+
lit: Lit::Str(lit_str),
14+
..
15+
}) => match lit_str.parse::<Path>() {
16+
Ok(path) => path,
17+
Err(error) => return Err(Error::path(lit_str.span(), error)),
18+
},
19+
Expr::Path(ExprPath { path, .. }) => path.clone(),
20+
_ => return Err(Error::option_syntax(name_value.value.span())),
21+
};
22+
23+
if path == util::path_from_strs(&[DERIVE_WHERE]) {
24+
Err(Error::path_unnecessary(
25+
path.span(),
26+
&format!("::{}", DERIVE_WHERE),
27+
))
28+
} else {
29+
Ok((path, name_value.span()))
30+
}
31+
} else {
32+
Err(Error::option_syntax(meta.span()))
33+
}
34+
}

src/attr/item.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ use syn::{
77
parse::{discouraged::Speculative, Parse, ParseStream},
88
punctuated::Punctuated,
99
spanned::Spanned,
10-
Attribute, BoundLifetimes, Data, Ident, Meta, PredicateType, Result, Token, Type, TypePath,
11-
WhereClause, WherePredicate,
10+
Attribute, BoundLifetimes, Data, Ident, Meta, Path, PredicateType, Result, Token, Type,
11+
TypePath, WhereClause, WherePredicate,
1212
};
1313

1414
use crate::{trait_::DeriveTrait, Error, Incomparable, Item, Skip, SkipGroup, Trait, DERIVE_WHERE};
1515

1616
/// Attributes on item.
1717
#[derive(Default)]
1818
pub struct ItemAttr {
19+
/// Path to `derive_where` if set by `#[derive_where(crate = ...)]`.
20+
pub crate_: Option<Path>,
1921
/// [`Trait`]s to skip all fields for.
2022
pub skip_inner: Skip,
2123
/// Comparing item will yield `false` for [`PartialEq`] and [`None`] for
@@ -59,28 +61,29 @@ impl ItemAttr {
5961
// Needs to be parsed after all traits are known.
6062
incomparables.push(meta)
6163
} else if meta.path().is_ident("crate") {
62-
// Do nothing, we checked this before
63-
// already.
64+
let (path, _) = super::parse_crate(meta)
65+
.expect("failed to parse previously parsed attribute");
66+
self_.crate_ = Some(path);
6467
}
6568
// The list can have one item but still not be the `skip_inner`
6669
// attribute, continue with parsing `DeriveWhere`.
6770
else {
6871
self_
6972
.derive_wheres
70-
.push(DeriveWhere::from_attr(span, data, attr)?);
73+
.push(DeriveWhere::from_attr(attrs, span, data, attr)?);
7174
}
7275
}
7376
_ => self_
7477
.derive_wheres
75-
.push(DeriveWhere::from_attr(span, data, attr)?),
78+
.push(DeriveWhere::from_attr(attrs, span, data, attr)?),
7679
}
7780
}
7881
// Anything list that isn't using `,` as separator, is because we expect
7982
// `A, B; C`.
8083
else {
8184
self_
8285
.derive_wheres
83-
.push(DeriveWhere::from_attr(span, data, attr)?)
86+
.push(DeriveWhere::from_attr(attrs, span, data, attr)?)
8487
}
8588
} else {
8689
return Err(Error::option_syntax(attr.meta.span()));
@@ -93,6 +96,18 @@ impl ItemAttr {
9396
return Err(Error::none(span));
9497
}
9598

99+
// Check for `#[serde(...)]` attributes without `De/Serialize`.
100+
#[cfg(feature = "serde")]
101+
if !self_.derive_wheres.iter().any(|derive_where| {
102+
derive_where.contains(Trait::Deserialize) | derive_where.contains(Trait::Serialize)
103+
}) {
104+
for attr in attrs {
105+
if attr.path().is_ident("serde") {
106+
return Err(Error::serde_without_serde(attr.span()));
107+
}
108+
}
109+
}
110+
96111
// Merge `DeriveWhere`s with the same bounds.
97112
self_
98113
.derive_wheres
@@ -152,7 +167,7 @@ pub struct DeriveWhere {
152167

153168
impl DeriveWhere {
154169
/// Create [`DeriveWhere`] from [`Attribute`].
155-
fn from_attr(span: Span, data: &Data, attr: &Attribute) -> Result<Self> {
170+
fn from_attr(attrs: &[Attribute], span: Span, data: &Data, attr: &Attribute) -> Result<Self> {
156171
attr.parse_args_with(|input: ParseStream| {
157172
// Parse the attribute input, this should either be:
158173
// - Comma separated traits.
@@ -169,7 +184,7 @@ impl DeriveWhere {
169184
// Start with parsing a trait.
170185
// Not checking for duplicates here, we do that after merging `derive_where`s
171186
// with the same bounds.
172-
let (span, trait_) = DeriveTrait::from_stream(span, data, input)?;
187+
let (span, trait_) = DeriveTrait::from_stream(attrs, span, data, input)?;
173188
spans.push(span);
174189
traits.push(trait_);
175190

src/attr/skip.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ impl SkipGroup {
270270
| Trait::Ord
271271
| Trait::PartialEq
272272
| Trait::PartialOrd => true,
273+
#[cfg(feature = "serde")]
274+
Trait::Deserialize | Trait::Serialize => false,
273275
#[cfg(feature = "zeroize")]
274276
Trait::Zeroize | Trait::ZeroizeOnDrop => true,
275277
}

0 commit comments

Comments
 (0)