-
Notifications
You must be signed in to change notification settings - Fork 90
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
Add try_setter option and generate appropriate methods #72
Changes from 1 commit
fe38226
f0772df
4cec554
85f4d5a
0b59e93
a56e870
6d497bc
24d7eb4
79491a5
fd7deeb
79e4474
0600c05
1510419
22f49f0
ec3cc06
12daa64
c629cbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
//! This example illustrates the use of try-setters. | ||
#![feature(try_from)] | ||
|
||
#[macro_use] | ||
extern crate derive_builder; | ||
|
||
use std::convert::{From, TryFrom}; | ||
use std::net::{IpAddr, AddrParseError}; | ||
use std::str::FromStr; | ||
use std::string::ToString; | ||
|
||
/// Temporary newtype hack around lack of TryFrom implementations | ||
/// in std. The rust-lang issue on the subject says that there will be a | ||
/// blanket impl for everything that currently implements FromStr, which | ||
/// will make this feature much more useful for input validation. | ||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct MyAddr(IpAddr); | ||
|
||
impl From<IpAddr> for MyAddr { | ||
fn from(v: IpAddr) -> Self { | ||
MyAddr(v) | ||
} | ||
} | ||
|
||
impl<'a> TryFrom<&'a str> for MyAddr { | ||
type Err = AddrParseError; | ||
|
||
fn try_from(v: &str) -> Result<Self, AddrParseError> { | ||
Ok(MyAddr(IpAddr::from_str(v)?)) | ||
} | ||
} | ||
|
||
#[derive(Builder, Debug, PartialEq)] | ||
#[builder(try_setter, setter(into))] | ||
struct Lorem { | ||
pub name: String, | ||
pub addr: MyAddr, | ||
} | ||
|
||
fn main() { | ||
create("Jane", "1.2.3.4").unwrap(); | ||
create("Bobby", "").unwrap_err(); | ||
} | ||
|
||
fn create(name: &str, addr: &str) -> Result<Lorem, String> { | ||
// Fallible and infallible setters can be mixed freely when using | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @colin-kiegel can you please try running There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me guess, does the comment appear at random places in the generated output? :-) I noticed very strange distributions of comments before. I could imagine that this is caused by the syn crate, which we use to parse the input into an ast. But didn't investigate it, because it didn't cause any trouble. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to consistently get moved into the |
||
// the mutable builder pattern. | ||
LoremBuilder::default() | ||
.name(name) | ||
.try_addr(addr).map_err(|e| e.to_string())? | ||
.build() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -216,6 +216,31 @@ | |
//! } | ||
//! ``` | ||
//! | ||
//! ## Fallible Setters | ||
//! | ||
//! Alongside the normal setter methods, you can expose fallible setters which are generic over | ||
//! the `TryInto` trait. TryInto is a not-yet-stable trait | ||
//! (see rust-lang issue [#33417](https://github.com/rust-lang/rust/issues/33417)) similar to | ||
//! `Into` with the key distinction that the conversion can fail, and therefore produces a `Result`. | ||
//! | ||
//! You can declare the `try_setter` attribute today, and your builder will work on stable Rust - | ||
//! the fallible setters will automatically light up in future when the `try_from` feature is | ||
//! stabilized. | ||
//! | ||
//! | ||
//! ```rust,ignore | ||
//! # #[macro_use] | ||
//! # extern crate derive_builder; | ||
//! # | ||
//! #[derive(Builder, Debug, PartialEq)] | ||
//! #[builder(try_setter, setter(into))] | ||
//! struct Lorem { | ||
//! pub ipsum: u8, | ||
//! } | ||
//! ``` | ||
//! | ||
//! A complete example of using fallible setters is available in `examples/try_setter.rs`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a note that the name and visibility are inherited from the You could change the example into something like this, to better explain it? //! # #[macro_use]
//! # extern crate derive_builder;
//! #
//! #[derive(Builder, Debug, PartialEq)]
//! struct Lorem {
//! #[builder(try_setter, setter(name="foo"))]
//! pub ipsum: u8,
//! }
|
||
//! | ||
//! ## Default Values | ||
//! | ||
//! You can define default values for each field via annotation by `#[builder(default="...")]`, | ||
|
@@ -366,6 +391,9 @@ | |
//! - If derive_builder depends on your crate, and vice versa, then a cyclic | ||
//! dependency would occur. To break it you could try to depend on the | ||
//! [`derive_builder_core`] crate instead. | ||
//! - The `try_setter` attribute and `owned` builder pattern are not compatible in practice; | ||
//! an error during building will consume the builder, making it impossible to continue | ||
//! construction. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice catch :-) |
||
//! | ||
//! ## Debugging Info | ||
//! | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -131,9 +131,9 @@ impl<'a> ToTokens for Setter<'a> { | |
#vis fn #try_ident #try_ty_params (#self_param, value: VALUE) | ||
-> #result<#return_ty, VALUE::Err> | ||
{ | ||
let value : #ty = value.try_into()?; | ||
let converted : #ty = value.try_into()?; | ||
let mut new = #self_into_return_ty; | ||
new.#field_ident = #option::Some(value); | ||
new.#field_ident = #option::Some(converted); | ||
Ok(new) | ||
})); | ||
} else { | ||
|
@@ -244,7 +244,7 @@ mod tests { | |
)); | ||
} | ||
|
||
// including | ||
// including try_setter | ||
#[test] | ||
fn full() { | ||
let attrs = vec![syn::parse_outer_attr("#[some_attr]").unwrap()]; | ||
|
@@ -256,14 +256,36 @@ mod tests { | |
setter.attrs = attrs.as_slice(); | ||
setter.generic_into = true; | ||
setter.deprecation_notes = &deprecated; | ||
setter.try_setter = true; | ||
|
||
// Had to hoist these out to avoid a recursion limit in the quote! | ||
// macro invocation below. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, problem. You can just add |
||
let action = quote!( | ||
let mut new = self; | ||
new.foo = ::std::option::Option::Some(value.into()); | ||
new | ||
); | ||
|
||
let try_action = quote!( | ||
let converted : Foo = value.try_into()?; | ||
let mut new = self; | ||
new.foo = ::std::option::Option::Some(converted); | ||
Ok(new) | ||
); | ||
|
||
println!("{}", quote!(#setter)); | ||
|
||
assert_eq!(quote!(#setter), quote!( | ||
#[some_attr] | ||
pub fn foo <VALUE: ::std::convert::Into<Foo>>(&mut self, value: VALUE) -> &mut Self { | ||
#deprecated | ||
let mut new = self; | ||
new.foo = ::std::option::Option::Some(value.into()); | ||
new | ||
#action | ||
} | ||
|
||
#[some_attr] | ||
pub fn try_foo<VALUE: ::std::convert::TryInto<Foo>>(&mut self, value: VALUE) | ||
-> ::std::result::Result<&mut Self, VALUE::Err> { | ||
#try_action | ||
} | ||
)); | ||
} | ||
|
@@ -305,4 +327,36 @@ mod tests { | |
|
||
assert_eq!(quote!(#setter), quote!()); | ||
} | ||
|
||
#[test] | ||
fn try_setter() { | ||
let mut setter : Setter = default_setter!(); | ||
setter.pattern = BuilderPattern::Mutable; | ||
setter.try_setter = true; | ||
|
||
let setter_quote = quote!( | ||
pub fn foo(&mut self, value: Foo) -> &mut Self { | ||
let mut new = self; | ||
new.foo = ::std::option::Option::Some(value); | ||
new | ||
} | ||
); | ||
|
||
// hoisting necessary to avoid recursion limit. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
let try_setter_body = quote!( | ||
let converted : Foo = value.try_into()?; | ||
let mut new = self; | ||
new.foo = ::std::option::Option::Some(converted); | ||
Ok(new) | ||
); | ||
|
||
assert_eq!(quote!(#setter), quote!( | ||
#setter_quote | ||
|
||
pub fn try_foo<VALUE: ::std::convert::TryInto<Foo>>(&mut self, value: VALUE) | ||
-> ::std::result::Result<&mut Self, VALUE::Err> { | ||
#try_setter_body | ||
} | ||
)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
#![cfg_attr(feature = "try_setter", feature(try_from))] | ||
#![cfg_attr(feature = "nightlytests", feature(try_from))] | ||
|
||
#[macro_use] | ||
extern crate derive_builder; | ||
|
||
#[allow(unused_imports)] | ||
mod struct_level { | ||
#[cfg(feature = "try_setter")] | ||
#[cfg(feature = "try_from")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, gating on Alternatively also gate on |
||
use std::convert::TryFrom; | ||
|
||
use std::net::{IpAddr, AddrParseError}; | ||
|
@@ -21,7 +21,7 @@ mod struct_level { | |
} | ||
} | ||
|
||
#[cfg(feature = "try_setter")] | ||
#[cfg(feature = "nightlytests")] | ||
impl<'a> TryFrom<&'a str> for MyAddr { | ||
type Err = AddrParseError; | ||
|
||
|
@@ -39,11 +39,14 @@ mod struct_level { | |
|
||
#[test] | ||
fn infallible_set() { | ||
let _ = LoremBuilder::default().source(IpAddr::from_str("1.2.3.4").unwrap()).build(); | ||
let _ = LoremBuilder::default() | ||
.source(IpAddr::from_str("1.2.3.4").unwrap()) | ||
.dest(IpAddr::from_str("0.0.0.0").unwrap()) | ||
.build(); | ||
} | ||
|
||
#[test] | ||
#[cfg(feature = "try_setter")] | ||
#[cfg(feature = "nightlytests")] | ||
fn fallible_set() { | ||
let mut builder = LoremBuilder::default(); | ||
let try_result = builder.try_source("1.2.3.4"); | ||
|
@@ -55,15 +58,15 @@ mod struct_level { | |
} | ||
|
||
// Allow dead code here since the test that uses this depends on the try_setter feature. | ||
#[cfg_attr(not(feature = "try_setter"), allow(dead_code))] | ||
#[cfg_attr(not(feature = "nightlytests"), allow(dead_code))] | ||
fn exact_helper() -> Result<Lorem, String> { | ||
LoremBuilder::default() | ||
.source(IpAddr::from_str("1.2.3.4").unwrap()) | ||
.dest(IpAddr::from_str("0.0.0.0").unwrap()) | ||
.build() | ||
} | ||
|
||
#[cfg(feature = "try_setter")] | ||
#[cfg(feature = "nightlytests")] | ||
fn try_helper() -> Result<Lorem, String> { | ||
LoremBuilder::default() | ||
.try_source("1.2.3.4").map_err(|e| e.to_string())? | ||
|
@@ -72,7 +75,7 @@ mod struct_level { | |
} | ||
|
||
#[test] | ||
#[cfg(feature = "try_setter")] | ||
#[cfg(feature = "nightlytests")] | ||
fn with_helper() { | ||
assert_eq!(exact_helper().unwrap(), try_helper().unwrap()); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice summary! 👍