Crate configurable_serde

Crate configurable_serde 

Source
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 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

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.