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

feat: Add PostBuild trait #95

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
29 changes: 29 additions & 0 deletions examples/postbuild.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use typed_builder::{PostBuild, TypedBuilder};

#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(postbuild)]
struct Foo {
x: i32,
y: i32,
}

impl PostBuild for Foo {
type Output = Result<Self, String>;

fn postbuild(self) -> Self::Output {
if self.x >= 5 {
return Err("x too high - must be below or 5".into());
}

Ok(self)
}
}

fn main() {
let foo = Foo::builder().x(1).y(2).build().unwrap();
assert_eq!(foo, Foo { x: 1, y: 2 });

// Fails to validate during runtime
// let foo = Foo::builder().x(5).y(6).build().unwrap();
// assert_eq!(foo, Foo { x: 5, y: 6 });
}
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ impl<T> Optional<T> for (T,) {
}
}

pub trait PostBuild {
type Output;

fn postbuild(self) -> Self::Output;
}

// It'd be nice for the compilation tests to live in tests/ with the rest, but short of pulling in
// some other test runner for that purpose (e.g. compiletest_rs), rustdoc compile_fail in this
// crate is all we can use.
Expand Down
196 changes: 123 additions & 73 deletions tests/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![warn(clippy::pedantic)]

use typed_builder::TypedBuilder;
use typed_builder::{PostBuild, TypedBuilder};

#[test]
fn test_simple() {
Expand Down Expand Up @@ -624,115 +624,165 @@ fn test_builder_type() {
assert!(builder.x(1).build() == Foo { x: 1 });
}

#[test]
fn test_default_builder_type() {
#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(builder_method(vis = ""), builder_type(name = InnerBuilder), build_method(into = Outer))]
struct Inner {
a: i32,
b: i32,
}

#[derive(Debug, PartialEq)]
struct Outer(Inner);

impl Outer {
pub fn builder() -> InnerBuilder {
Inner::builder()
}
}

impl From<Inner> for Outer {
fn from(value: Inner) -> Self {
Self(value)
}
}

let outer = Outer::builder().a(3).b(5).build();
assert_eq!(outer, Outer(Inner { a: 3, b: 5 }));
}
// #[test]
// fn test_default_builder_type() {
// #[derive(Debug, PartialEq, TypedBuilder)]
// #[builder(builder_method(vis = ""), builder_type(name = InnerBuilder), build_method(into = Outer))]
// struct Inner {
// a: i32,
// b: i32,
// }

// #[derive(Debug, PartialEq)]
// struct Outer(Inner);

// impl Outer {
// pub fn builder() -> InnerBuilder {
// Inner::builder()
// }
// }

// impl From<Inner> for Outer {
// fn from(value: Inner) -> Self {
// Self(value)
// }
// }

// let outer = Outer::builder().a(3).b(5).build();
// assert_eq!(outer, Outer(Inner { a: 3, b: 5 }));
// }

// #[test]
// fn test_into_set_generic_impl_from() {
// #[derive(TypedBuilder)]
// #[builder(build_method(into))]
// struct Foo {
// value: i32,
// }

// #[derive(Debug, PartialEq)]
// struct Bar {
// value: i32,
// }

// impl From<Foo> for Bar {
// fn from(value: Foo) -> Self {
// Self { value: value.value }
// }
// }

// let bar: Bar = Foo::builder().value(42).build();
// assert_eq!(bar, Bar { value: 42 });
// }

// #[test]
// fn test_into_set_generic_impl_into() {
// #[derive(TypedBuilder)]
// #[builder(build_method(into))]
// struct Foo {
// value: i32,
// }

// #[derive(Debug, PartialEq)]
// struct Bar {
// value: i32,
// }

// impl From<Foo> for Bar {
// fn from(val: Foo) -> Self {
// Self { value: val.value }
// }
// }

// let bar: Bar = Foo::builder().value(42).build();
// assert_eq!(bar, Bar { value: 42 });
// }

#[test]
fn test_into_set_generic_impl_from() {
#[derive(TypedBuilder)]
#[builder(build_method(into))]
fn test_prefix() {
#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(field_defaults(setter(prefix = "with_")))]
struct Foo {
value: i32,
}

#[derive(Debug, PartialEq)]
struct Bar {
value: i32,
}

impl From<Foo> for Bar {
fn from(value: Foo) -> Self {
Self { value: value.value }
}
x: i32,
y: i32,
}

let bar: Bar = Foo::builder().value(42).build();
assert_eq!(bar, Bar { value: 42 });
let foo = Foo::builder().with_x(1).with_y(2).build();
assert_eq!(foo, Foo { x: 1, y: 2 })
}

#[test]
fn test_into_set_generic_impl_into() {
#[derive(TypedBuilder)]
#[builder(build_method(into))]
fn test_suffix() {
#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(field_defaults(setter(suffix = "_value")))]
struct Foo {
value: i32,
}

#[derive(Debug, PartialEq)]
struct Bar {
value: i32,
}

impl From<Foo> for Bar {
fn from(val: Foo) -> Self {
Self { value: val.value }
}
x: i32,
y: i32,
}

let bar: Bar = Foo::builder().value(42).build();
assert_eq!(bar, Bar { value: 42 });
let foo = Foo::builder().x_value(1).y_value(2).build();
assert_eq!(foo, Foo { x: 1, y: 2 })
}

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

let foo = Foo::builder().with_x(1).with_y(2).build();
let foo = Foo::builder().with_x_value(1).with_y_value(2).build();
assert_eq!(foo, Foo { x: 1, y: 2 })
}

#[test]
fn test_suffix() {
fn test_postbuild_valid() {
#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(field_defaults(setter(suffix = "_value")))]
#[builder(postbuild)]
struct Foo {
x: i32,
y: i32,
}

let foo = Foo::builder().x_value(1).y_value(2).build();
assert_eq!(foo, Foo { x: 1, y: 2 })
impl PostBuild for Foo {
type Output = Result<Self, String>;

fn postbuild(self) -> Self::Output {
if self.x >= 5 {
return Err("x too high - must be below or 5".into());
}

Ok(self)
}
}

let foo = Foo::builder().x(1).y(2).build().unwrap();
assert_eq!(foo, Foo { x: 1, y: 2 });
}

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

let foo = Foo::builder().with_x_value(1).with_y_value(2).build();
assert_eq!(foo, Foo { x: 1, y: 2 })
impl PostBuild for Foo {
type Output = Result<Self, String>;

fn postbuild(self) -> Self::Output {
if self.x >= 5 {
return Err("x too high - must be below or 5".into());
}

Ok(self)
}
}

let foo = Foo::builder().x(5).y(6).build();
assert_eq!(foo, Err("x too high - must be below or 5".into()));
}
2 changes: 2 additions & 0 deletions typed-builder-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
.filter(|f| f.builder_attr.default.is_none())
.map(|f| struct_info.required_field_impl(f));
let build_method = struct_info.build_method_impl();
let postbuild_impl = struct_info.postbuild_trait_impl();

quote! {
#builder_creation
#fields
#(#required_fields)*
#build_method
#postbuild_impl
}
}
syn::Fields::Unnamed(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for tuple structs")),
Expand Down
53 changes: 45 additions & 8 deletions typed-builder-macro/src/struct_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,18 +501,47 @@ impl<'a> StructInfo<'a> {
impl #impl_generics #builder_name #modified_ty_generics #where_clause {
#build_method_doc
#[allow(clippy::default_trait_access)]
#build_method_visibility fn #build_method_name #build_method_generic (self) -> #output_type #build_method_where_clause {
#build_method_visibility fn #build_method_name #build_method_generic (self) -> <#output_type as typed_builder::PostBuild>::Output #build_method_where_clause {
let ( #(#descructuring,)* ) = self.fields;
#( #assignments )*

#[allow(deprecated)]
#name {
typed_builder::PostBuild::postbuild(#name {
#( #field_names ),*
}.into()
}).into()
}
}
)
}

