generated from mitchell/rust_template
Compare commits
2 Commits
360ef63b58
...
e7fd8ec7e1
| Author | SHA1 | Date | |
|---|---|---|---|
| e7fd8ec7e1 | |||
| bf04957370 |
14
src/form.rs
14
src/form.rs
@ -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 Validator<FD> {
|
pub struct FormValidator<FD> {
|
||||||
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
|
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FD: FormToolData> Validator<FD> {
|
impl<FD: FormToolData> FormValidator<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) -> Validator<FD> {
|
pub fn validator(self) -> FormValidator<FD> {
|
||||||
Validator {
|
FormValidator {
|
||||||
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>, Validator<FD>, View) {
|
pub fn to_parts(self) -> (RwSignal<FD>, FormValidator<FD>, View) {
|
||||||
(
|
(
|
||||||
self.fd,
|
self.fd,
|
||||||
Validator {
|
FormValidator {
|
||||||
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() -> Validator<Self> {
|
fn get_validator() -> FormValidator<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()
|
||||||
|
|||||||
@ -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, Validator},
|
form::{Form, FormToolData, FormValidator},
|
||||||
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) -> Validator<FD> {
|
pub(crate) fn validator(&self) -> FormValidator<FD> {
|
||||||
Validator {
|
FormValidator {
|
||||||
validations: self.validations.clone(),
|
validations: self.validations.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
pub mod controls;
|
pub mod controls;
|
||||||
pub mod form;
|
mod form;
|
||||||
pub mod form_builder;
|
mod form_builder;
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
|
mod validation_builder;
|
||||||
|
|
||||||
pub use form::{Form, FormToolData, Validator};
|
pub use form::{Form, FormToolData, FormValidator};
|
||||||
pub use form_builder::FormBuilder;
|
pub use form_builder::FormBuilder;
|
||||||
|
pub use validation_builder::ValidationBuilder;
|
||||||
|
|||||||
152
src/validation_builder.rs
Normal file
152
src/validation_builder.rs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user