Skip to content

Commit 2960518

Browse files
authored
Merge pull request #6 from bitflags/feat/serde
Support deriving serde traits
2 parents 3323d13 + 389f0f3 commit 2960518

File tree

9 files changed

+191
-0
lines changed

9 files changed

+191
-0
lines changed

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@ categories = ["no-std"]
1111
edition = "2021"
1212
exclude = ["/tests", "/.github"]
1313

14+
[features]
15+
serde = ["dep:serde", "bitflags-derive-macros/serde"]
16+
1417
[dependencies.bitflags-derive-macros]
1518
path = "macros"
1619
version = "0.0.3"
1720

1821
[dependencies.bitflags]
1922
version = "2"
2023

24+
[dependencies.serde]
25+
version = "1"
26+
optional = true
27+
2128
# Adding new library support to `bitflags-derive`:
2229
#
2330
# 1. Add an optional dependency here

macros/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ edition = "2021"
1313
[lib]
1414
proc-macro = true
1515

16+
[features]
17+
serde = []
18+
1619
[dependencies.proc-macro2]
1720
version = "1"
1821

macros/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ mod debug;
1515
mod display;
1616
mod from_str;
1717

18+
#[cfg(feature = "serde")]
19+
mod serde;
20+
1821
/**
1922
Derive [`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html).
2023
@@ -48,6 +51,26 @@ pub fn derive_bitflags_from_str(item: proc_macro::TokenStream) -> proc_macro::To
4851
from_str::expand(syn::parse_macro_input!(item as syn::DeriveInput)).unwrap_or_compile_error()
4952
}
5053

54+
/**
55+
Derive [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html).
56+
*/
57+
#[cfg(feature = "serde")]
58+
#[proc_macro_derive(FlagsSerialize)]
59+
pub fn derive_bitflags_serialize(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
60+
serde::serialize::expand(syn::parse_macro_input!(item as syn::DeriveInput))
61+
.unwrap_or_compile_error()
62+
}
63+
64+
/**
65+
Derive [`Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html).
66+
*/
67+
#[cfg(feature = "serde")]
68+
#[proc_macro_derive(FlagsDeserialize)]
69+
pub fn derive_bitflags_deserialize(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
70+
serde::deserialize::expand(syn::parse_macro_input!(item as syn::DeriveInput))
71+
.unwrap_or_compile_error()
72+
}
73+
5174
trait ResultExt {
5275
fn unwrap_or_compile_error(self) -> proc_macro::TokenStream;
5376
}

macros/src/serde.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
pub(crate) mod serialize {
2+
use proc_macro2::TokenStream;
3+
4+
pub(crate) fn expand(item: syn::DeriveInput) -> Result<TokenStream, syn::Error> {
5+
let ident = item.ident;
6+
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
7+
8+
Ok(
9+
quote!(impl #impl_generics bitflags_derive::__private::serde::Serialize for #ident #ty_generics #where_clause {
10+
fn serialize<S: bitflags_derive::__private::serde::Serializer>(&self, serializer: S) -> bitflags_derive::__private::core::result::Result<S::Ok, S::Error> {
11+
bitflags_derive::__private::serde::serialize(self, serializer)
12+
}
13+
}),
14+
)
15+
}
16+
}
17+
18+
pub(crate) mod deserialize {
19+
use proc_macro2::{Span, TokenStream};
20+
21+
pub(crate) fn expand(item: syn::DeriveInput) -> Result<TokenStream, syn::Error> {
22+
let ident = item.ident;
23+
24+
let de_lt = syn::Lifetime::new("'bitflags_derive_de", Span::call_site());
25+
26+
let mut de_generics = item.generics.clone();
27+
de_generics
28+
.params
29+
.push_value(syn::GenericParam::Lifetime(syn::LifetimeParam::new(
30+
de_lt.clone(),
31+
)));
32+
33+
let (impl_generics, _, where_clause) = de_generics.split_for_impl();
34+
let (_, ty_generics, _) = item.generics.split_for_impl();
35+
36+
Ok(
37+
quote!(impl #impl_generics bitflags_derive::__private::serde::Deserialize<#de_lt> for #ident #ty_generics #where_clause {
38+
fn deserialize<D: bitflags_derive::__private::serde::Deserializer<#de_lt>>(deserializer: D) -> bitflags_derive::__private::core::result::Result<Self, D::Error> {
39+
bitflags_derive::__private::serde::deserialize(deserializer)
40+
}
41+
}),
42+
)
43+
}
44+
}