pub fn postbuild_trait_impl(&self) -> TokenStream {
let StructInfo { name, .. } = &self;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();

// TODO (Techassi): Move into method / function
let (_, output_type, _) = match &self.builder_attr.build_method.into {
IntoSetting::NoConversion => (None, quote!(#name #ty_generics), None),
IntoSetting::GenericConversion => (
Some(quote!(<__R>)),
quote!(__R),
Some(quote!(where #name #ty_generics: Into<__R>)),
),
IntoSetting::TypeConversionToSpecificType(into) => (None, into.to_token_stream(), None),
};

if !self.builder_attr.custom_postbuild {
return quote!(
impl #impl_generics typed_builder::PostBuild for #output_type #where_clause {
type Output = #output_type;

fn postbuild(self) -> Self::Output {
self
}
}
);
}

quote!()
}
}

#[derive(Debug, Default, Clone)]
Expand Down Expand Up @@ -639,6 +668,10 @@ pub struct TypeBuilderAttr<'a> {
pub build_method: BuildMethodSettings,

pub field_defaults: FieldBuilderAttr<'a>,

/// Wether to generate the default PostBuild trait implementation or use a
/// custom user-provided one.
pub custom_postbuild: bool,
}

impl<'a> TypeBuilderAttr<'a> {
Expand Down Expand Up @@ -673,7 +706,7 @@ impl<'a> TypeBuilderAttr<'a> {
let name =
expr_to_single_string(&assign.left).ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;

let gen_structure_depracation_error = |put_under: &str, new_name: &str| {
let gen_structure_deprecation_error = |put_under: &str, new_name: &str| {
Error::new_spanned(
&assign.left,
format!(
Expand All @@ -683,9 +716,9 @@ impl<'a> TypeBuilderAttr<'a> {
)
};
match name.as_str() {
"builder_method_doc" => Err(gen_structure_depracation_error("builder_method", "doc")),
"builder_type_doc" => Err(gen_structure_depracation_error("builder_type", "doc")),
"build_method_doc" => Err(gen_structure_depracation_error("build_method", "doc")),
"builder_method_doc" => Err(gen_structure_deprecation_error("builder_method", "doc")),
"builder_type_doc" => Err(gen_structure_deprecation_error("builder_type", "doc")),
"build_method_doc" => Err(gen_structure_deprecation_error("build_method", "doc")),
_ => Err(Error::new_spanned(&assign, format!("Unknown parameter {:?}", name))),
}
}
Expand All @@ -696,6 +729,10 @@ impl<'a> TypeBuilderAttr<'a> {
self.doc = true;
Ok(())
}
"postbuild" => {
self.custom_postbuild = true;
Ok(())
}
_ => Err(Error::new_spanned(&path, format!("Unknown parameter {:?}", name))),
}
}
Expand Down