formidable 0.1.0

Easily derive forms from structs for Leptos.
Documentation
use crate::{
    components::{Input, InputType},
    FieldError, Form, FormError, Name,
};
use derive_more::{Deref, Into};
use leptos::prelude::*;
use std::fmt::Display;
use std::str::FromStr;
use time::macros::format_description;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Into, Deref)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Date(time::Date);

const DATE_FORMAT: &[time::format_description::BorrowedFormatItem<'_>] =
    format_description!("[year]-[month]-[day]");

impl Display for Date {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0.format(DATE_FORMAT).unwrap())
    }
}

impl FromStr for Date {
    type Err = time::error::Parse;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let date = time::Date::parse(s, DATE_FORMAT)?;
        Ok(Date(date))
    }
}

impl Default for Date {
    fn default() -> Self {
        let now: time::OffsetDateTime =
            time::OffsetDateTime::now_local().unwrap_or(time::OffsetDateTime::now_utc());
        Date(now.date())
    }
}

impl Form for Date {
    fn view(
        field: crate::FieldConfiguration,
        name: Name,
        value: Option<Self>,
        callback: Option<Callback<Result<Self, FormError>>>,
    ) -> impl IntoView {
        view! {
            <Input<Date>
                label=field.label.expect("No label provided")
                description=field.description
                name=name
                value=value
                callback={callback.map(|callback| Callback::new(move |v: Result<Self, FieldError>| {
                    callback.run(v.map_err(FormError::from));
                }))}
                input_type=InputType::Date
            />
        }
    }
}

const DATETIME_LOCAL_FORMAT: &[time::format_description::BorrowedFormatItem<'_>] =
    format_description!("[year]-[month]-[day]T[hour]:[minute]");

#[derive(Debug, Clone, PartialEq, Eq, Hash, Into, Deref)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PrimitiveDateTime(time::PrimitiveDateTime);

impl Display for PrimitiveDateTime {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0.format(DATETIME_LOCAL_FORMAT).unwrap())
    }
}

impl FromStr for PrimitiveDateTime {
    type Err = time::error::Parse;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let datetime = time::PrimitiveDateTime::parse(s, DATETIME_LOCAL_FORMAT)?;
        Ok(PrimitiveDateTime(datetime))
    }
}

impl Default for PrimitiveDateTime {
    fn default() -> Self {
        let now: time::OffsetDateTime =
            time::OffsetDateTime::now_local().unwrap_or(time::OffsetDateTime::now_utc());
        PrimitiveDateTime(time::PrimitiveDateTime::new(now.date(), now.time()))
    }
}

impl Form for PrimitiveDateTime {
    fn view(
        field: crate::FieldConfiguration,
        name: Name,
        value: Option<Self>,
        callback: Option<Callback<Result<Self, FormError>>>,
    ) -> impl IntoView {
        view! {
            <Input<PrimitiveDateTime>
                label=field.label.expect("No label provided")
                description=field.description
                name=name
                value=value
                callback={callback.map(|callback| Callback::new(move |v: Result<Self, FieldError>| {
                    callback.run(v.map_err(FormError::from));
                }))}
                input_type=InputType::DatetimeLocal
            />
        }
    }
}

const TIME_FORMAT: &[time::format_description::BorrowedFormatItem<'_>] =
    format_description!("[hour]:[minute]");

#[derive(Debug, Clone, PartialEq, Eq, Hash, Into, Deref)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Time(time::Time);

impl Display for Time {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0.format(TIME_FORMAT).unwrap())
    }
}

impl FromStr for Time {
    type Err = time::error::Parse;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let time = time::Time::parse(s, TIME_FORMAT)?;
        Ok(Time(time))
    }
}

impl Default for Time {
    fn default() -> Self {
        let now: time::OffsetDateTime =
            time::OffsetDateTime::now_local().unwrap_or(time::OffsetDateTime::now_utc());
        Time(now.time())
    }
}

impl Form for Time {
    fn view(
        field: crate::FieldConfiguration,
        name: Name,
        value: Option<Self>,
        callback: Option<Callback<Result<Self, FormError>>>,
    ) -> impl IntoView {
        view! {
            <Input<Time>
                label=field.label.expect("No label provided")
                description=field.description
                name=name
                value=value
                callback={callback.map(|callback| Callback::new(move |v: Result<Self, FieldError>| {
                    callback.run(v.map_err(FormError::from));
                }))}
                input_type=InputType::Time
            />
        }
    }
}