#builder #enums #macro #reuse #shorthand #proc-macro

macro variant-builder-macro

This crate gives us the VariantBuider proc macro which can be used to streamline creting an enum from wrapping variants each using the builder pattern

2 unstable releases

new 0.2.0 Dec 6, 2024
0.1.0 Dec 6, 2024

#22 in #shorthand

Download history 143/week @ 2024-12-01

143 downloads per month

MIT license

11KB
69 lines

variant_builder_macro

A procedural macro to simplify the creation of builder methods for enum variants, especially when used with the derive_builder crate. This macro automates the generation of variant-specific builder methods, enabling concise and flexible creation of enum instances.


Features

  • Automated Builder Methods: Automatically generates builder methods for each variant in an enum.
  • Integration with derive_builder: Works seamlessly with the derive_builder crate to manage complex field initialization.
  • Default Variant Support: Automatically implements the Default trait for enums with a designated #[default] variant.
  • Improved Type Safety: Ensures that only valid builder configurations are allowed.
  • Simplified API: Reduces boilerplate by eliminating the need to manually write repetitive constructor methods.

Installation

Add variant_builder_macro to your Cargo.toml:

[dependencies]
variant_builder_macro = "0.1"
derive_builder = "0.10"  # Required for field builders

Usage

Basic Example

Define an enum and apply the VariantBuilder macro. Each variant must have a single unnamed field with a type that implements Default and has a builder (e.g., generated by derive_builder). Use the #[default] attribute to specify a default variant.

use variant_builder_macro::VariantBuilder;
use derive_builder::Builder;

#[derive(VariantBuilder, Debug, Clone, PartialEq)]
pub enum AnimalVenom {
    #[default]
    Contact(ContactVenom),
    Injected(InjectedVenom),
    Defensive(DefensiveVenom),
    Offensive(OffensiveVenom),
}

#[derive(Builder, Debug, Clone, PartialEq, Default)]
#[builder(setter(into))]
pub struct ContactVenom {
    potency: String,
    activation: String,
}

#[derive(Builder, Debug, Clone, PartialEq, Default)]
#[builder(setter(into))]
pub struct InjectedVenom {
    delivery_method: String,
    potency: String,
}

#[derive(Builder, Debug, Clone, PartialEq, Default)]
#[builder(setter(into))]
pub struct DefensiveVenom {
    deterrence: String,
    effectiveness: u32,
}

#[derive(Builder, Debug, Clone, PartialEq, Default)]
#[builder(setter(into))]
pub struct OffensiveVenom {
    speed: u32,
    potency: String,
}

Generated API

The macro generates builder methods for each variant. For example, for the Contact variant:

impl AnimalVenom {
    pub fn contact<F>(build: F) -> Self
    where
        F: FnOnce(&mut ContactVenomBuilder),
    {
        let mut builder = ContactVenomBuilder::default();
        build(&mut builder);
        Self::Contact(builder.build().expect("Builder failed to construct variant"))
    }
}

Additionally, if a #[default] attribute is specified, the Default trait is implemented automatically:

impl Default for AnimalVenom {
    fn default() -> Self {
        Self::Contact(Default::default())
    }
}

Examples

Creating Enum Variants with Builders

fn main() {
    let contact_venom = AnimalVenom::contact(|builder| {
        builder
            .potency("High".to_string())
            .activation("Immediate".to_string());
    });

    let injected_venom = AnimalVenom::injected(|builder| {
        builder
            .delivery_method("Spines".to_string())
            .potency("Moderate".to_string());
    });

    println!("{:?}", contact_venom);
    println!("{:?}", injected_venom);
}

Using Default Values

If no fields are modified, the builder uses default values. The default variant is used for Default::default():

let default_contact_venom = AnimalVenom::default();
println!("{:?}", default_contact_venom);

You can also explicitly call the default variant builder:

let default_contact_venom = AnimalVenom::contact(|_builder| {});
println!("{:?}", default_contact_venom);

Partial Field Initialization

You can initialize only the fields you need:

let defensive_venom = AnimalVenom::defensive(|builder| {
    builder.deterrence("High".to_string());
});
println!("{:?}", defensive_venom);

Testing

This crate includes a comprehensive test suite. Run the tests with:

cargo test

Limitations

  • Each variant must have exactly one unnamed field.
  • The field type must implement Default and provide a build method (e.g., via derive_builder).
  • Only one variant can be marked with #[default].

License

This crate is licensed under the MIT License. See LICENSE for details.


Contributions

Contributions are welcome! If you find any issues or have ideas for new features, feel free to open an issue or submit a pull request.

Dependencies

~0.8–1.3MB
~27K SLoC