1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//! # Configurable Serde
//!
//! A crate providing a procedural macro to apply reusable serde configurations.
//!
//! The core of this crate is the [`#[configure_serde]`](configure_serde) attribute macro, which
//! generates `#[serde(...)]` attributes for you.
//!
//! There is also a generator macro provided [`create_config!`], which builds a wrapper
//! to apply [`#[configure_serde]`](configure_serde) to all wrapped structs and enums.
//!
//! NOTE: While it is tested and working, this is an early stage of the project
//! so it lacks many possible configurable parameters and also may be incompatible
//! with more complicated structs and enums.
//!
//! ## TL;DR
//!
//! ```rust
//! use configurable_serde::create_config;
//! use serde::{Serialize, Deserialize};
//!
//! // Generate serde configuration applier:
//!
//! create_config! {
//! my_api_models,
//!
//! struct_rename_all = "camelCase",
//! enum_rename_all = "SCREAMING_SNAKE_CASE",
//! skip_if_none,
//! deny_unknown_fields
//! }
//!
//! // Apply defined serde configuration to multiple structs or enums:
//!
//! my_api_models! {
//! #[derive(Serialize, Deserialize)]
//! struct User { // camelCase and deny_unknown_fields is applied here
//! name: String,
//! surname: Option<String>, // skip_if_none is applied here
//! vehicle: Vehicle,
//! }
//!
//! #[derive(Serialize, Deserialize)]
//! enum Vehicle { // SCREAMING_SNAKE_CASE is applied here
//! Car,
//! Bicycle,
//! ElectricScooter,
//! }
//! }
//! ```
//!
//! So this will be expanded to:
//!
//! ```rust
//! use serde::{Serialize, Deserialize};
//!
//! #[derive(Serialize, Deserialize)]
//! #[serde(rename_all = "camelCase", deny_unknown_fields)]
//! struct User {
//! name: String,
//! #[serde(default, skip_serializing_if = "Option::is_none")]
//! surname: Option<String>,
//! vehicle: Vehicle,
//! }
//!
//! #[derive(Serialize, Deserialize)]
//! #[serde(rename_all = "SCREAMING_SNAKE_CASE", deny_unknown_fields)]
//! enum Vehicle {
//! Car,
//! Bicycle,
//! ElectricScooter,
//! }
//! ```
//!
//! Note, that if you want to use your applier across a whole project, you need to place [`create_config!`]
//! before any `mod` directives for modules using the applier.
//! This is because the `#[macro_export]` term generated by the other macro is not recognized early enough.
//!
//! Also note this may confuse the rust analyzer too. To overcome this, place your configs in a separate file, f.ex.
//! `serde_configs.rs` and import it before any usage with `#[macro_use] mod serde_configs;`.
//!
//! ## Supported parameters
//!
//! * **`rename_all: Option<String>`**
//!
//! A general rename rule for both structs and enums.
//! Can be overridden by `struct_rename_all` or `enum_rename_all`.
//!
//! * **`struct_rename_all: Option<String>`**
//!
//! A specific rename rule for struct fields.
//!
//! * **`enum_rename_all: Option<String>`**
//!
//! A specific rename rule for enum variants.
//!
//! * **`skip_if_none: bool`**
//!
//! If present, adds `#[serde(default, skip_serializing_if = "Option::is_none")]`
//! to any field of type `Option<T>`.
//!
//! * **`deny_unknown_fields: bool`**
//!
//! If present, adds `#[serde(deny_unknown_fields)]` to the container.
//!
//! ## Manual Usage of `configure_serde` macro
//!
//! ```rust
//! use configurable_serde::configure_serde;
//! use serde::{Serialize, Deserialize};
//!
//! #[configure_serde(rename_all = "camelCase", skip_if_none)]
//! #[derive(Serialize, Deserialize, Debug, PartialEq)]
//! struct User {
//! user_id: String,
//! display_name: Option<String>,
//! }
//!
//! // The struct will be serialized with camelCase fields, and `display_name`
//! // will be omitted if it is `None`.
//!
//! let user = User { user_id: "u-123".to_string(), display_name: None };
//! let json = serde_json::to_string(&user).unwrap();
//!
//! assert_eq!(json, r#"{"userId":"u-123"}"#);
//! ```
//!
//! ## Reusing Configurations without `create_config`
//!
//! You can define your own applier manually. It is just a declarative macro (`macro_rules!`) that applies the
//! [`#[configure_serde]`](configure_serde) attribute with your desired settings.
//!
//! ### Reusable configuration which wraps one item
//!
//! ```rust
//! use configurable_serde::configure_serde;
//! use serde::{Serialize, Deserialize};
//!
//! /// Defines a reusable configuration named `apply_api_config`:
//! macro_rules! apply_api_config {
//! ($item:item) => {
//! #[configure_serde(
//! struct_rename_all = "camelCase",
//! enum_rename_all = "SCREAMING_SNAKE_CASE",
//! skip_if_none,
//! deny_unknown_fields
//! )]
//! $item
//! };
//! }
//!
//! // Now, apply this configuration to a struct.
//! apply_api_config! {
//! #[derive(Serialize, Deserialize, Debug, PartialEq)]
//! pub struct Product {
//! product_id: String,
//! stock_count: Option<u32>,
//! }
//! }
//!
//! // And to an enum.
//! apply_api_config! {
//! #[derive(Serialize, Deserialize, Debug, PartialEq)]
//! pub enum Status {
//! InStock,
//! Backordered,
//! Discontinued,
//! }
//! }
//!
//! // Test the struct serialization
//! let product = Product {
//! product_id: "prod-456".to_string(),
//! stock_count: Some(50)
//! };
//!
//! let product_json = serde_json::to_string(&product).unwrap();
//! assert_eq!(product_json, r#"{"productId":"prod-456","stockCount":50}"#);
//!
//! // Test the enum serialization
//! let status = Status::InStock;
//!
//! let status_json = serde_json::to_string(&status).unwrap();
//! assert_eq!(status_json, r#""IN_STOCK""#);
//! ```
//!
//! ### Reusable configuration which wraps multiple items
//!
//! This is the same as what you get using the [`create_config!`] macro, but it let's you to place the definition
//! in any place in your crate (that is: `#[macro_export]` works).
//!
//! ```rust
//! #[macro_export]
//! macro_rules! my_api_models {
//! ($($item:item)*) => {
//! $(
//! #[configurable_serde::configure_serde(
//! struct_rename_all = "camelCase",
//! enum_rename_all = "SCREAMING_SNAKE_CASE",
//! skip_if_none
//! )]
//! $item
//! )*
//! };
//! }
//! ```
/// Apply config manually to struct or enum.
pub use configure_serde;
/// Generate `configure_serde` applier to provide config to all wrapped items.
pub use create_config;