Expand description
§Configurable Serde
A crate providing a procedural macro to apply reusable serde configurations.
The core of this crate is the #[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]
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
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:
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
orenum_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 typeOption<T>
. -
deny_unknown_fields: bool
If present, adds
#[serde(deny_unknown_fields)]
to the container.
§Manual Usage of configure_serde
macro
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]
attribute with your desired settings.
§Reusable configuration which wraps one item
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).
#[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
)*
};
}
Macros§
- create_
config - Generate
configure_serde
applier to provide config to all wrapped items.
Attribute Macros§
- configure_
serde - Apply config manually to struct or enum.