Skip to content
Open
9 changes: 9 additions & 0 deletions cot-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod from_request;
mod main_fn;
mod model;
mod query;
mod select_as_form_field;
mod select_choice;

use darling::Error;
Expand All @@ -23,6 +24,7 @@ use crate::from_request::impl_from_request_head_for_struct;
use crate::main_fn::{fn_to_cot_e2e_test, fn_to_cot_main, fn_to_cot_test};
use crate::model::impl_model_for_struct;
use crate::query::{Query, query_to_tokens};
use crate::select_as_form_field::impl_select_as_form_field_for_enum;
use crate::select_choice::impl_select_choice_for_enum;

#[proc_macro_derive(Form, attributes(form))]
Expand Down Expand Up @@ -213,6 +215,13 @@ pub fn derive_select_choice(input: TokenStream) -> TokenStream {
token_stream.into()
}

#[proc_macro_derive(SelectAsFormField)]
pub fn derive_select_as_form_field(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input as DeriveInput);
let token_stream = impl_select_as_form_field_for_enum(&ast);
token_stream.into()
}

#[proc_macro_derive(IntoResponse)]
pub fn derive_into_response(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
Expand Down
42 changes: 42 additions & 0 deletions cot-macros/src/select_as_form_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use darling::Error;
use quote::quote;
use syn::{Data, DeriveInput};

use crate::cot_ident;

pub(super) fn impl_select_as_form_field_for_enum(ast: &DeriveInput) -> proc_macro2::TokenStream {
let enum_name = &ast.ident;
let cot = cot_ident();

match &ast.data {
Data::Enum(_) => {}
_ => {
return Error::custom("`SelectAsFormField` can only be derived for enums")
.write_errors();
}
}

let impl_single = quote! {
#[automatically_derived]
impl #cot::form::AsFormField for #enum_name {
type Type = #cot::form::fields::SelectField<Self>;

fn clean_value(
field: &Self::Type
) -> ::core::result::Result<Self, #cot::form::FormFieldValidationError> {
match #cot::form::FormField::value(field) {
::core::option::Option::Some(v) if !v.is_empty() => <Self as #cot::form::fields::SelectChoice>::from_str(v),
_ => ::core::result::Result::Err(#cot::form::FormFieldValidationError::Required),
}
}

fn to_field_value(&self) -> ::std::string::String {
<Self as #cot::form::fields::SelectChoice>::to_string(self)
}
}
};

quote! {
#impl_single
}
}
15 changes: 15 additions & 0 deletions cot-macros/tests/compile_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ fn derive_select_choice() {
t.compile_fail("tests/ui/derive_select_choice_empty_enum.rs");
}

#[rustversion::attr(
not(nightly),
ignore = "only test on nightly for consistent error messages"
)]
#[test]
#[cfg_attr(
miri,
ignore = "unsupported operation: extern static `pidfd_spawnp` is not supported by Miri"
)]
fn derive_select_as_form_field() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/derive_select_as_form_field.rs");
t.compile_fail("tests/ui/derive_select_as_form_field_struct.rs");
}

#[rustversion::attr(
not(nightly),
ignore = "only test on nightly for consistent error messages"
Expand Down
10 changes: 10 additions & 0 deletions cot-macros/tests/ui/derive_select_as_form_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use cot::form::fields::{SelectChoice, SelectAsFormField};

#[derive(SelectChoice, SelectAsFormField, Debug, Clone, PartialEq, Eq, Hash)]
enum Status {
Draft,
Published,
Archived,
}

fn main() {}
8 changes: 8 additions & 0 deletions cot-macros/tests/ui/derive_select_as_form_field_struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use cot_macros::SelectAsFormField;

#[derive(SelectAsFormField)]
struct NotAnEnum {
x: u8,
}

fn main() {}
7 changes: 7 additions & 0 deletions cot-macros/tests/ui/derive_select_as_form_field_struct.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
error: `SelectAsFormField` can only be derived for enums
--> tests/ui/derive_select_as_form_field_struct.rs:3:10
|
3 | #[derive(SelectAsFormField)]
| ^^^^^^^^^^^^^^^^^
|
= note: this error originates in the derive macro `SelectAsFormField` (in Nightly builds, run with -Z macro-backtrace for more info)
3 changes: 2 additions & 1 deletion cot/src/form/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ pub use chrono::{
pub use files::{FileField, FileFieldOptions, InMemoryUploadedFile};
pub(crate) use select::check_required_multiple;
pub use select::{
SelectChoice, SelectField, SelectFieldOptions, SelectMultipleField, SelectMultipleFieldOptions,
SelectAsFormField, SelectChoice, SelectField, SelectFieldOptions, SelectMultipleField,
SelectMultipleFieldOptions,
};

use crate::auth::PasswordHash;
Expand Down
42 changes: 5 additions & 37 deletions cot/src/form/fields/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ use cot::form::FormField;
use cot::form::fields::impl_form_field;
use cot::html::HtmlTag;

use crate::form::fields::{
SelectChoice, SelectField, SelectMultipleField, Step, check_required, check_required_multiple,
};
use crate::form::fields::{SelectChoice, SelectField, Step, check_required};
use crate::form::{AsFormField, FormFieldValidationError};

impl AsFormField for Weekday {
Expand All @@ -29,39 +27,7 @@ impl AsFormField for Weekday {
}
}

macro_rules! impl_as_form_field_mult {
($field_type:ty) => {
impl_as_form_field_mult_collection!(::std::vec::Vec<$field_type>, $field_type);
impl_as_form_field_mult_collection!(::std::collections::VecDeque<$field_type>, $field_type);
impl_as_form_field_mult_collection!(
::std::collections::LinkedList<$field_type>,
$field_type
);
impl_as_form_field_mult_collection!(::std::collections::HashSet<$field_type>, $field_type);
impl_as_form_field_mult_collection!(::indexmap::IndexSet<$field_type>, $field_type);
};
}

macro_rules! impl_as_form_field_mult_collection {
($collection_type:ty, $field_type:ty) => {
impl AsFormField for $collection_type {
type Type = SelectMultipleField<$field_type>;

fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
let value = check_required_multiple(field)?;

value.iter().map(|id| <$field_type>::from_str(id)).collect()
}

fn to_field_value(&self) -> String {
String::new()
}
}
};
}

impl_as_form_field_mult!(Weekday);
impl_as_form_field_mult_collection!(WeekdaySet, Weekday);
crate::form::fields::select::impl_as_form_field_mult_collection!(() => WeekdaySet, Weekday);

const MONDAY_ID: &str = "mon";
const TUESDAY_ID: &str = "tue";
Expand Down Expand Up @@ -730,7 +696,9 @@ mod tests {
use cot::form::FormFieldValue;

use super::*;
use crate::form::fields::{SelectFieldOptions, SelectMultipleFieldOptions};
use crate::form::fields::{
SelectFieldOptions, SelectMultipleField, SelectMultipleFieldOptions,
};
use crate::form::{FormField, FormFieldOptions};

#[test]
Expand Down
Loading
Loading