-
-
Notifications
You must be signed in to change notification settings - Fork 19
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 input validation in builders #34
Comments
Hi, thank you for opening the issue! I can see this within the scope of Right now, with the current state of use anyhow::{Context, Error};
use bon::bon;
use uuid::Uuid;
struct User {
name: String,
id: Uuid,
group_id: Uuid,
}
#[bon]
impl User {
#[builder]
fn new(name: String, id: &str, group_id: &str) -> Result<Self, Vec<Error>> {
let mut errors = vec![];
if name.contains("@#!$") {
errors.push(anyhow::anyhow!("Name contains invalid characters: {name}"));
}
let id = Uuid::parse_str(id)
.with_context(|| format!("Invalid UUID: {id}"))
.map_err(|err| errors.push(err));
let group_id = Uuid::parse_str(group_id)
.with_context(|| format!("Invalid UUID: {group_id}"))
.map_err(|err| errors.push(err));
if !errors.is_empty() {
return Err(errors);
}
Ok(Self {
name,
// Unwraps are meh.. but not critical. `errors.is_empty()` check ensures no panics here
id: id.unwrap(),
group_id: group_id.unwrap(),
})
}
}
let result = User::builder()
.name("username")
.id("a1da0850-03dc-4f53-8e54-e609b28d17e8")
.group_id("fbb1efc2-bd9b-425f-a97d-b7ffc6430a1b")
.build();
if let Err(errors) = result {
// handle errors
} Did you consider this possibility with |
This would be a great example to have in the documentation. I was looking for exactly the kind of thing you're showing here where the builder can succeed or fail to build, and couldn't find it, and then luckily saw it here in this issue. |
Makes sense, I'll add it to the docs together with some other common patterns with the coming release. |
I added an example to the docs at the "Fallible builders" page. There are some other patterns described in the "Patterns" section. I recommend you to check them out. See the 2.0 release blog post for details. |
What are possible options of validating inputs regarding ability to reference already set fields? |
I'm not sure I understand the full context of this question. Currently |
I refer to this text
If to introduce |
Hey guys, I've created an attribute #[derive(bon::Builder)]
struct Example {
#[builder(with = |string: &str| -> Result<_, std::num::ParseIntError> {
string.parse()
})]
level: u32,
#[builder(with = |name: String| -> anyhow::Result<_> {
if name.len() > 10 {
return Err(anyhow::anyhow!("name is too long: {name}"));
}
Ok(name)
})]
name: String,
}
Example::builder()
.level("10")?
.name("hello".to_string())?
.build();
Example::builder()
.level("10")?
// Setter that accepts `Option<String>` is also fallible
.maybe_name(Some("hello".to_string()))?
.build(); Of course you don't have to write all the validation logic inside of an attribute. You can write it in a separate function and just invoke that one inside of the closure. Unfortunatelly, this syntax requires a closure. This is needed because the builder macro needs to see the signature of the function (input types and the type of the result/error). As a last resort it's possible to define custom methods (including custom setters) on the builder struct itself. An example of that is in #145 PR description, but I'll add extensive documentation about that soon. It's not released yet, but this feature is already in master (merged via #145). I'll release it as part of cc all the people who put a like under the issue: @MeGaGiGaGon, @yeswalrus, @Songtronix, @dzmitry-lahoda, @elefant-dev, @wilzbach, @dsully, @ferreira-tb and other who commented (they get a notification automatically). |
Hello and thank you for writing this lib!
I'm wondering if extending this lib with validation would be in scope. What I'm imagining is an interface where I can specify input validators (e.g.
string -> uuid
). The builder would then run them all and collect the errors. The type of the build method would befn build() -> Result<T, Vec<E>>
or something like this.It is a common scenario in a lot of software to write types that have some guaranteed invariants, but are built from some basic wire types. Validating all of the fields manually is a lot of boilerplate. I've attempted to do a PoC of this in my crate called valibuk, but I don't have the capacity to properly maintain an OSS project.
A note for the community from the maintainers
Please vote on this issue by adding a 👍 reaction to help the maintainers with prioritizing it. You may add a comment describing your real use case related to this issue for us to better understand the problem domain.
The text was updated successfully, but these errors were encountered: