Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support deriving builder for enums #129

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 149 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,44 @@ use core::ops::FnOnce;
/// Struct::builder().x(2).x_into_b().x_into_b().x_into_b_field().inc_a(2).build(),
/// Struct {x: 2, a: 3, b: vec![2, 2, 2]});
/// ```
///
/// # Enum
/// `TypedBuilder` also supports deriving builder of enums.
/// By default (inconfigurable though), `TypedBuilder` generates builder methods with
/// the name of "snake_case" of variant names.
/// For example, the builder of `Foo::BarQux` is created by `Foo::bar_qux()`.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's common for enums to use these methods for returning an Option that's Some iff self's discriminant matches the method name (like Result::ok and Result::err from the standard library). Using the same name for the builder will conflict with these methods.

Wouldn't it be better to use Foo::bar_qux_builder()?

/// Note that it does not support enums with generics or lifetime.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? I mean, obviously you'd have to specify (if they cannot be inferred) the generics and lifetimes that are only relevant for the variants not chosen, but that is no different from regular Rust enums.

///
/// You can specify `#[builder(...)]` on enums, on variants, and on any fields.
/// But since `TypedBuilder` internally generates a builder struct each enum variant,
/// some subsections are prohibited; you cannot specify `builder_method(name=...)`,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could be better worded? From the examples I understand that builder_method(name = ...), for example, goes on the variant and not on the top level type declaration, but from this sentence it seems like it's not allowed at all when using TypedBuilder on enums.

/// `builder_type(name=...)`, `build_method(into=...)` on enums.
/// ```
/// use typed_builder::TypedBuilder;
///
/// #[derive(PartialEq, TypedBuilder)]
/// #[builder(field_defaults(setter(into)))]
/// enum Foo {
/// Bar {
/// x: i32,
/// },
/// // builder method is `.baz_qux()`, "snake_case" of variant names
/// #[builder(field_defaults(setter(prefix = "with_")))]
/// BazQux {
/// #[builder(default, setter(strip_option))]
/// y: Option<i32>,
/// z: String,
/// },
/// }
///
/// assert!(
/// Foo::bar().x(1).build()
/// == Foo::Bar { x: 1 });
///
/// assert!(
/// Foo::baz_qux().with_y(1).with_z("bar").build()
/// == Foo::BazQux { y: Some(1), z: "bar".to_owned() });
/// ```
pub use typed_builder_macro::TypedBuilder;

