ridstack-form 0.1.0

End-to-End Type-safe form handling for Dioxus applications
use tokio::sync::RwLock;

use crate::{field::*, traits::*};
use std::{cell::RefCell, error::Error, ops::Deref, rc::Rc, sync::Arc};

/// Validators to be applied to fields thrould the Field component.
#[derive(Clone)]
pub struct Validator<TFieldValue: FieldValue, TFieldError: FieldError> {
    pub(crate) validator: Rc<RefCell<dyn FnMut(&TFieldValue) -> Result<(), TFieldError>>>,
    pub(crate) id: u8,
}

impl<TFieldValue: FieldValue, TFieldError: FieldError> Validator<TFieldValue, TFieldError> {
    /// You'll need the [u8] id param for maintaining the uniqueness of the validator. Instead
    /// of using [Rc::ptr_eq], this id is used to test equality of validator, so that its not run
    /// unless required.
    pub fn new<Validator>(validator: Validator, id: u8) -> Self
    where
        Validator: FnMut(&TFieldValue) -> Result<(), TFieldError> + 'static,
    {
        Self {
            validator: Rc::new(RefCell::new(validator)),
            id,
        }
    }
    pub(crate) fn validate(&self, value: &TFieldValue) -> Result<(), TFieldError> {
        self.validator.borrow_mut()(value)
    }
}

/// Async validator for fields requiring db or heavy computation checks. By default, for now,
/// if input validation is enabled, on typing the form, it debounces by 350 ms.
#[derive(Clone)]
pub struct AsyncValidator<TFieldValue: FieldValue, TFieldError: FieldError> {
    pub(crate) validator: Rc<
        RefCell<
            dyn FnMut(
                &TFieldValue,
            )
                -> std::pin::Pin<Box<dyn Future<Output = Result<(), TFieldError>>>>,
        >,
    >,
    pub(crate) id: u8,
}

impl<TFieldValue: FieldValue, TFieldError: FieldError> AsyncValidator<TFieldValue, TFieldError> {
    /// You'll need the [u8] id param for maintaining the uniqueness of the validator. Instead
    /// of using [Rc::ptr_eq], this id is used to test equality of validator, so that its not run
    /// unless required.
    pub fn new<Validator>(validator: Validator, id: u8) -> Self
    where
        Validator: FnMut(&TFieldValue) -> std::pin::Pin<Box<dyn Future<Output = Result<(), TFieldError>>>>
            + 'static,
    {
        Self {
            validator: Rc::new(RefCell::new(validator)),
            id,
        }
    }

    pub(crate) async fn validate(&self, value: &TFieldValue) -> Result<(), TFieldError> {
        (self.validator.borrow_mut())(value).await
    }
}

pub type Validators<TFieldValue: FieldValue, TFieldError: FieldError> =
    Vec<Validator<TFieldValue, TFieldError>>;
pub type AsyncValidators<TFieldValue: FieldValue, TFieldError: FieldError> =
    Vec<AsyncValidator<TFieldValue, TFieldError>>;

#[derive(Debug, PartialEq, Clone, Default, Copy)]
pub enum ValidateOn {
    Input,
    Blur,
    #[default]
    All,
    Submit,
}

impl<TFieldValue: FieldValue, TFieldError: FieldError> PartialEq
    for Validator<TFieldValue, TFieldError>
{
    fn eq(&self, other: &Self) -> bool {
        self.id.eq(&other.id)
    }
}
impl<TFieldValue: FieldValue, TFieldError: FieldError> Eq for Validator<TFieldValue, TFieldError> {}
impl<TFieldValue: FieldValue, TFieldError: FieldError> PartialOrd
    for Validator<TFieldValue, TFieldError>
{
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.id.partial_cmp(&other.id)
    }
}
impl<TFieldValue: FieldValue, TFieldError: FieldError> Ord for Validator<TFieldValue, TFieldError> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.id.cmp(&other.id)
    }
}

impl<TFieldValue: FieldValue, TFieldError: FieldError> PartialEq
    for AsyncValidator<TFieldValue, TFieldError>
{
    fn eq(&self, other: &Self) -> bool {
        self.id.eq(&other.id)
    }
}
impl<TFieldValue: FieldValue, TFieldError: FieldError> Eq
    for AsyncValidator<TFieldValue, TFieldError>
{
}
impl<TFieldValue: FieldValue, TFieldError: FieldError> PartialOrd
    for AsyncValidator<TFieldValue, TFieldError>
{
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.id.partial_cmp(&other.id)
    }
}
impl<TFieldValue: FieldValue, TFieldError: FieldError> Ord
    for AsyncValidator<TFieldValue, TFieldError>
{
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.id.cmp(&other.id)
    }
}