https://github.com/seokminhong/builder-pattern
A derivable macro for declaring a builder pattern.
https://github.com/seokminhong/builder-pattern
builder-pattern macro rust
Last synced: 11 months ago
JSON representation
A derivable macro for declaring a builder pattern.
- Host: GitHub
- URL: https://github.com/seokminhong/builder-pattern
- Owner: SeokminHong
- License: mit
- Created: 2021-08-16T17:02:23.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2023-12-21T06:05:33.000Z (over 2 years ago)
- Last Synced: 2025-04-18T19:41:33.735Z (11 months ago)
- Topics: builder-pattern, macro, rust
- Language: Rust
- Homepage: https://crates.io/crates/builder-pattern
- Size: 194 KB
- Stars: 29
- Watchers: 1
- Forks: 3
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# builder-pattern
[](https://crates.io/crates/builder-pattern)
[](https://docs.rs/builder-pattern/)
[](https://github.com/SeokminHong/builder-pattern/actions/workflows/build_windows.yml)
[](https://github.com/SeokminHong/builder-pattern/actions/workflows/build_macos.yml)
[](https://github.com/SeokminHong/builder-pattern/actions/workflows/build_ubuntu.yml)
[](https://github.com/SeokminHong/builder-pattern/actions/workflows/build_wasm.yml)
A derivable macro for declaring a builder pattern. This crate is highly inspired by [derive_builder](https://github.com/colin-kiegel/rust-derive-builder).
## Usage
```rust
use builder_pattern::Builder;
#[derive(Builder)]
struct Person {
#[into]
name: String,
age: i32,
#[default(Gender::Nonbinary)]
#[setter(value, async)]
gender: Gender,
}
let p1 = Person::new() // PersonBuilder<(), (), (), ...>
.name(String::from("Joe")) // PersonBuilder
.age(27) // PersonBuilder
.build(); // Person
// Order 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
.gender(Gender::Male) // PersonBuilder
.build(); // Person
let p3 = Person::new() // PersonBuilder<(), (), (), ...>
.age(32) // PersonBuilder<(), i32, (), ...>
// `&str` is implicitly converted into `String`
// because of `into` attribute!
.name("Jack") // PersonBuilder
.gender_async(|| async {
Gender::Male
}) // PersonBuilder
.build() // Future
.await; // Person
// `name` field required - Compilation error.
let p4 = Person::new() // PersonBuilder<(), (), (), ...>
.age(15) // PersonBuilder<(), i32, (), ...>
.build();
```
## Get Started
Add `builder-pattern` to `Cargo.toml`.
```toml
# Cargo.toml
[dependencies]
builder-pattern = "0.4"
```
The crate feature `future` is enabled by default. If you don't need asynchronous features, you can disable it.
```toml
# Cargo.toml
[dependencies]
builder-pattern = { version = "0.4", default-features = false }
```
## Features
- **Chaining**: Can make structure with chained setters.
- **Complex types are supported**: Lifetime, trait bounds, and where clauses are well supported.
- **Type safety**: Autocompletion tools can suggest correct setters to build the struct. Also, `build` function is allowed only the all of required fields are provided. **No Result**, **No Unwrap**. Just use it.
- **Lazy evaluation and asynchronous**: Lazy evaluation and asynchronous are supported. The values will be evaluated when the structure is built.
- **No additional tasks**: There's no additional constraints to use the macro. Any structures and fields are allowed.
- **Auto-generated documentation**: Documentation for the builder functions are automatically generated.
## 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.
```rust
#[derive(Builder)]
struct Test {
#[default]
pub a: i32,
pub b: &'static str,
}
let t1 = Test::new().b("Hello").build(); // The structure can be built without `a`.
let t2 = Test::new().b("Hi").a(3).build();
```
### `#[default_lazy(expr)]`
A field having this attribute will be considered as optional, and the `expr` will be lazily evaluated as a default value of the field. `expr` should be a function or a closure having no arguments.
```rust
#[derive(Builder)]
struct Test {
#[default_lazy(|| some_heavy_task() + 3)]
pub a: i32,
#[default_lazy(some_heavy_task)]
pub b: i32,
}
let t1 = Test::new().build(); // The structure can be built without `a` and `b`.
let t2 = Test::new().a(3).build();
```
### `#[hidden]`
If this attribute is present, the builder function would not be generated for the field. This field requires `default` or `default_lazy` 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
.build(); // Test
let test2 = Test::new() // TestBuilder<(), (), ...>
.name(String::from("Jack")) // TestBuilder
// Error: `id` function is not generated.
.id(Uuid::parse_str("46ebd0ee-0e6d-43c9-b90d-ccc35a913f3e").unwrap())
.build();
```
### `#[public]`
If this attribute is present, a field would be exposed with setter functions even though the
field is private. It provides a way to access private fields during the building.
Example:
```rust
mod test {
#[derive(Builder)]
pub struct Test {
#[public]
id: Uuid,
pub name: &'static str,
}
}
use test::Test;
let test1 = Test::new() // TestBuilder<(), (), ...>
.id(Uuid::new_v4()) // TestBuilder
.name("Joe") // TestBuilder
.build(); // Test
assert_eq!(test1.name, "Joe");
println!("{}", test1.id); // Error: `id` is a private field.
```
### `#[setter(value | lazy | async)]`
If this attribute presents, it provides specified setters. If it doesn't, only the value setter is provided.
```rust
#[derive(Builder, Debug)]
struct Person {
// All kinds of setters are provided.
#[setter(value, lazy, async)]
name: String,
// Only value setter is provided.
age: u8,
// Only lazy setter is provided.
#[setter(lazy)]
address: &'static str,
}
let p1 = Person::new()
.name_async(|| async { String::from("Joe") })
.age(15)
.address_lazy(|| "123 Main St")
.build() // `address` is validated here
.await; // `name` is validated here
```
### `#[into]`
A setter function for a field having this attribute will accept `Into` trait as a parameter. You can use this setter with implicit conversion.
Example:
```rust
#[derive(Builder)]
struct Test {
#[into]
#[setter(value, lazy)]
pub name: String,
}
let test1 = Test::new() // TestBuilder<(), ...>
// `&str` is implicitly converted into `String`.
.name("Hello") // TestBuilder
.build(); //
let test2 = Test::new() // TestBuilder<(), ...>
// `&str` is implicitly converted into `String`.
.name_lazy(|| "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(is_not_empty)]
#[into]
pub name: String,
}
fn is_not_empty(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();
```
If the validator is used with lazy or async setters, it will also validated lazily or asynchronously. So, the setter doesn't return `Result` but it is returned when it is built.
```rust
#[derive(Builder)]
struct Test {
#[validator(is_not_empty)]
#[setter(value, lazy, async)]
pub name: &'static str,
}
let test1 = Test::new() // TestBuilder<(), ...>
.name_lazy(|| "Hello") // TestBuilder
.build() // Ok(Test)
.unwrap(); // Test
let test2 = Test::new() // TestBuilder<(), ...>
.name_async(|| async {
"Hello".to_string()
}) // TestBuilder
.build() // Future TestBuilder<(), ()> {
TestBuilder {
_phantom: PhantomData,
positive: None,
zero: Some(0),
}
}
}
/// A builder for `Test`.
struct TestBuilder {
_phantom: 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 {
_phantom: PhantomData,
positive: Some(Setter::Value(value)),
zero: self.zero,
}
}
}
impl TestBuilder {
/// # zero
/// - Type: `i32`
/// - Default: `0`
///
/// An integer having zero as a default value.
pub fn zero(self, value: i32) -> TestBuilder {
TestBuilder {
_phantom: PhantomData,
positive: self.positive,
zero: Some(Setter::Value(value)),
}
}
}
```
## How it works
The following code
```rust
#[derive(Builder)]
struct Person {
#[into]
#[validator(is_not_empty)]
name: String,
age: i32,
#[default(Gender::Nonbinary)]
gender: Gender,
}
```
will generates:
```rust
impl Person {
// Create an empty builder
fn new<'a>() -> PersonBuilder<'a, (), (), (), (), ()> {
PersonBuilder {
_phantom: PhantomData,
age: None,
name: None,
gender: Some(Setter::Value(Gender::Nonbinary)),
}
}
}
// A builder structure for `Person`.
struct PersonBuilder<
'a, T1, T2, T3,
AsyncFieldMarker, // A generic for checking async fields
ValidatorOption, // A generic for checking lazy validators
> {
_phantom: PhantomData<(
T1, T2, T3,
AsyncFieldMarker,
ValidatorOption,
)>,
// Fields are wrapped in `Option`s.
age: Option>,
name: Option>,
gender: Option>,
}
// Implementation for `build` function
impl<'a, T3>
// It can be called regardless of whether `T3` is `()` or `Gender`.
PersonBuilder<'a, i32, String, T3, (), ()>
{
fn build(self) -> Person {
let age = match self.age.unwrap() {
Setter::Value(v) => v,
Setter::Lazy(f) => f(),
_ => unimplemented!(),
};
let name = match self.name.unwrap() {
Setter::Value(v) => v,
Setter::Lazy(f) => f(),
_ => unimplemented!(),
};
let gender = match self.gender.unwrap() {
Setter::Value(v) => v,
Setter::Lazy(f) => f(),
_ => unimplemented!(),
};
Person { age, name, gender }
}
}
impl<'a, T2, T3, AsyncFieldMarker, ValidatorOption>
PersonBuilder<
'a, (), T2, T3,
AsyncFieldMarker,
ValidatorOption,
>
{
// Setter for `age`
fn age(
self,
value: i32,
) -> PersonBuilder<
'a, i32, T2, T3,
AsyncFieldMarker,
ValidatorOption,
> {
PersonBuilder {
_phantom: PhantomData,
age: Some(Setter::Value(value.into())),
name: self.name,
gender: self.gender,
}
}
}
impl<'a, T1, T3, AsyncFieldMarker, ValidatorOption>
PersonBuilder<
'a, T1, (), T3,
AsyncFieldMarker,
ValidatorOption,
>
{
// Setter for `name`
fn name>(
self,
value: IntoType,
) -> Result<
PersonBuilder<
'a, T1, String, T3,
AsyncFieldMarker,
ValidatorOption,
>,
String,
> {
// Validate the value
match is_not_empty(value.into()) {
Ok(value) => Ok(PersonBuilder {
_phantom: PhantomData,
age: self.age,
name: Some(Setter::Value(value)),
gender: self.gender,
}),
Err(e) => Err(format!("Validation failed: {:?}", e)),
}
}
}
impl<'a, T1, T2, AsyncFieldMarker, ValidatorOption>
PersonBuilder<
'a, T1, T2, (),
AsyncFieldMarker,
ValidatorOption,
>
{
// Setter for `gender`
fn gender(
self,
value: Gender,
) -> PersonBuilder<
'a, T1, T2, Gender,
AsyncFieldMarker,
ValidatorOption,
> {
PersonBuilder {
_phantom: PhantomData,
age: self.age,
name: self.name,
gender: Some(Setter::Value(value.into())),
}
}
}
```
## License
[MIT](./LICENSE)