src/__private/serde.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
extern crate serde;
2+
3+
use bitflags::{
4+
parser::{self, ParseHex, WriteHex},
5+
Flags,
6+
};
7+
8+
use core::{fmt, str};
9+
10+
pub use serde::{
11+
de::{Error, Visitor},
12+
Deserialize, Deserializer, Serialize, Serializer,
13+
};
14+
15+
struct AsDisplay<'a, B>(pub(crate) &'a B);
16+
17+
impl<'a, B: Flags> fmt::Display for AsDisplay<'a, B>
18+
where
19+
B::Bits: WriteHex,
20+
{
21+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22+
parser::to_writer(self.0, f)
23+
}
24+
}
25+
26+
/**
27+
Serialize a set of flags as a human-readable string or their underlying bits.
28+
29+
Any unknown bits will be retained.
30+
*/
31+
pub fn serialize<B: Flags, S: Serializer>(flags: &B, serializer: S) -> Result<S::Ok, S::Error>
32+
where
33+
B::Bits: WriteHex + Serialize,
34+
{
35+
// Serialize human-readable flags as a string like `"A | B"`
36+
if serializer.is_human_readable() {
37+
serializer.collect_str(&AsDisplay(flags))
38+
}
39+
// Serialize non-human-readable flags directly as the underlying bits
40+
else {
41+
flags.bits().serialize(serializer)
42+
}
43+
}
44+
45+
/**
46+
Deserialize a set of flags from a human-readable string or their underlying bits.
47+
48+
Any unknown bits will be retained.
49+
*/
50+
pub fn deserialize<'de, B: Flags, D: Deserializer<'de>>(deserializer: D) -> Result<B, D::Error>
51+
where
52+
B::Bits: ParseHex + Deserialize<'de>,
53+
{
54+
if deserializer.is_human_readable() {
55+
// Deserialize human-readable flags by parsing them from strings like `"A | B"`
56+
struct FlagsVisitor<B>(core::marker::PhantomData<B>);
57+
58+
impl<'de, B: Flags> Visitor<'de> for FlagsVisitor<B>
59+
where
60+
B::Bits: ParseHex,
61+
{
62+
type Value = B;
63+
64+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65+
formatter.write_str("a string value of `|` separated flags")
66+
}
67+
68+
fn visit_str<E: Error>(self, flags: &str) -> Result<Self::Value, E> {
69+
parser::from_str(flags).map_err(|e| E::custom(e))
70+
}
71+
}
72+
73+
deserializer.deserialize_str(FlagsVisitor(Default::default()))
74+
} else {
75+
// Deserialize non-human-readable flags directly from the underlying bits
76+
let bits = B::Bits::deserialize(deserializer)?;
77+
78+
Ok(B::from_bits_retain(bits))
79+
}
80+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ pub use bitflags_derive_macros::*;
5050

5151
#[doc(hidden)]
5252
pub mod __private {
53+
#[cfg(feature = "serde")]
54+
pub mod serde;
55+
5356
pub use bitflags;
5457
pub use core;
5558
}

tests/ui/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@ version = "0.0.0"
44
edition = "2021"
55
publish = false
66

7+
[features]
8+
serde = ["bitflags-derive/serde", "dep:serde_test"]
9+
710
[dependencies.bitflags-derive]
811
path = "../../"
912

1013
[dependencies.bitflags]
1114
version = "2"
1215

16+
[dependencies.serde_test]
17+
version = "1"
18+
optional = true
19+
1320
[dependencies.trybuild]
1421
version = "1"

tests/ui/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ extern crate bitflags_derive;
99
mod debug;
1010
mod display;
1111
mod from_str;
12+
13+
#[cfg(feature = "serde")]
14+
mod serde;

tests/ui/src/serde.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use serde_test::{assert_tokens, Configure, Token::*};
2+
3+
#[test]
4+
fn derive_serialize_deserialize() {
5+
bitflags! {
6+
#[derive(FlagsSerialize, FlagsDeserialize, PartialEq, Eq, Debug)]
7+
struct Flags: u8 {
8+
const A = 1;
9+
const B = 1 << 1;
10+
const C = 1 << 2;
11+
}
12+
}
13+
14+
assert_tokens(&Flags::empty().readable(), &[Str("")]);
15+
16+
assert_tokens(&Flags::empty().compact(), &[U8(0)]);
17+
18+
assert_tokens(&(Flags::A | Flags::B).readable(), &[Str("A | B")]);
19+
20+
assert_tokens(&(Flags::A | Flags::B).compact(), &[U8(1 | 2)]);
21+
}

0 commit comments

Comments
 (0)