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.18" )] |
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 | impl serde::Serialize for #ident { |
65 | #[allow(clippy::use_self)] |
66 | fn serialize<S>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error> |
67 | where |
68 | S: serde::Serializer |
69 | { |
70 | let value: #repr = match *self { |
71 | #(#match_variants)* |
72 | }; |
73 | serde::Serialize::serialize(&value, serializer) |
74 | } |
75 | } |
76 | }) |
77 | } |
78 | |
79 | #[proc_macro_derive (Deserialize_repr, attributes(serde))] |
80 | pub fn derive_deserialize(input: TokenStream) -> TokenStream { |
81 | let input = parse_macro_input!(input as Input); |
82 | let ident = input.ident; |
83 | let repr = input.repr; |
84 | let variants = input.variants.iter().map(|variant| &variant.ident); |
85 | |
86 | let declare_discriminants = input.variants.iter().map(|variant| { |
87 | let variant = &variant.ident; |
88 | quote! { |
89 | const #variant: #repr = #ident::#variant as #repr; |
90 | } |
91 | }); |
92 | |
93 | let match_discriminants = input.variants.iter().map(|variant| { |
94 | let variant = &variant.ident; |
95 | quote! { |
96 | discriminant::#variant => ::core::result::Result::Ok(#ident::#variant), |
97 | } |
98 | }); |
99 | |
100 | let error_format = match input.variants.len() { |
101 | 1 => "invalid value: {}, expected {}" .to_owned(), |
102 | 2 => "invalid value: {}, expected {} or {}" .to_owned(), |
103 | n => "invalid value: {}, expected one of: {}" .to_owned() + &", {}" .repeat(n - 1), |
104 | }; |
105 | |
106 | let other_arm = match input.default_variant { |
107 | Some(variant) => { |
108 | let variant = &variant.ident; |
109 | quote! { |
110 | ::core::result::Result::Ok(#ident::#variant) |
111 | } |
112 | } |
113 | None => quote! { |
114 | ::core::result::Result::Err(serde::de::Error::custom( |
115 | format_args!(#error_format, other #(, discriminant::#variants)*) |
116 | )) |
117 | }, |
118 | }; |
119 | |
120 | TokenStream::from(quote! { |
121 | impl<'de> serde::Deserialize<'de> for #ident { |
122 | #[allow(clippy::use_self)] |
123 | fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error> |
124 | where |
125 | D: serde::Deserializer<'de>, |
126 | { |
127 | #[allow(non_camel_case_types)] |
128 | struct discriminant; |
129 | |
130 | #[allow(non_upper_case_globals)] |
131 | impl discriminant { |
132 | #(#declare_discriminants)* |
133 | } |
134 | |
135 | match <#repr as serde::Deserialize>::deserialize(deserializer)? { |
136 | #(#match_discriminants)* |
137 | other => #other_arm, |
138 | } |
139 | } |
140 | } |
141 | }) |
142 | } |
143 | |