1 | //! [![github]](https://github.com/dtolnay/serde-repr) [![crates-io]](https://crates.io/crates/serde_repr) [![docs-rs]](https://docs.rs/serde_repr) |
2 | //! |
3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github |
4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust |
5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs |
6 | //! |
7 | //! <br> |
8 | //! |
9 | //! Derive `Serialize` and `Deserialize` that delegates to the underlying repr |
10 | //! of a C-like enum. |
11 | //! |
12 | //! # Examples |
13 | //! |
14 | //! ``` |
15 | //! use serde_repr::{Serialize_repr, Deserialize_repr}; |
16 | //! |
17 | //! #[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)] |
18 | //! #[repr(u8)] |
19 | //! enum SmallPrime { |
20 | //! Two = 2, |
21 | //! Three = 3, |
22 | //! Five = 5, |
23 | //! Seven = 7, |
24 | //! } |
25 | //! |
26 | //! fn main() -> serde_json::Result<()> { |
27 | //! let j = serde_json::to_string(&SmallPrime::Seven)?; |
28 | //! assert_eq!(j, "7" ); |
29 | //! |
30 | //! let p: SmallPrime = serde_json::from_str("2" )?; |
31 | //! assert_eq!(p, SmallPrime::Two); |
32 | //! |
33 | //! Ok(()) |
34 | //! } |
35 | //! ``` |
36 | |
37 | #![doc (html_root_url = "https://docs.rs/serde_repr/0.1.20" )] |
38 | #![allow (clippy::single_match_else)] |
39 | |
40 | extern crate proc_macro; |
41 | |
42 | mod parse; |
43 | |
44 | use proc_macro::TokenStream; |
45 | use quote::quote; |
46 | use syn::parse_macro_input; |
47 | |
48 | use crate::parse::Input; |
49 | |
50 | #[proc_macro_derive (Serialize_repr)] |
51 | pub fn derive_serialize(input: TokenStream) -> TokenStream { |
52 | let input = parse_macro_input!(input as Input); |
53 | let ident = input.ident; |
54 | let repr = input.repr; |
55 | |
56 | let match_variants = input.variants.iter().map(|variant| { |
57 | let variant = &variant.ident; |
58 | quote! { |
59 | #ident::#variant => #ident::#variant as #repr, |
60 | } |
61 | }); |
62 | |
63 | TokenStream::from(quote! { |
64 | #[allow(deprecated)] |
65 | impl serde::Serialize for #ident { |
66 | #[allow(clippy::use_self)] |
67 | fn serialize<S>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error> |
68 | where |
69 | S: serde::Serializer |
70 | { |
71 | let value: #repr = match *self { |
72 | #(#match_variants)* |
73 | }; |
74 | serde::Serialize::serialize(&value, serializer) |
75 | } |
76 | } |
77 | }) |
78 | } |
79 | |
80 | #[proc_macro_derive (Deserialize_repr, attributes(serde))] |
81 | pub fn derive_deserialize(input: TokenStream) -> TokenStream { |
82 | let input = parse_macro_input!(input as Input); |
83 | let ident = input.ident; |
84 | let repr = input.repr; |
85 | let variants = input.variants.iter().map(|variant| &variant.ident); |
86 | |
87 | let declare_discriminants = input.variants.iter().map(|variant| { |
88 | let variant = &variant.ident; |
89 | quote! { |
90 | const #variant: #repr = #ident::#variant as #repr; |
91 | } |
92 | }); |
93 | |
94 | let match_discriminants = input.variants.iter().map(|variant| { |
95 | let variant = &variant.ident; |
96 | quote! { |
97 | discriminant::#variant => ::core::result::Result::Ok(#ident::#variant), |
98 | } |
99 | }); |
100 | |
101 | let error_format = match input.variants.len() { |
102 | 1 => "invalid value: {}, expected {}" .to_owned(), |
103 | 2 => "invalid value: {}, expected {} or {}" .to_owned(), |
104 | n => "invalid value: {}, expected one of: {}" .to_owned() + &", {}" .repeat(n - 1), |
105 | }; |
106 | |
107 | let other_arm = match input.default_variant { |
108 | Some(variant) => { |
109 | let variant = &variant.ident; |
110 | quote! { |
111 | ::core::result::Result::Ok(#ident::#variant) |
112 | } |
113 | } |
114 | None => quote! { |
115 | ::core::result::Result::Err(serde::de::Error::custom( |
116 | format_args!(#error_format, other #(, discriminant::#variants)*) |
117 | )) |
118 | }, |
119 | }; |
120 | |
121 | TokenStream::from(quote! { |
122 | #[allow(deprecated)] |
123 | impl<'de> serde::Deserialize<'de> for #ident { |
124 | #[allow(clippy::use_self)] |
125 | fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error> |
126 | where |
127 | D: serde::Deserializer<'de>, |
128 | { |
129 | #[allow(non_camel_case_types)] |
130 | struct discriminant; |
131 | |
132 | #[allow(non_upper_case_globals)] |
133 | impl discriminant { |
134 | #(#declare_discriminants)* |
135 | } |
136 | |
137 | match <#repr as serde::Deserialize>::deserialize(deserializer)? { |
138 | #(#match_discriminants)* |
139 | other => #other_arm, |
140 | } |
141 | } |
142 | } |
143 | }) |
144 | } |
145 | |