#[doc(hidden)]
Expand Down Expand Up @@ -347,4 +385,114 @@ impl<T> Optional<T> for (T,) {
/// #[deny(deprecated)]
/// Foo::builder().value(42).build();
///```
fn _compile_fail_tests() {}
fn _struct_compile_fail_tests() {}

#[doc(hidden)]
/// Enum with generics:
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// enum Foo<T> {
/// Bar { value: T },
/// }
/// ```
///
/// Enum with lifetime:
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// enum Foo<'a> {
/// Bar { value: &'a i32 },
/// }
/// ```
///
/// Enum with builder_method(...):
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// #[builder(builder_method(name=builder))]
/// enum Foo {
/// Bar { value: i32 },
/// }
/// ```
///
/// But for variant:
///
/// ```
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// enum Foo {
/// #[builder(builder_method(name=builder))]
/// Bar { value: i32 },
/// }
///
/// let _ = Foo::builder().value(0).build();
/// ```
///
/// Enum with builder_type(...):
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// #[builder(builder_type(name=FooBuilder))]
/// enum Foo {
/// Bar { value: i32 },
/// }
/// ```
///
/// But for variant:
///
/// ```
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// enum Foo {
/// #[builder(builder_type(name=CustomBuilder))]
/// Bar { value: i32 },
/// }
///
/// let _: CustomBuilder = Foo::bar();
/// ```
///
/// Enum with build_method(into):
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// #[builder(build_method(into))]
/// enum Foo {
/// Bar { value: i32 },
/// }
/// ```
///
/// Enum with tuple variants:
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// enum Foo {
/// Bar(i32),
/// }
/// ```
///
/// Enum with unit variants:
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// enum Foo {
/// Bar,
/// }
/// ```
fn _enum_compile_fail_tests() {}
181 changes: 181 additions & 0 deletions tests/enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use typed_builder::TypedBuilder;

#[test]
fn test_simple() {
#[derive(PartialEq, Debug, TypedBuilder)]
enum Foo {
Bar { x: i32 },
Baz { y: i32, z: String },
}

assert_eq!(Foo::bar().x(1).build(), Foo::Bar { x: 1 });
assert_eq!(
Foo::baz().y(2).z("z".to_owned()).build(),
Foo::Baz { y: 2, z: "z".to_owned() }
);
}

#[test]
fn test_into() {
#[derive(PartialEq, Debug, TypedBuilder)]
#[builder(field_defaults(setter(into)))]
enum Foo {
Bar {
x: i32,
},
Baz {
#[builder(setter(!into))]
y: u32,
z: String,
},
}

assert_eq!(Foo::bar().x(1_u8).build(), Foo::Bar { x: 1 });
assert_eq!(Foo::baz().y(2).z("z").build(), Foo::Baz { y: 2, z: "z".to_owned() });
}

#[test]
fn test_default() {
#[derive(PartialEq, Debug, TypedBuilder)]
enum Foo {
#[builder(field_defaults(default = 1))]
Bar {
#[builder(default = 2)]
x: i32,
y: i32,
},
Baz {
#[builder(default = Some(3), setter(strip_option))]
y: Option<i32>,
#[builder(default = vec![1,2,3], setter(into))]
z: Vec<i32>,
},
}

assert_eq!(Foo::bar().build(), Foo::Bar { x: 2, y: 1 });
assert_eq!(Foo::bar().x(3).y(4).build(), Foo::Bar { x: 3, y: 4 });
assert_eq!(
Foo::baz().build(),
Foo::Baz {
y: Some(3),
z: vec![1, 2, 3]
}
);
assert_eq!(
Foo::baz().y(5).z([6, 7, 8]).build(),
Foo::Baz {
y: Some(5),
z: vec![6, 7, 8]
}
);
}

#[test]
fn test_skip() {
#[derive(PartialEq, Debug, TypedBuilder)]
enum Foo {
Bar {
#[builder(default, setter(skip))]
x: i32,
},
Baz {
#[builder(setter(strip_option))]
y: Option<i32>,
#[builder(default = y.into_iter().collect(), setter(skip))]
z: Vec<i32>,
},
}

assert_eq!(Foo::bar().build(), Foo::Bar { x: 0 });
assert_eq!(Foo::baz().y(1).build(), Foo::Baz { y: Some(1), z: vec![1] });
}

#[test]
fn test_build_method_name() {
#[derive(PartialEq, Debug, TypedBuilder)]
#[builder(doc, build_method(vis="", name=__build), field_defaults(default))]
pub enum Foo {
Bar { x: i32 },
Baz { y: i32 },
}

assert_eq!(Foo::bar().x(1).__build(), Foo::Bar { x: 1 });
assert_eq!(Foo::baz().__build(), Foo::Baz { y: 0 });
}

#[test]
fn test_prefix_and_suffix() {
#[derive(PartialEq, Debug, TypedBuilder)]
#[builder(field_defaults(setter(prefix = "with_")))]
enum Foo {
Bar {
x: i32,
},
#[builder(field_defaults(setter(suffix = "_value")))]
Baz {
y: i32,
#[builder(setter(prefix = ""))]
z: i32,
},
}

assert_eq!(Foo::bar().with_x(1).build(), Foo::Bar { x: 1 });
assert_eq!(Foo::baz().with_y_value(2).z_value(3).build(), Foo::Baz { y: 2, z: 3 });
}

#[test]
fn test_builder_method() {
#[derive(PartialEq, Debug, TypedBuilder)]
enum Foo {
BarBaz {
x: i32,
},
QuxHTTPQuux {
y: i32,
},
#[builder(builder_method(name = custom_builder))]
Custom {
z: i32,
},
}

assert_eq!(Foo::bar_baz().x(1).build(), Foo::BarBaz { x: 1 });
assert_eq!(Foo::qux_http_quux().y(2).build(), Foo::QuxHTTPQuux { y: 2 });
assert_eq!(Foo::custom_builder().z(3).build(), Foo::Custom { z: 3 });
}

#[test]
fn test_builder_type_visibility() {
mod foo {
use typed_builder::TypedBuilder;

#[derive(PartialEq, Debug, TypedBuilder)]

Check warning on line 152 in tests/enum.rs

View workflow job for this annotation

GitHub Actions / Tests (ubuntu-latest, 1.70.0)

private type `foo::Foo` in public interface (error E0446)
enum Foo {
#[builder(builder_type(vis="pub", name=CustomBuilder))]
Bar { x: i32 },
}

pub fn foo_bar_builder() -> CustomBuilder {
Foo::bar()
}

pub fn build_and_get_x(builder: CustomBuilder, x: i32) -> i32 {
let Foo::Bar { x } = builder.x(x).build();
x
}
}

let builder: foo::CustomBuilder = foo::foo_bar_builder();
assert_eq!(foo::build_and_get_x(builder, 1), 1);
}

#[test]
fn test_builder_on_enum_with_keywords() {
#[allow(non_camel_case_types)]
#[derive(PartialEq, Debug, TypedBuilder)]
enum r#enum {
Bar { r#type: i32 },
}

assert_eq!(r#enum::bar().r#type(1).build(), r#enum::Bar { r#type: 1 });
}
File renamed without changes.
1 change: 1 addition & 0 deletions typed-builder-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ proc-macro = true
syn = { version = "2", features = ["full", "extra-traits"] }
quote = "1"
proc-macro2 = "1"
convert_case = "0.6.0"
Loading
Loading