pub mod fields;
use std::borrow::Cow;
use std::fmt::{Debug, Display};
use async_trait::async_trait;
pub use cot_macros::Form;
use thiserror::Error;
use crate::request;
use crate::request::{Request, RequestExt};
#[derive(Debug, Error)]
pub enum FormError {
#[error("Request error: {error}")]
RequestError {
#[from]
error: Box<crate::Error>,
},
}
#[must_use]
#[derive(Debug, Clone)]
pub enum FormResult<T: Form> {
Ok(T),
ValidationError(T::Context),
}
impl<T: Form> FormResult<T> {
#[track_caller]
pub fn unwrap(self) -> T {
match self {
Self::Ok(form) => form,
Self::ValidationError(context) => panic!("Form validation failed: {context:?}"),
}
}
}
#[derive(Debug, Error, PartialEq, Eq)]
#[error("{message}")]
pub enum FormFieldValidationError {
#[error("This field is required.")]
Required,
#[error("This exceeds the maximum length of {max_length}.")]
MaximumLengthExceeded {
max_length: u32,
},
#[error("This field must be checked.")]
BooleanRequiredToBeTrue,
#[error("Value is not valid for this field.")]
InvalidValue(String),
#[error("{0}")]
Custom(Cow<'static, str>),
}
impl FormFieldValidationError {
#[must_use]
pub fn invalid_value<T: Into<String>>(value: T) -> Self {
Self::InvalidValue(value.into())
}
#[must_use]
pub fn maximum_length_exceeded(max_length: u32) -> Self {
Self::MaximumLengthExceeded { max_length }
}
#[must_use]
pub const fn from_string(message: String) -> Self {
Self::Custom(Cow::Owned(message))
}
#[must_use]
pub const fn from_static(message: &'static str) -> Self {
Self::Custom(Cow::Borrowed(message))
}
}
#[derive(Debug)]
pub enum FormErrorTarget<'a> {
Field(&'a str),
Form,
}
#[async_trait]
pub trait Form: Sized {
type Context: FormContext;
async fn from_request(request: &mut Request) -> Result<FormResult<Self>, FormError>;
fn to_context(&self) -> Self::Context;
async fn build_context(request: &mut Request) -> Result<Self::Context, FormError> {
let form_data = request
.form_data()
.await
.map_err(|error| FormError::RequestError {
error: Box::new(error),
})?;
let mut context = Self::Context::new();
for (field_id, value) in request::query_pairs(&form_data) {
let field_id = field_id.as_ref();
if let Err(err) = context.set_value(field_id, value) {
context.add_error(FormErrorTarget::Field(field_id), err);
}
}
Ok(context)
}
}
pub trait FormContext: Debug {
fn new() -> Self
where
Self: Sized;
fn fields(&self) -> Box<dyn DoubleEndedIterator<Item = &dyn DynFormField> + '_>;
fn set_value(
&mut self,
field_id: &str,
value: Cow<'_, str>,
) -> Result<(), FormFieldValidationError>;
fn add_error(&mut self, target: FormErrorTarget<'_>, error: FormFieldValidationError) {
self.errors_for_mut(target).push(error);
}
fn errors_for(&self, target: FormErrorTarget<'_>) -> &[FormFieldValidationError];
fn errors_for_mut(&mut self, target: FormErrorTarget<'_>)
-> &mut Vec<FormFieldValidationError>;
fn has_errors(&self) -> bool;
}
#[derive(Debug)]
pub struct FormFieldOptions {
pub id: String,
pub name: String,
pub required: bool,
}
pub trait FormField: Display {
type CustomOptions: Default;
fn with_options(options: FormFieldOptions, custom_options: Self::CustomOptions) -> Self
where
Self: Sized;
fn options(&self) -> &FormFieldOptions;
fn id(&self) -> &str {
&self.options().id
}
fn name(&self) -> &str {
&self.options().name
}
fn value(&self) -> Option<&str>;
fn set_value(&mut self, value: Cow<'_, str>);
}
pub trait DynFormField: Display {
fn dyn_options(&self) -> &FormFieldOptions;
fn dyn_id(&self) -> &str;
fn dyn_value(&self) -> Option<&str>;
fn dyn_set_value(&mut self, value: Cow<'_, str>);
}
impl<T: FormField> DynFormField for T {
fn dyn_options(&self) -> &FormFieldOptions {
FormField::options(self)
}
fn dyn_id(&self) -> &str {
FormField::id(self)
}
fn dyn_value(&self) -> Option<&str> {
FormField::value(self)
}
fn dyn_set_value(&mut self, value: Cow<'_, str>) {
FormField::set_value(self, value);
}
}
pub trait AsFormField {
type Type: FormField;
fn new_field(
options: FormFieldOptions,
custom_options: <Self::Type as FormField>::CustomOptions,
) -> Self::Type {
Self::Type::with_options(options, custom_options)
}
fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError>
where
Self: Sized;
fn to_field_value(&self) -> String;
}