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 support for setter prefix and suffix #94

Merged
merged 9 commits into from
Jun 19, 2023
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
generated code itself does trigger the deprecation warning, and instead the
setter for that field now does.

### Added
- Support for setter method prefixes and suffixes `#[builder(field_defaults(setter(prefix = "...", suffix = "...")))]`.
This either prepends or appends the provided string to the setter method. This allows method names like: `set_x()`,
`with_y()`, or `set_z_value()`.

## 0.14.0 - 2023-03-08
### Added
- `build_method(into)` and `build_method(into = ...)`.
Expand Down
6 changes: 3 additions & 3 deletions examples/complicate_build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod scope {
use typed_builder::TypedBuilder;

#[derive(PartialEq, TypedBuilder)]
#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(build_method(vis="", name=__build))]
pub struct Foo {
// Mandatory Field:
Expand Down Expand Up @@ -36,7 +36,7 @@ mod scope {
}
}

#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
pub struct Bar {
pub x: i32,
pub y: Option<i32>,
Expand All @@ -47,7 +47,7 @@ mod scope {
use scope::{Bar, Foo};

fn main() {
assert!(Foo::builder().x(1).y(2).z(3).build() == Bar { x: 2, y: Some(3), z: 4 });
assert_eq!(Foo::builder().x(1).y(2).z(3).build(), Bar { x: 2, y: Some(3), z: 4 });

// This will not compile - because `__build` is a private method
// Foo::builder().x(1).y(2).z(3).__build()
Expand Down
8 changes: 4 additions & 4 deletions examples/example.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use typed_builder::TypedBuilder;

#[derive(PartialEq, TypedBuilder)]
#[derive(Debug, PartialEq, TypedBuilder)]
struct Foo {
// Mandatory Field:
x: i32,
Expand All @@ -16,13 +16,13 @@ struct Foo {
}

fn main() {
assert!(Foo::builder().x(1).y(2).z(3).build() == Foo { x: 1, y: Some(2), z: 3 });
assert_eq!(Foo::builder().x(1).y(2).z(3).build(), Foo { x: 1, y: Some(2), z: 3 });

// Change the order of construction:
assert!(Foo::builder().z(1).x(2).y(3).build() == Foo { x: 2, y: Some(3), z: 1 });
assert_eq!(Foo::builder().z(1).x(2).y(3).build(), Foo { x: 2, y: Some(3), z: 1 });

// Optional fields are optional:
assert!(Foo::builder().x(1).build() == Foo { x: 1, y: None, z: 20 });
assert_eq!(Foo::builder().x(1).build(), Foo { x: 1, y: None, z: 20 });

// This will not compile - because we did not set x:
// Foo::builder().build();
Expand Down
39 changes: 39 additions & 0 deletions examples/example_prefix_suffix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use typed_builder::TypedBuilder;

#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(field_defaults(setter(prefix = "with_", suffix = "_value")))]
struct Foo {
// Mandatory Field:
x: i32,

// #[builder(default)] without parameter - use the type's default
// #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)`
#[builder(default, setter(strip_option))]
y: Option<i32>,

// Or you can set the default
#[builder(default = 20)]
z: i32,
}

fn main() {
assert_eq!(
Foo::builder().with_x_value(1).with_y_value(2).with_z_value(3).build(),
Foo { x: 1, y: Some(2), z: 3 }
);

// Change the order of construction:
assert_eq!(
Foo::builder().with_z_value(1).with_x_value(2).with_y_value(3).build(),
Foo { x: 2, y: Some(3), z: 1 }
);

// Optional fields are optional:
assert_eq!(Foo::builder().with_x_value(1).build(), Foo { x: 1, y: None, z: 20 });

// This will not compile - because we did not set x:
// Foo::builder().build();

// This will not compile - because we set y twice:
// Foo::builder().with_x_value(1).with_y_value(2).with_y_value(3);
}
10 changes: 9 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
/// // Foo::builder().x(1).y(2).y(3);
/// ```
///
/// # Customisation with attributes
/// # Customization with attributes
///
/// In addition to putting `#[derive(TypedBuilder)]` on a type, you can specify a `#[builder(…)]`
/// attribute on the type, and on any fields in it.
Expand Down Expand Up @@ -148,6 +148,14 @@
/// `param1: Type1, param2: Type2 ...` instead of the field type itself. The parameters are
/// transformed into the field type using the expression `expr`. The transformation is performed
/// when the setter is called.
///
/// - `prefix = "..."` prepends the setter method with the specified prefix. For example, setting
/// `prefix = "with_"` results in setters like `with_x` or `with_y`. This option is combinable
/// with `suffix = "..."`.
///
/// - `suffix = "..."` appends the setter method with the specified suffix. For example, setting
/// `suffix = "_value"` results in setters like `x_value` or `y_value`. This option is combinable
/// with `prefix = "..."`.
pub use typed_builder_macro::TypedBuilder;

#[doc(hidden)]
Expand Down
65 changes: 65 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,32 @@ fn test_builder_on_struct_with_keywords() {
);
}

#[test]
fn test_builder_on_struct_with_keywords_prefix_suffix() {
#[allow(non_camel_case_types)]
#[derive(PartialEq, TypedBuilder)]
#[builder(field_defaults(setter(prefix = "set_", suffix = "_value")))]
struct r#struct {
r#fn: u32,
#[builder(default, setter(strip_option))]
r#type: Option<u32>,
#[builder(default = Some(()), setter(skip))]
r#enum: Option<()>,
#[builder(setter(into))]
r#union: String,
}

assert!(
r#struct::builder().r#set_fn_value(1).r#set_union_value("two").build()
== r#struct {
r#fn: 1,
r#type: None,
r#enum: Some(()),
r#union: "two".to_owned(),
}
);
}

#[test]
fn test_field_setter_transform() {
#[derive(PartialEq)]
Expand Down Expand Up @@ -671,3 +697,42 @@ fn test_into_set_generic_impl_into() {
let bar: Bar = Foo::builder().value(42).build();
assert_eq!(bar, Bar { value: 42 });
}

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

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

#[test]
fn test_suffix() {
#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(field_defaults(setter(suffix = "_value")))]
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 })
}

#[test]
fn test_prefix_and_suffix() {
#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(field_defaults(setter(prefix = "with_", suffix = "_value")))]
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 })
}
30 changes: 28 additions & 2 deletions typed-builder-macro/src/field_info.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use proc_macro2::{Span, TokenStream};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{parse::Error, spanned::Spanned};

