use std::fmt;
mod numeric_input;
mod text_input;
pub use numeric_input::*;
pub use text_input::*;
pub use structform_derive::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
Required,
InvalidFormat {
required_type: String,
},
FromStrError(String),
NumberOutOfRange {
required_type: String,
min: String,
max: String,
},
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::Required => write!(f, "This field is required."),
ParseError::InvalidFormat { required_type } => write!(f, "Expected {}.", required_type),
ParseError::FromStrError(error) => write!(f, "{}.", error),
ParseError::NumberOutOfRange {
required_type,
min,
max,
} => write!(f, "Expected {} between {} and {}.", required_type, min, max),
}
}
}
pub trait StructForm<Model> {
type Field;
fn new(model: &Model) -> Self;
fn set_input(&mut self, field: Self::Field, value: String);
fn submit(&mut self) -> Result<Model, ParseError>;
fn submit_update(&mut self, model: Model) -> Result<Model, ParseError>;
fn submit_attempted(&self) -> bool;
fn is_empty(&self) -> bool;
fn has_unsaved_changes(&self, pristine: &Model) -> bool
where
Self: Clone,
Model: Clone + PartialEq,
{
let mut tmp_form = self.clone();
let updated_model = tmp_form.submit_update(pristine.clone());
match updated_model {
Ok(updated_model) => *pristine != updated_model,
Err(_) => true,
}
}
fn validation_error(&self) -> Option<ParseError>
where
Self: Clone,
{
if self.submit_attempted() {
self.clone().submit().err()
} else {
None
}
}
}
pub trait ParseAndFormat<T> {
fn parse(value: &str) -> Result<T, ParseError>;
fn format(value: &T) -> String;
}
#[macro_export]
macro_rules! derive_form_input {
($input:ident) => {
#[derive(Clone)]
pub struct $input<T> {
pub initial_input: String,
pub input: String,
pub value: Result<T, structform::ParseError>,
pub is_edited: bool,
}
impl<T> Default for $input<T>
where
$input<T>: structform::ParseAndFormat<T>,
{
fn default() -> $input<T> {
$input {
initial_input: String::new(),
input: String::new(),
value: $input::parse(""),
is_edited: false,
}
}
}
impl<T> $input<T> {
pub fn show_validation_msg(&self) -> bool {
self.is_edited && self.value.is_err()
}
pub fn validation_error(&self) -> Option<&structform::ParseError> {
self.value
.as_ref()
.err()
.filter(|_| self.show_validation_msg())
}
pub fn is_empty(&self) -> bool {
self.input.is_empty()
}
}
#[allow(dead_code)]
impl<T> $input<T>
where
$input<T>: structform::ParseAndFormat<T>,
T: Clone,
{
pub fn new(value: &T) -> $input<T> {
let initial_input = Self::format(value);
$input {
initial_input: initial_input.clone(),
input: initial_input,
value: Ok(value.clone()),
is_edited: false,
}
}
pub fn submit(&mut self) -> Result<T, structform::ParseError> {
self.is_edited = true;
self.value.clone()
}
pub fn set_input(&mut self, value: String) {
self.value = Self::parse(&value);
self.input = value;
self.is_edited = true;
}
pub fn clear(&mut self) {
self.initial_input = "".to_string();
self.set_input("".to_string());
self.is_edited = false;
}
}
};
}