builder-pattern

creates.io API Docs

Build Status (Windows) Build Status (MacOS) Build Status (Ubuntu) Build Status (WebAssembly)

A derivable macro for declaring a builder pattern. This crate is highly inspired by derive_builder.

Usage

```rust use builder_pattern::Builder;

[derive(Builder)]

struct Person { #[into] name: String, age: i32, #[default(Gender::Nonbinary)] gender: Gender, }

let p1 = Person::new() // PersonBuilder<(), (), ()> .name(String::from("Joe")) // PersonBuilder

// Orders does not matter. let p2 = Person::new() // PersonBuilder<(), (), ()> .age(32) // PersonBuilder<(), i32, ()> // &str is implicitly converted into String // because of into attribute! .name("Jack") // PersonBuilder .build(); // Person

// name field required - Compilation error. let p3 = Person::new() // PersonBuilder<(), (), ()> .age(15) // PersonBuilder<(), i32, ()> .build(); ```

Get Started

Add builder-pattern to Cargo.toml.

```toml

Cargo.toml

[dependencies] builder-pattern = "0.3" ```

Features

Attributes

#[default(expr)]

A field having this attribute will be considered as optional and the expr will be evaluated as a default value of the field. build function can be called without providing this field.

#[hidden]

If this attribute is present, the builder function would not be generated for the field. This field requires default attribute.

Example:

```rust

[derive(Builder)]

struct Test { #[default(Uuid::new_v4())] #[hidden] id: Uuid, name: String, }

let test1 = Test::new() // TestBuilder<(), ()> .name(String::from("Joe")) // TestBuilder

let test2 = Test::new() // TestBuilder<(), ()> .name(String::from("Jack")) // TestBuilderid function is not generated. .id(Uuid::parse_str("46ebd0ee-0e6d-43c9-b90d-ccc35a913f3e").unwrap()) .build(); ```

#[into]

A setter function for a field having this attribute will accept an Into trait as a parameter. You can use this setter with implicit conversion.

Example:

```rust

[derive(Builder)]

struct Test { #[into] pub name: String, }

let test = Test::new() // TestBuilder<()> // &str is implicitly converted into String. .name("Hello") // TestBuilder .build(); // Test ```

#[validator(expr)]

Implement a validator for a field. expr could be a validating function that takes the field's type and returns Result.

```rust

[derive(Builder)]

struct Test { #[validator(isnotempty)] #[into] pub name: String, }

fn isnotempty(name: String) -> Result { if name.is_empty() { Err("Name cannot be empty.") } else { Ok(name) } }

let test1 = Test::new() // TestBuilder<()> .name("Hello") // Ok(TestBuilder) .unwrap() // TestBuilder .build(); // Test

let test2 = Test::new() // TestBuilder<()> .name("") // Err(String{ "Validation failed: Name cannot be empty." }) .unwrap() // panic! .build(); ```

Auto-Generated Documentions

This crate generates documentations for the builder functions. If you documentate the fields, the builder functions for them also copy the documentations.

Example

Example code:

```rust

[derive(Builder)]

struct Test { /// A positive integer. pub positive: i32,

/// A integer having zero as a default value.
#[default(0)]
pub zero: i32,

} ```

Generated code:

``rust impl Test { /// Creating a builder. /// ## Required fields /// ###positive /// - Type:i32 /// /// A positive integer. /// /// ## Optional fields /// ###zero /// - Type:i32 /// - Default:0` /// /// A integer having zero as a default value. fn new() -> TestBuilder<(), ()> { TestBuilder { _phatom: PhantomData, positive: None, zero: Some(0), } } }

/// A builder for Test. struct TestBuilder { _phatom: PhantomData<(T1, T2)>, positive: Option, zero: Option, }

impl TestBuilder { fn build(self) -> Test { Test { positive: self.positive.unwrap(), zero: self.zero.unwrap(), } } }

impl TestBuilder<(), T2> { /// # positive /// - Type: i32 /// /// A positive integer. pub fn positive(self, value: i32) -> TestBuilder { TestBuilder { _phatom: PhantomData, positive: Some(value), zero: self.zero, } } }

impl TestBuilderi32 /// - Default: 0 /// /// A integer having zero as a default value. pub fn zero(self, value: i32) -> TestBuilder { TestBuilder { _phatom: PhantomData, positive: self.positive, zero: Some(value), } } } ```

How it works

The following code

```rust

[derive(Builder)]

struct Person { #[into] #[validator(isnotempty)] name: String, age: i32, #[default(Gender::Nonbinary)] gender: Gender, }

```

will generates:

```rust struct PersonBuilder { name: Option, age: Option, gender: Option, _phantom: PhantomData<(T1, T2, T3)> }

impl Person { // Create an empty builder fn new() -> PersonBuilder<(), (), ()> { PersonBuilder { name: None, age: None, // Default value gender: Some(Gender::Nonbinary), _phantom: PhantomData } } }

// Builder for name. impl PersonBuilder<(), T2, T3> { // Receives Into traits. fn name>(self, value: IntoType) -> Result, String> { // Validation check. match isnotempty(value.into()) { Ok(value) => Ok(PersonBuilder { // Converts IntoType into String. name: Some(value.into()), age: self.age, gender: self.gender, _phantom: PhantomData, }), Err(e) => Err(format!("Validation failed: {:?}", e)) } } }

// Builder for age. impl PersonBuilder { fn age(self, value: i32) -> PersonBuilder { PersonBuilder { name: self.name, age: Some(value), gender: self.gender, _phantom: PhantomData, } } }

// Builder for gender. impl PersonBuilder PersonBuilder { PersonBuilder { name: self.name, age: self.age, gender: Some(value), _phantom: PhantomData, } } }

// build function // It can be called regardless of whether T3 is () or Gender. impl PersonBuilder { fn build(self) -> Person { Person { name: self.name.unwrap(), age: self.age.unwrap(), gender: self.gender.unwrap(), } } } ```

License

MIT