Refactor #[derive(FromRequest)] to make illegal state unrepresentable (#1004)

This commit is contained in:
David Pedersen 2022-05-06 16:53:12 +02:00 committed by GitHub
parent b5183afbec
commit 6bd726f4a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 38 deletions

View file

@ -30,18 +30,14 @@ pub(crate) fn expand(item: syn::ItemStruct) -> syn::Result<TokenStream> {
return Err(syn::Error::new_spanned(where_clause, GENERICS_ERROR)); return Err(syn::Error::new_spanned(where_clause, GENERICS_ERROR));
} }
let FromRequestContainerAttr { match parse_container_attrs(&attrs)? {
via, FromRequestContainerAttr::Via(path) => impl_by_extracting_all_at_once(ident, fields, path),
rejection_derive, FromRequestContainerAttr::RejectionDerive(opt_outs) => {
} = parse_container_attrs(&attrs)?; impl_by_extracting_each_field(ident, fields, vis, opt_outs)
}
if let Some((_, path)) = via { FromRequestContainerAttr::None => {
impl_by_extracting_all_at_once(ident, fields, path) impl_by_extracting_each_field(ident, fields, vis, RejectionDeriveOptOuts::default())
} else { }
let rejection_derive_opt_outs = rejection_derive
.map(|(_, opt_outs)| opt_outs)
.unwrap_or_default();
impl_by_extracting_each_field(ident, fields, vis, rejection_derive_opt_outs)
} }
} }

View file

@ -10,10 +10,10 @@ pub(crate) struct FromRequestFieldAttr {
pub(crate) via: Option<(kw::via, syn::Path)>, pub(crate) via: Option<(kw::via, syn::Path)>,
} }
#[derive(Default)] pub(crate) enum FromRequestContainerAttr {
pub(crate) struct FromRequestContainerAttr { Via(syn::Path),
pub(crate) via: Option<(kw::via, syn::Path)>, RejectionDerive(RejectionDeriveOptOuts),
pub(crate) rejection_derive: Option<(kw::rejection_derive, RejectionDeriveOptOuts)>, None,
} }
pub(crate) mod kw { pub(crate) mod kw {
@ -47,47 +47,53 @@ pub(crate) fn parse_field_attrs(attrs: &[syn::Attribute]) -> syn::Result<FromReq
pub(crate) fn parse_container_attrs( pub(crate) fn parse_container_attrs(
attrs: &[syn::Attribute], attrs: &[syn::Attribute],
) -> syn::Result<FromRequestContainerAttr> { ) -> syn::Result<FromRequestContainerAttr> {
let attrs = parse_attrs(attrs)?; let attrs = parse_attrs::<ContainerAttr>(attrs)?;
let mut out = FromRequestContainerAttr::default(); let mut out_via = None;
let mut out_rejection_derive = None;
for from_request_attr in attrs { // we track the index of the attribute to know which comes last
// used to give more accurate error messages
for (idx, from_request_attr) in attrs.into_iter().enumerate() {
match from_request_attr { match from_request_attr {
ContainerAttr::Via { via, path } => { ContainerAttr::Via { via, path } => {
if out.rejection_derive.is_some() { if out_via.is_some() {
return Err(syn::Error::new_spanned(
via,
"cannot use both `rejection_derive` and `via`",
));
}
if out.via.is_some() {
return Err(double_attr_error("via", via)); return Err(double_attr_error("via", via));
} else { } else {
out.via = Some((via, path)); out_via = Some((idx, via, path));
} }
} }
ContainerAttr::RejectionDerive { ContainerAttr::RejectionDerive {
rejection_derive, rejection_derive,
opt_outs, opt_outs,
} => { } => {
if out.via.is_some() { if out_rejection_derive.is_some() {
return Err(syn::Error::new_spanned(
rejection_derive,
"cannot use both `via` and `rejection_derive`",
));
}
if out.rejection_derive.is_some() {
return Err(double_attr_error("rejection_derive", rejection_derive)); return Err(double_attr_error("rejection_derive", rejection_derive));
} else { } else {
out.rejection_derive = Some((rejection_derive, opt_outs)); out_rejection_derive = Some((idx, rejection_derive, opt_outs));
} }
} }
} }
} }
Ok(out) match (out_via, out_rejection_derive) {
(Some((via_idx, via, _)), Some((rejection_derive_idx, rejection_derive, _))) => {
if via_idx > rejection_derive_idx {
Err(syn::Error::new_spanned(
via,
"cannot use both `rejection_derive` and `via`",
))
} else {
Err(syn::Error::new_spanned(
rejection_derive,
"cannot use both `via` and `rejection_derive`",
))
}
}
(Some((_, _, path)), None) => Ok(FromRequestContainerAttr::Via(path)),
(None, Some((_, _, opt_outs))) => Ok(FromRequestContainerAttr::RejectionDerive(opt_outs)),
(None, None) => Ok(FromRequestContainerAttr::None),
}
} }
pub(crate) fn parse_attrs<T>(attrs: &[syn::Attribute]) -> syn::Result<Punctuated<T, Token![,]>> pub(crate) fn parse_attrs<T>(attrs: &[syn::Attribute]) -> syn::Result<Punctuated<T, Token![,]>>