Compare commits

..

No commits in common. "e7fd8ec7e1f67a0a977f3584fd8a46ea4a7fa54f" and "360ef63b5883764004d665383f9ab657f1492cc8" have entirely different histories.

4 changed files with 13 additions and 167 deletions

View File

@ -11,11 +11,11 @@ use web_sys::FormData;
/// ///
/// This can be useful to use the same validation logic on the front /// This can be useful to use the same validation logic on the front
/// end and backend without duplicating the logic. /// end and backend without duplicating the logic.
pub struct FormValidator<FD> { pub struct Validator<FD> {
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>, pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
} }
impl<FD: FormToolData> FormValidator<FD> { impl<FD: FormToolData> Validator<FD> {
/// Validates the given form data. /// Validates the given form data.
/// ///
/// This runs all the validation functions for all the fields /// This runs all the validation functions for all the fields
@ -40,8 +40,8 @@ pub struct Form<FD: FormToolData> {
impl<FD: FormToolData> Form<FD> { impl<FD: FormToolData> Form<FD> {
/// Gets the [`Validator`] for this form. /// Gets the [`Validator`] for this form.
pub fn validator(self) -> FormValidator<FD> { pub fn validator(self) -> Validator<FD> {
FormValidator { Validator {
validations: self.validations, validations: self.validations,
} }
} }
@ -57,10 +57,10 @@ impl<FD: FormToolData> Form<FD> {
} }
/// Splits this [`Form`] into it's parts. /// Splits this [`Form`] into it's parts.
pub fn to_parts(self) -> (RwSignal<FD>, FormValidator<FD>, View) { pub fn to_parts(self) -> (RwSignal<FD>, Validator<FD>, View) {
( (
self.fd, self.fd,
FormValidator { Validator {
validations: self.validations, validations: self.validations,
}, },
self.view, self.view,
@ -131,7 +131,7 @@ pub trait FormToolData: Default + Clone + 'static {
/// ///
/// However, the code to render the views are not configured out, it /// However, the code to render the views are not configured out, it
/// simply doesn't run, so the view needs to compile even on the server. /// simply doesn't run, so the view needs to compile even on the server.
fn get_validator() -> FormValidator<Self> { fn get_validator() -> Validator<Self> {
let builder = FormBuilder::new(Self::default(), Self::Style::default()); let builder = FormBuilder::new(Self::default(), Self::Style::default());
let builder = Self::build_form(builder); let builder = Self::build_form(builder);
builder.validator() builder.validator()

View File

@ -4,7 +4,7 @@ use crate::{
FieldSetter, ParseFn, RenderFn, UnparseFn, ValidationCb, ValidationFn, FieldSetter, ParseFn, RenderFn, UnparseFn, ValidationCb, ValidationFn,
VanityControlBuilder, VanityControlData, VanityControlBuilder, VanityControlData,
}, },
form::{Form, FormToolData, FormValidator}, form::{Form, FormToolData, Validator},
styles::FormStyle, styles::FormStyle,
}; };
use leptos::{ use leptos::{
@ -305,8 +305,8 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
} }
} }
pub(crate) fn validator(&self) -> FormValidator<FD> { pub(crate) fn validator(&self) -> Validator<FD> {
FormValidator { Validator {
validations: self.validations.clone(), validations: self.validations.clone(),
} }
} }

View File

@ -1,9 +1,7 @@
pub mod controls; pub mod controls;
mod form; pub mod form;
mod form_builder; pub mod form_builder;
pub mod styles; pub mod styles;
mod validation_builder;
pub use form::{Form, FormToolData, FormValidator}; pub use form::{Form, FormToolData, Validator};
pub use form_builder::FormBuilder; pub use form_builder::FormBuilder;
pub use validation_builder::ValidationBuilder;

View File

@ -1,152 +0,0 @@
use crate::{controls::ValidationFn, FormToolData};
use std::fmt::Display;
/// A helper builder that allows you to specify a validation function
/// declaritivly
///
/// Using this builder is not required as validation functions can just be
/// closures, but for simple validation function this builder can be helpful
///
/// Validations are run in the order that they are called in the builder.
pub struct ValidationBuilder<FD: FormToolData, T: 'static> {
/// The name of the field, for error messages.
name: String,
/// The getter function for the field to validate.
field_fn: Box<dyn Fn(&FD) -> &T + 'static>,
/// The functions to be called when validating.
functions: Vec<Box<dyn Fn(&str, &T) -> Result<(), String> + 'static>>,
}
impl<FD: FormToolData, T: 'static> ValidationBuilder<FD, T> {
/// Creates a new empty [`ValidationBuilder`] on the given field.
pub fn for_field(field_fn: impl Fn(&FD) -> &T + 'static) -> Self {
ValidationBuilder {
name: String::from("Field"),
field_fn: Box::new(field_fn),
functions: Vec::new(),
}
}
/// The name of the field that is being validated.
///
/// This is the name that will be used for error messages.
pub fn named(mut self, name: impl ToString) -> Self {
self.name = name.to_string();
self
}
/// Adds a custom validation function.
///
/// The function should take the value as an argument and return
/// a [`Result<(), String>`], just like any other validation function.
pub fn custom(mut self, f: impl ValidationFn<T>) -> Self {
self.functions.push(Box::new(move |_name, value| f(value)));
self
}
/// Builds the action validation function.
pub fn build(self) -> impl ValidationFn<FD> {
move |form_data| {
let value = (self.field_fn)(form_data);
for f in self.functions.iter() {
match f(self.name.as_str(), value) {
Ok(()) => {}
err => return err,
}
}
Ok(())
}
}
}
impl<FD: FormToolData> ValidationBuilder<FD, String> {
/// Requires the field to not be empty.
pub fn required(mut self) -> Self {
self.functions.push(Box::new(move |name, value| {
if value.is_empty() {
Err(format!("{} is required", name))
} else {
Ok(())
}
}));
self
}
/// Requires the field's length to be at least `min_len`.
pub fn min_len(mut self, min_len: usize) -> Self {
self.functions.push(Box::new(move |name, value| {
if value.len() < min_len {
Err(format!("{} must be >= {} characters", name, min_len))
} else {
Ok(())
}
}));
self
}
/// Requires the field's length to be less than or equal to `min_len`.
pub fn max_len(mut self, max_len: usize) -> Self {
self.functions.push(Box::new(move |name, value| {
if value.len() > max_len {
Err(format!("{} must be <= {} characters", name, max_len))
} else {
Ok(())
}
}));
self
}
}
impl<FD: FormToolData, T: PartialOrd<T> + Display + 'static> ValidationBuilder<FD, T> {
/// Requires the value to be at least `min_value` according to
/// `PartialOrd`.
pub fn min_value(mut self, min_value: T) -> Self {
self.functions.push(Box::new(move |name, value| {
if value < &min_value {
Err(format!("{} mut be >= {}", name, min_value))
} else {
Ok(())
}
}));
self
}
/// Requires the value to be at most `max_value` according to
/// `PartialOrd`.
pub fn max_value(mut self, max_value: T) -> Self {
self.functions.push(Box::new(move |name, value| {
if value > &max_value {
Err(format!("{} mut be <= {}", name, max_value))
} else {
Ok(())
}
}));
self
}
}
impl<FD: FormToolData, T: PartialEq<T> + Display + 'static> ValidationBuilder<FD, T> {
/// Requires the field to be in the provided whitelist.
pub fn whitelist(mut self, whitelist: Vec<T>) -> Self {
self.functions.push(Box::new(move |name, value| {
if !whitelist.contains(value) {
Err(format!("{} cannot be {}", name, value))
} else {
Ok(())
}
}));
self
}
/// Requires the field to not be in the provided blacklist.
pub fn blacklist(mut self, blacklist: Vec<T>) -> Self {
self.functions.push(Box::new(move |name, value| {
if blacklist.contains(value) {
Err(format!("{} cannot be {}", name, value))
} else {
Ok(())
}
}));
self
}
}