Skip to content

Commit

Permalink
Merge pull request #7 from meilisearch/field-attrs
Browse files Browse the repository at this point in the history
Various improvements to field attributes
  • Loading branch information
irevoire authored Jan 10, 2023
2 parents 72dd35c + fbeb8cc commit bd75724
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 103 deletions.
14 changes: 10 additions & 4 deletions derive/src/attribute_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct FieldAttributesInfo {
/// The default value to deserialise to when the field is missing.
pub default: Option<DefaultFieldAttribute>,
/// The error to return when the field is missing and no default value exists.
pub missing_field_error: Option<Expr>,
pub missing_field_error: Option<ExprPath>,
/// The type of the error used to deserialize the field
pub error: Option<syn::Type>,
/// The function to apply to the result after it has been deserialised successfully
Expand All @@ -28,6 +28,8 @@ pub struct FieldAttributesInfo {
pub from: Option<AttributeFrom>,
/// Whether an additional where clause should be added to deserialize this field
pub needs_predicate: bool,
/// Whether the field should be skipped
pub skipped: bool,

/// Span of the `default` attribute, if any, for compile error reporting purposes
default_span: Option<Span>,
Expand Down Expand Up @@ -116,6 +118,7 @@ impl FieldAttributesInfo {
self.from = Some(from)
}
self.needs_predicate |= other.needs_predicate;
self.skipped |= other.skipped;

Ok(())
}
Expand Down Expand Up @@ -159,9 +162,9 @@ impl syn::parse::Parse for FieldAttributesInfo {
}
"missing_field_error" => {
let _eq = input.parse::<Token![=]>()?;
let expr = input.parse::<Expr>()?;
// #[deserr( ... missing_field_error = expr )]
other.missing_field_error = Some(expr);
let func = input.parse::<ExprPath>()?;
// #[deserr( ... missing_field_error = func )]
other.missing_field_error = Some(func);
}
"needs_predicate" => other.needs_predicate = true,
"error" => {
Expand All @@ -181,6 +184,9 @@ impl syn::parse::Parse for FieldAttributesInfo {
// #[deserr( .. from(from_ty) = function::path::<_> -> to_ty )]
other.from = Some(from_attr);
}
"skip" => {
other.skipped = true;
}
_ => {
let message = format!("Unknown deserr field attribute: {}", attr_name);
return Result::Err(syn::Error::new_spanned(attr_name, message));
Expand Down
25 changes: 21 additions & 4 deletions derive/src/derive_named_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,34 @@ pub fn generate_named_fields_impl(

let froms = field_from_fns
.iter()
.map(|from_func| match from_func {
.zip(field_errs.iter())
.map(|(from_func, field_err)| match from_func {
Some(from_func) => quote!(
match (#from_func)(x) {
::std::result::Result::Ok(x) => {
::deserr::FieldState::Some(x)
}
::std::result::Result::Err(e) => {
deserr_error__ = Some(<#err_ty as ::deserr::MergeWithError<_>>::merge(
deserr_error__,
let tmp_deserr_error__ = match <#field_err as ::deserr::MergeWithError<_>>::merge(
None,
e,
deserr_location__.push_key(deserr_key__.as_str())
) {
::std::result::Result::Ok(e) => e,
::std::result::Result::Err(e) => {
return ::std::result::Result::Err(
<#err_ty as ::deserr::MergeWithError<_>>::merge(
deserr_error__,
e,
deserr_location__.push_key(deserr_key__.as_str())
)?
)
}
};
deserr_error__ = ::std::option::Option::Some(<#err_ty as ::deserr::MergeWithError<_>>::merge(
deserr_error__,
tmp_deserr_error__,
deserr_location__.push_key(deserr_key__.as_str())
)?);
::deserr::FieldState::Err
}
Expand Down Expand Up @@ -91,7 +108,7 @@ pub fn generate_named_fields_impl(
// Now we check whether any field was missing
#(
if #field_names .is_missing() {
#missing_field_errors
#missing_field_errors
}
)*

Expand Down
154 changes: 90 additions & 64 deletions derive/src/parse_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,33 +177,6 @@ impl DerivedTypeInfo {
// The goal of creating these simple bindings is to be able to reference them in a quote! macro
let ident = input.ident;

// extract all the error types that can occurs from the `from` attributes in the struct
let extra_constraint = match data {
TraitImplementationInfo::Struct(NamedFieldsInfo {
ref field_from_errors,
..
}) => field_from_errors
.iter()
.filter_map(|error| error.as_ref())
.map(|error| quote!(+ ::deserr::MergeWithError<#error>))
.collect::<TokenStream>(),
TraitImplementationInfo::Enum { ref variants, .. } => variants
.iter()
.filter_map(|variant| match variant.data {
VariantData::Unit => None,
VariantData::Named(ref variant_info) => Some(variant_info),
})
.flat_map(|error| {
error
.field_from_errors
.iter()
.filter_map(|error| error.as_ref())
})
.map(|error| quote!(+ ::deserr::MergeWithError<#error>))
.collect(),
_ => TokenStream::new(),
};

// append the additional clause to the existing where clause
let mut new_predicates = input
.generics
Expand All @@ -222,6 +195,34 @@ impl DerivedTypeInfo {
#err_ty : ::deserr::DeserializeError
));
}
match &data {
TraitImplementationInfo::Struct(NamedFieldsInfo {
field_from_errors, ..
}) => {
for field_from_error in field_from_errors.iter().flatten() {
new_predicates.push(parse_quote!(
#err_ty : ::deserr::MergeWithError<#field_from_error>
))
}
}
TraitImplementationInfo::Enum { variants, .. } => {
for variant in variants {
match &variant.data {
VariantData::Unit => continue,
VariantData::Named(variant_info) => {
for field_from_error in
variant_info.field_from_errors.iter().flatten()
{
new_predicates.push(parse_quote!(
#err_ty : ::deserr::MergeWithError<#field_from_error>
));
}
}
}
}
}
TraitImplementationInfo::UserProvidedFunction { .. } => {}
}

// Add MergeWithError<FromFunctionError> requirement
if let Some(from) = &attrs.from {
Expand Down Expand Up @@ -289,7 +290,7 @@ impl DerivedTypeInfo {
.extend(attrs.where_predicates.clone());

quote! {
impl #impl_generics ::deserr::DeserializeFromValue<#err_ty> for #ident #ty_generics #bounded_where_clause #extra_constraint
impl #impl_generics ::deserr::DeserializeFromValue<#err_ty> for #ident #ty_generics #bounded_where_clause
}
};

Expand Down Expand Up @@ -389,18 +390,24 @@ impl NamedFieldsInfo {
// `true` iff the field has the needs_predicate attribute
let mut needs_predicate = vec![];

for field in fields.named.iter() {
let mut fields_extra = fields
.named
.into_iter()
.map(|field| {
let attrs = read_deserr_field_attributes(&field.attrs)?;
Ok((field, attrs))
})
.collect::<Result<Vec<_>, syn::Error>>()?;

// We put all the non-skipped fields at the beginning, so that when we iterate
// over the non-skipped key names, we can access their corresponding field names
// using the same index.
fields_extra.sort_by_key(|x| x.1.skipped);

for (field, attrs) in fields_extra.iter() {
let field_name = field.ident.clone().unwrap();
let field_ty = &field.ty;

let attrs = read_deserr_field_attributes(&field.attrs)?;
let renamed = attrs.rename.as_ref().map(|i| i.value());
let key_name = key_name_for_ident(
field_name.to_string(),
data_attrs.rename_all.as_ref(),
renamed.as_deref(),
);

let field_default = if let Some(default) = &attrs.default {
match default {
// #[deserr(default)] => use the Default trait
Expand All @@ -412,35 +419,46 @@ impl NamedFieldsInfo {
quote! { ::std::option::Option::Some(#expr) }
}
}
} else if attrs.skipped {
quote! { ::std::option::Option::Some(::std::default::Default::default()) }
} else {
// no `default` attribute => use the DeserializeFromValue::default() method
quote! { ::deserr::DeserializeFromValue::<#err_ty>::default() }
};

let missing_field_error = match attrs.missing_field_error {
Some(error_expr) => {
let field_ty = match attrs.from {
Some(ref from) => from.from_ty.clone(),
None => field_ty.clone(),
};

let field_map = match &attrs.map {
Some(func) => {
quote! {
let deserr_e__ = #error_expr ;
deserr_error__ = ::std::option::Option::Some(<#err_ty as ::deserr::MergeWithError<_>>::merge(
deserr_error__,
deserr_e__,
deserr_location__
)?);
#func
}
}
None => {
quote! {
deserr_error__ = ::std::option::Option::Some(<#err_ty as ::deserr::DeserializeError>::error::<V>(
deserr_error__,
::deserr::ErrorKind::MissingField {
field: #key_name,
},
deserr_location__
)?);
}
quote! { ::std::convert::identity }
}
};

field_names.push(field_name);
field_tys.push(field_ty.clone());
field_defaults.push(field_default);
field_maps.push(field_map);
needs_predicate.push(attrs.needs_predicate);
}

for (field, attrs) in fields_extra.into_iter().filter(|x| !x.1.skipped) {
let field_ty = &field.ty;
let field_name = field.ident.clone().unwrap();

let renamed = attrs.rename.as_ref().map(|i| i.value());
let key_name = key_name_for_ident(
field_name.to_string(),
data_attrs.rename_all.as_ref(),
renamed.as_deref(),
);
let error = match attrs.error {
Some(error) => error,
None => data_attrs
Expand Down Expand Up @@ -471,27 +489,35 @@ impl NamedFieldsInfo {
.as_ref()
.map(|from| from.function.error_ty.clone());

let field_map = match attrs.map {
Some(func) => {
let missing_field_error = match &attrs.missing_field_error {
Some(error_function) => {
quote! {
#func
let deserr_e__ = #error_function ( #key_name, deserr_location__ ) ;
deserr_error__ = ::std::option::Option::Some(<#err_ty as ::deserr::MergeWithError<_>>::merge(
deserr_error__,
deserr_e__,
deserr_location__
)?);
}
}
None => {
quote! { ::std::convert::identity }
quote! {
deserr_error__ = ::std::option::Option::Some(<#err_ty as ::deserr::DeserializeError>::error::<V>(
deserr_error__,
::deserr::ErrorKind::MissingField {
field: #key_name,
},
deserr_location__
)?);
}
}
};

field_names.push(field_name);
field_tys.push(field_ty.clone());
key_names.push(key_name.clone());
field_defaults.push(field_default);
field_errs.push(error);
field_from_fns.push(field_from_fn);
field_from_errors.push(field_from_error);
field_maps.push(field_map);
missing_field_errors.push(missing_field_error);
needs_predicate.push(attrs.needs_predicate);
}

// Create the token stream representing the code to handle an unknown field key.
Expand All @@ -506,7 +532,7 @@ impl NamedFieldsInfo {
quote! {
deserr_error__ = ::std::option::Option::Some(<#err_ty as ::deserr::DeserializeError>::error::<V>(
deserr_error__,
deserr::ErrorKind::UnknownKey {
::deserr::ErrorKind::UnknownKey {
key: deserr_key__,
accepted: &[#(#key_names),*],
},
Expand Down
33 changes: 24 additions & 9 deletions src/default_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ use std::num::ParseIntError;
use crate::*;

#[derive(Debug, PartialEq, Eq)]
pub enum DefaultError {
pub struct DefaultError {
pub location: ValuePointer,
pub content: DefaultErrorContent,
}

#[derive(Debug, PartialEq, Eq)]
pub enum DefaultErrorContent {
Unexpected(String),
MissingField(String),
IncorrectValueKind {
Expand Down Expand Up @@ -35,37 +41,46 @@ impl DeserializeError for DefaultError {
fn error<V: IntoValue>(
_self_: Option<Self>,
error: ErrorKind<V>,
_location: ValuePointerRef,
location: ValuePointerRef,
) -> Result<Self, Self> {
Err(match error {
let content = match error {
ErrorKind::IncorrectValueKind {
actual: _,
accepted,
} => Self::IncorrectValueKind {
} => DefaultErrorContent::IncorrectValueKind {
accepted: accepted.to_vec(),
},
ErrorKind::MissingField { field } => Self::MissingField(field.to_string()),
ErrorKind::UnknownKey { key, accepted } => Self::UnknownKey {
ErrorKind::MissingField { field } => {
DefaultErrorContent::MissingField(field.to_string())
}
ErrorKind::UnknownKey { key, accepted } => DefaultErrorContent::UnknownKey {
key: key.to_string(),
accepted: accepted
.iter()
.map(|accepted| accepted.to_string())
.collect(),
},
ErrorKind::UnknownValue { value, accepted } => Self::UnknownValue {
ErrorKind::UnknownValue { value, accepted } => DefaultErrorContent::UnknownValue {
value: value.to_string(),
accepted: accepted
.iter()
.map(|accepted| accepted.to_string())
.collect(),
},
ErrorKind::Unexpected { msg } => Self::Unexpected(msg),
ErrorKind::Unexpected { msg } => DefaultErrorContent::Unexpected(msg),
};
Err(Self {
location: location.to_owned(),
content,
})
}
}

impl From<ParseIntError> for DefaultError {
fn from(value: ParseIntError) -> Self {
Self::Unexpected(value.to_string())
Self {
location: ValuePointerRef::Origin.to_owned(),
content: DefaultErrorContent::Unexpected(value.to_string()),
}
}
}
Loading

0 comments on commit bd75724

Please sign in to comment.