Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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