use crate::util::{apply_subsections, expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix};
use crate::util::{
apply_subsections, expr_to_lit_string, expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix,
};

#[derive(Debug)]
pub struct FieldInfo<'a> {
Expand Down Expand Up @@ -73,6 +75,20 @@ impl<'a> FieldInfo<'a> {
}
}

pub fn setter_method_name(&self) -> Ident {
let name = strip_raw_ident_prefix(self.name.to_string());

if let (Some(prefix), Some(suffix)) = (&self.builder_attr.setter.prefix, &self.builder_attr.setter.suffix) {
Ident::new(&format!("{}{}{}", prefix, name, suffix), Span::call_site())
} else if let Some(prefix) = &self.builder_attr.setter.prefix {
Ident::new(&format!("{}{}", prefix, name), Span::call_site())
} else if let Some(suffix) = &self.builder_attr.setter.suffix {
Ident::new(&format!("{}{}", name, suffix), Span::call_site())
} else {
self.name.clone()
}
}

fn post_process(mut self) -> Result<Self, Error> {
if let Some(ref strip_bool_span) = self.builder_attr.setter.strip_bool {
if let Some(default_span) = self.builder_attr.default.as_ref().map(Spanned::span) {
Expand Down Expand Up @@ -110,6 +126,8 @@ pub struct SetterSettings {
pub strip_option: Option<Span>,
pub strip_bool: Option<Span>,
pub transform: Option<Transform>,
pub prefix: Option<String>,
pub suffix: Option<String>,
}

impl<'a> FieldBuilderAttr<'a> {
Expand Down Expand Up @@ -286,6 +304,14 @@ impl SetterSettings {
self.transform = Some(parse_transform_closure(assign.left.span(), *assign.right)?);
Ok(())
}
"prefix" => {
self.prefix = Some(expr_to_lit_string(&*assign.right)?);
Ok(())
}
"suffix" => {
self.suffix = Some(expr_to_lit_string(&*assign.right)?);
Ok(())
}
_ => Err(Error::new_spanned(&assign, format!("Unknown parameter {:?}", name))),
}
}
Expand Down
8 changes: 5 additions & 3 deletions typed-builder-macro/src/struct_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ impl<'a> StructInfo<'a> {
let reconstructing = self.included_fields().map(|f| f.name);

let &FieldInfo {
name: ref field_name,
name: field_name,
ty: field_type,
..
} = field;
Expand Down Expand Up @@ -262,12 +262,14 @@ impl<'a> StructInfo<'a> {
);
let repeated_fields_error_message = format!("Repeated field {}", field_name);

let method_name = field.setter_method_name();

Ok(quote! {
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
#deprecated
#doc
pub fn #field_name (self, #param_list) -> #builder_name <#( #target_generics ),*> {
pub fn #method_name (self, #param_list) -> #builder_name <#( #target_generics ),*> {
let #field_name = (#arg_expr,);
let ( #(#descructuring,)* ) = self.fields;
#builder_name {
Expand All @@ -285,7 +287,7 @@ impl<'a> StructInfo<'a> {
#[deprecated(
note = #repeated_fields_error_message
)]
pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name <#( #target_generics ),*> {
pub fn #method_name (self, _: #repeated_fields_error_type_name) -> #builder_name <#( #target_generics ),*> {
self
}
}
Expand Down
12 changes: 11 additions & 1 deletion typed-builder-macro/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use quote::ToTokens;
use syn::parse::Parser;
use syn::{parse::Parser, Error};

pub fn path_to_single_string(path: &syn::Path) -> Option<String> {
if path.leading_colon.is_some() {
Expand Down Expand Up @@ -115,3 +115,13 @@ pub fn apply_subsections(

Ok(())
}

pub fn expr_to_lit_string(expr: &syn::Expr) -> Result<String, Error> {
match expr {
syn::Expr::Lit(lit) => match &lit.lit {
syn::Lit::Str(str) => Ok(str.value()),
_ => return Err(Error::new_spanned(expr, "attribute only allows str values")),
},
_ => return Err(Error::new_spanned(expr, "attribute only allows str values")),
}
}
Loading