use proc_macro::TokenStream;
use quote::quote;
use syn::{
Attribute, Data, DeriveInput, Fields, Index, Lit, LitFloat, LitInt, LitStr, Meta, Path, Token, Type,
TypePath,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
};
fn tag_type() -> (proc_macro2::TokenStream, usize) {
if cfg!(feature = "tag-u16") {
(quote! { u16 }, 2)
} else if cfg!(feature = "tag-u32") {
(quote! { u32 }, 4)
} else {
(quote! { u8 }, 1)
}
}
#[proc_macro_derive(AFastSerialize, attributes(afast))]
pub fn derive_serialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let generics = &input.generics;
let mut generics_with_bounds = generics.clone();
for param in &mut generics_with_bounds.params {
if let syn::GenericParam::Type(ref mut ty) = *param {
ty.bounds
.push(syn::parse_quote!(::afastdata::AFastSerialize));
}
}
let (impl_generics, _, _) = generics_with_bounds.split_for_impl();
let (_, ty_generics, _) = generics.split_for_impl();
let expanded = match &input.data {
Data::Struct(data) => {
let serialize_body = generate_serialize_fields(&data.fields, quote!(self));
quote! {
impl #impl_generics ::afastdata::AFastSerialize for #name #ty_generics {
fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
#(#serialize_body)*
bytes
}
}
}
}
Data::Enum(data) => {
let (tag_ty, _) = tag_type();
let mut arms = Vec::new();
for (i, variant) in data.variants.iter().enumerate() {
let variant_name = &variant.ident;
match &variant.fields {
Fields::Unit => {
arms.push(quote! {
#name::#variant_name => {
bytes.extend((#i as #tag_ty).to_le_bytes());
}
});
}
Fields::Unnamed(fields) => {
let field_names: Vec<_> = (0..fields.unnamed.len())
.map(|i| {
syn::Ident::new(&format!("__f{}", i), variant_name.span())
})
.collect();
let field_patterns = &field_names;
let mut serialize_fields = Vec::new();
for (i, f) in fields.unnamed.iter().enumerate() {
if !has_skip_attr(&f.attrs).0 {
let fname = &field_names[i];
serialize_fields.push(quote! {
bytes.extend(::afastdata::AFastSerialize::to_bytes(#fname));
});
}
}
arms.push(quote! {
#name::#variant_name(#(#field_patterns),*) => {
bytes.extend((#i as #tag_ty).to_le_bytes());
#(#serialize_fields)*
}
});
}
Fields::Named(fields) => {
let all_field_names: Vec<_> = fields
.named
.iter()
.map(|f| f.ident.as_ref().unwrap())
.collect();
let non_skip_names: Vec<_> = fields
.named
.iter()
.filter(|f| !has_skip_attr(&f.attrs).0)
.map(|f| f.ident.as_ref().unwrap())
.collect();
let has_skip = non_skip_names.len() < all_field_names.len();
let mut serialize_fields = Vec::new();
for fname in &non_skip_names {
serialize_fields.push(quote! {
bytes.extend(::afastdata::AFastSerialize::to_bytes(#fname));
});
}
if has_skip {
arms.push(quote! {
#name::#variant_name { #(#non_skip_names),*, .. } => {
bytes.extend((#i as #tag_ty).to_le_bytes());
#(#serialize_fields)*
}
});
} else {
arms.push(quote! {
#name::#variant_name { #(#non_skip_names),* } => {
bytes.extend((#i as #tag_ty).to_le_bytes());
#(#serialize_fields)*
}
});
}
}
}
}
quote! {
impl #impl_generics ::afastdata::AFastSerialize for #name #ty_generics {
fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
match self {
#(#arms)*
}
bytes
}
}
}
}
Data::Union(_) => panic!("AFastSerialize does not support unions"),
};
TokenStream::from(expanded)
}
#[proc_macro_derive(AFastDeserialize, attributes(afast))]
pub fn derive_deserialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let generics = &input.generics;
let mut generics_with_bounds = generics.clone();
for param in &mut generics_with_bounds.params {
if let syn::GenericParam::Type(ref mut ty) = *param {
ty.bounds
.push(syn::parse_quote!(::afastdata::AFastSerialize));
ty.bounds
.push(syn::parse_quote!(::afastdata::AFastDeserialize));
}
}
let (impl_generics, _, _) = generics_with_bounds.split_for_impl();
let (_, ty_generics, _) = generics.split_for_impl();
let expanded = match &input.data {
Data::Struct(data) => {
let (construct, field_desers) =
generate_deserialize_fields(&data.fields, name, &ty_generics);
quote! {
impl #impl_generics ::afastdata::AFastDeserialize for #name #ty_generics {
fn from_bytes(data: &[u8]) -> Result<(Self, usize), ::afastdata::Error> {
let mut offset: usize = 0;
#(#field_desers)*
Ok((#construct, offset))
}
}
}
}
Data::Enum(data) => {
let (tag_ty, _) = tag_type();
let mut arms = Vec::new();
for (i, variant) in data.variants.iter().enumerate() {
let variant_name = &variant.ident;
match &variant.fields {
Fields::Unit => {
arms.push(quote! {
#i => {
Ok((#name::#variant_name, offset))
}
});
}
Fields::Unnamed(fields) => {
let mut field_desers = Vec::new();
let mut field_names = Vec::new();
for (i, f) in fields.unnamed.iter().enumerate() {
let fname = syn::Ident::new(
&format!("__f{}", i),
variant_name.span(),
);
let ftype = &f.ty;
let (skip, default_fn) = has_skip_attr(&f.attrs);
if skip {
if let Some(func_name) = default_fn {
match syn::parse_str::<syn::Ident>(&func_name) {
Ok(ident) => {
field_desers.push(quote! {
let #fname: #ftype = #ident();
});
}
Err(_) => {
field_desers.push(quote! {
compile_error!(concat!("invalid function name in skip: ", #func_name));
});
}
}
} else {
field_desers.push(quote! {
let #fname: #ftype = <#ftype as ::std::default::Default>::default();
});
}
} else {
let validates = parse_validations(&fname, ftype, &f.attrs);
field_desers.push(quote! {
let (__val, __new_offset) = ::afastdata::AFastDeserialize::from_bytes(&data[offset..])?;
let #fname: #ftype = __val;
#(#validates)*
offset += __new_offset;
});
}
field_names.push(fname);
}
arms.push(quote! {
#i => {
#(#field_desers)*
Ok((#name::#variant_name(#(#field_names),*), offset))
}
});
}
Fields::Named(fields) => {
let mut field_desers = Vec::new();
let mut field_names = Vec::new();
for f in &fields.named {
let fname = f.ident.as_ref().unwrap();
let ftype = &f.ty;
let (skip, default_fn) = has_skip_attr(&f.attrs);
if skip {
if let Some(func_name) = default_fn {
match syn::parse_str::<syn::Ident>(&func_name) {
Ok(ident) => {
field_desers.push(quote! {
let #fname: #ftype = #ident();
});
}
Err(_) => {
field_desers.push(quote! {
compile_error!(concat!("invalid function name in skip: ", #func_name));
});
}
}
} else {
field_desers.push(quote! {
let #fname: #ftype = <#ftype as ::std::default::Default>::default();
});
}
} else {
let validates = parse_validations(fname, ftype, &f.attrs);
field_desers.push(quote! {
let (__val, __new_offset) = ::afastdata::AFastDeserialize::from_bytes(&data[offset..])?;
let #fname: #ftype = __val;
#(#validates)*
offset += __new_offset;
});
}
field_names.push(fname);
}
arms.push(quote! {
#i => {
#(#field_desers)*
Ok((#name::#variant_name { #(#field_names),* }, offset))
}
});
}
}
}
quote! {
impl #impl_generics ::afastdata::AFastDeserialize for #name #ty_generics {
fn from_bytes(data: &[u8]) -> Result<(Self, usize), ::afastdata::Error> {
let mut offset: usize = 0;
let (__tag_bytes, __new_offset) = <#tag_ty as ::afastdata::AFastDeserialize>::from_bytes(&data[offset..])?;
offset += __new_offset;
match __tag_bytes as usize {
#(#arms)*
v => Err(::afastdata::Error::deserialize(format!("Unknown variant tag: {} for {}", v, ::std::stringify!(#name)))),
}
}
}
}
}
Data::Union(_) => panic!("AFastDeserialize does not support unions"),
};
TokenStream::from(expanded)
}
fn generate_serialize_fields(
fields: &Fields,
self_prefix: proc_macro2::TokenStream,
) -> Vec<proc_macro2::TokenStream> {
match fields {
Fields::Named(named) => named
.named
.iter()
.filter(|f| !has_skip_attr(&f.attrs).0)
.map(|f| {
let fname = f.ident.as_ref().unwrap();
quote! {
bytes.extend(::afastdata::AFastSerialize::to_bytes(&#self_prefix.#fname));
}
})
.collect(),
Fields::Unnamed(unnamed) => unnamed
.unnamed
.iter()
.enumerate()
.filter(|(_, f)| !has_skip_attr(&f.attrs).0)
.map(|(i, _)| {
let idx = Index::from(i);
quote! {
bytes.extend(::afastdata::AFastSerialize::to_bytes(&#self_prefix.#idx));
}
})
.collect(),
Fields::Unit => vec![],
}
}
fn has_skip_attr(attrs: &[Attribute]) -> (bool, Option<String>) {
for attr in attrs {
if attr.path().is_ident("afast")
&& let Ok(nested) =
attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
{
for meta in nested {
match meta {
Meta::Path(path) if path.is_ident("skip") => {
return (true, None);
}
Meta::List(meta_list) if meta_list.path.is_ident("skip") => {
if let Ok(lit_str) = syn::parse2::<LitStr>(meta_list.tokens.clone()) {
return (true, Some(lit_str.value()));
} else {
return (true, None);
}
}
_ => {}
}
}
}
}
(false, None)
}
enum RangeValue {
Int(LitInt),
Float(LitFloat),
}
impl RangeValue {
fn to_token_stream(&self) -> proc_macro2::TokenStream {
match self {
RangeValue::Int(v) => quote! { #v },
RangeValue::Float(v) => quote! { #v },
}
}
}
struct Range {
value: RangeValue,
_comma1: Token![,],
code: LitInt,
_comma2: Token![,],
msg: LitStr,
}
impl Parse for Range {
fn parse(input: ParseStream) -> syn::Result<Self> {
let value = if input.peek(LitFloat) {
RangeValue::Float(input.parse()?)
} else {
RangeValue::Int(input.parse()?)
};
Ok(Range {
value,
_comma1: input.parse()?,
code: input.parse()?,
_comma2: input.parse()?,
msg: input.parse()?,
})
}
}
struct Length {
min: LitInt,
_comma1: Token![,],
max: LitInt,
_comma2: Token![,],
code: LitInt,
_comma3: Token![,],
msg: LitStr,
}
impl Parse for Length {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Length {
min: input.parse()?,
_comma1: input.parse()?,
max: input.parse()?,
_comma2: input.parse()?,
code: input.parse()?,
_comma3: input.parse()?,
msg: input.parse()?,
})
}
}
#[derive(Clone)]
enum ValidateValue {
Int(i64),
Float(f64),
Bool(bool),
Str(String),
}
impl ValidateValue {
fn to_token_stream(&self) -> proc_macro2::TokenStream {
match self {
ValidateValue::Int(v) => quote! { #v },
ValidateValue::Float(v) => {
let v_str = v.to_string();
v_str.parse().unwrap_or_else(|_| {
quote! { #v }
})
}
ValidateValue::Bool(v) => quote! { #v },
ValidateValue::Str(v) => quote! { #v },
}
}
}
struct OfValidator {
allowed_values: Vec<ValidateValue>,
code: syn::LitInt,
msg: syn::LitStr,
}
impl Parse for OfValidator {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
syn::bracketed!(content in input);
let mut values = Vec::new();
if !content.is_empty() {
loop {
let lit = content.parse::<Lit>()?;
let value = match lit {
Lit::Int(lit_int) => {
let int_value: i64 = lit_int.base10_parse()?;
ValidateValue::Int(int_value)
}
Lit::Float(lit_float) => {
let float_value: f64 = lit_float.base10_parse()?;
ValidateValue::Float(float_value)
}
Lit::Bool(lit_bool) => ValidateValue::Bool(lit_bool.value),
Lit::Str(lit_str) => ValidateValue::Str(lit_str.value()),
_ => {
return Err(syn::Error::new_spanned(
&lit,
"unsupported literal type in 'of' validator; \
only int, float, bool, and str literals are supported",
));
}
};
values.push(value);
if !content.peek(Token![,]) {
break;
}
content.parse::<Token![,]>()?;
if content.is_empty() {
break;
}
}
}
input.parse::<Token![,]>()?;
let code = input.parse::<syn::LitInt>()?;
input.parse::<Token![,]>()?;
let msg = input.parse::<syn::LitStr>()?;
Ok(OfValidator {
allowed_values: values,
code,
msg,
})
}
}
fn is_numeric_type(ty: &Type) -> bool {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
let name = segment.ident.to_string();
return matches!(
name.as_str(),
"i8" | "i16"
| "i32"
| "i64"
| "i128"
| "u8"
| "u16"
| "u32"
| "u64"
| "u128"
| "usize"
| "f32"
| "f64"
);
}
}
false
}
fn is_option_type(ty: &Type) -> bool {
if let Type::Path(TypePath {
path: Path { segments, .. },
..
}) = ty
{
segments.len() == 1 && segments[0].ident == "Option"
} else {
false
}
}
fn extract_option_inner(ty: &Type) -> Option<&Type> {
if let Type::Path(TypePath {
path: Path { segments, .. },
..
}) = ty
{
if segments.len() == 1 && segments[0].ident == "Option" {
if let syn::PathArguments::AngleBracketed(args) = &segments[0].arguments {
if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
return Some(inner);
}
}
}
}
None
}
fn is_comparable_type(ty: &Type) -> bool {
if is_numeric_type(ty) {
return true;
}
if let Some(inner) = extract_option_inner(ty) {
return is_numeric_type(inner);
}
false
}
fn is_collection_type(ty: &Type) -> bool {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
let name = segment.ident.to_string();
return matches!(name.as_str(), "String" | "Vec" | "BTreeSet" | "BTreeMap" | "HashSet" | "HashMap");
}
}
if let Type::Array(_) = ty {
return true;
}
if let Type::Reference(r) = ty {
if let Type::Path(TypePath { path, .. }) = &*r.elem {
if let Some(segment) = path.segments.last() {
return segment.ident == "str";
}
}
}
false
}
fn parse_validations(
field_name: &syn::Ident,
field_type: &Type,
attrs: &[Attribute],
) -> Vec<proc_macro2::TokenStream> {
let mut validates = Vec::new();
for attr in attrs {
if attr.path().is_ident("afast") {
let nested = match attr
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
{
Ok(n) => n,
Err(e) => {
validates.push(e.to_compile_error());
continue;
}
};
for meta in nested {
if let Meta::List(meta) = meta {
if meta.path.is_ident("gt") {
if !is_comparable_type(field_type) {
validates.push(
syn::Error::new_spanned(
&meta.path,
format!(
"validation `gt` is only supported on numeric types or Option<numeric>, but field `{}` is not",
field_name
),
)
.to_compile_error(),
);
continue;
}
let inner = match meta.parse_args::<Range>() {
Ok(v) => v,
Err(e) => {
validates.push(e.to_compile_error());
continue;
}
};
let cmp_value = inner.value.to_token_stream();
let code = match inner.code.base10_parse::<i64>() {
Ok(v) => v,
Err(e) => {
validates.push(
syn::Error::new_spanned(&inner.code, format!("invalid error code: {}", e))
.to_compile_error(),
);
continue;
}
};
let err_msg = inner
.msg
.value()
.replace("${field}", &field_name.to_string());
if is_option_type(field_type) {
validates.push(quote! {
if let Some(ref __val) = #field_name {
if *__val <= #cmp_value {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
}
});
} else {
validates.push(quote! {
if #field_name <= #cmp_value {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
});
}
} else if meta.path.is_ident("gte") {
if !is_comparable_type(field_type) {
validates.push(
syn::Error::new_spanned(
&meta.path,
format!(
"validation `gte` is only supported on numeric types (or Option<numeric>), but field `{}` is not",
field_name
),
)
.to_compile_error(),
);
continue;
}
let inner = match meta.parse_args::<Range>() {
Ok(v) => v,
Err(e) => {
validates.push(e.to_compile_error());
continue;
}
};
let cmp_value = inner.value.to_token_stream();
let code = match inner.code.base10_parse::<i64>() {
Ok(v) => v,
Err(e) => {
validates.push(
syn::Error::new_spanned(&inner.code, format!("invalid error code: {}", e))
.to_compile_error(),
);
continue;
}
};
let err_msg = inner
.msg
.value()
.replace("${field}", &field_name.to_string());
if is_option_type(field_type) {
validates.push(quote! {
if let Some(ref __val) = #field_name {
if *__val < #cmp_value {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
}
});
} else {
validates.push(quote! {
if #field_name < #cmp_value {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
});
}
} else if meta.path.is_ident("lt") {
if !is_comparable_type(field_type) {
validates.push(
syn::Error::new_spanned(
&meta.path,
format!(
"validation `lt` is only supported on numeric types (or Option<numeric>), but field `{}` is not",
field_name
),
)
.to_compile_error(),
);
continue;
}
let inner = match meta.parse_args::<Range>() {
Ok(v) => v,
Err(e) => {
validates.push(e.to_compile_error());
continue;
}
};
let cmp_value = inner.value.to_token_stream();
let code = match inner.code.base10_parse::<i64>() {
Ok(v) => v,
Err(e) => {
validates.push(
syn::Error::new_spanned(&inner.code, format!("invalid error code: {}", e))
.to_compile_error(),
);
continue;
}
};
let err_msg = inner
.msg
.value()
.replace("${field}", &field_name.to_string());
if is_option_type(field_type) {
validates.push(quote! {
if let Some(ref __val) = #field_name {
if *__val >= #cmp_value {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
}
});
} else {
validates.push(quote! {
if #field_name >= #cmp_value {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
});
}
} else if meta.path.is_ident("lte") {
if !is_comparable_type(field_type) {
validates.push(
syn::Error::new_spanned(
&meta.path,
format!(
"validation `lte` is only supported on numeric types (or Option<numeric>), but field `{}` is not",
field_name
),
)
.to_compile_error(),
);
continue;
}
let inner = match meta.parse_args::<Range>() {
Ok(v) => v,
Err(e) => {
validates.push(e.to_compile_error());
continue;
}
};
let cmp_value = inner.value.to_token_stream();
let code = match inner.code.base10_parse::<i64>() {
Ok(v) => v,
Err(e) => {
validates.push(
syn::Error::new_spanned(&inner.code, format!("invalid error code: {}", e))
.to_compile_error(),
);
continue;
}
};
let err_msg = inner
.msg
.value()
.replace("${field}", &field_name.to_string());
if is_option_type(field_type) {
validates.push(quote! {
if let Some(ref __val) = #field_name {
if *__val > #cmp_value {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
}
});
} else {
validates.push(quote! {
if #field_name > #cmp_value {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
});
}
} else if meta.path.is_ident("len") {
let field_is_option = is_option_type(field_type);
if field_is_option {
} else if !is_collection_type(field_type) {
validates.push(
syn::Error::new_spanned(
&meta.path,
format!(
"validation `len` is only supported on String, &[u8], Vec<T>, [T; N], or Option<T> wrapping these types, but field `{}` is not",
field_name
),
)
.to_compile_error(),
);
continue;
}
let inner = match meta.parse_args::<Length>() {
Ok(v) => v,
Err(e) => {
validates.push(e.to_compile_error());
continue;
}
};
let min_value = match inner.min.base10_parse::<i64>() {
Ok(v) => v,
Err(e) => {
validates.push(
syn::Error::new_spanned(&inner.min, format!("invalid min value: {}", e))
.to_compile_error(),
);
continue;
}
};
let max_value = match inner.max.base10_parse::<i64>() {
Ok(v) => v,
Err(e) => {
validates.push(
syn::Error::new_spanned(&inner.max, format!("invalid max value: {}", e))
.to_compile_error(),
);
continue;
}
};
let code = match inner.code.base10_parse::<i64>() {
Ok(v) => v,
Err(e) => {
validates.push(
syn::Error::new_spanned(&inner.code, format!("invalid error code: {}", e))
.to_compile_error(),
);
continue;
}
};
let err_msg = inner
.msg
.value()
.replace("${field}", &field_name.to_string());
if min_value >= 0 && max_value >= 0 && min_value > max_value {
validates.push(
syn::Error::new_spanned(
&meta.path,
format!(
"invalid len validation: min ({}) > max ({}) for field `{}`",
min_value, max_value, field_name
),
)
.to_compile_error(),
);
continue;
}
if min_value < 0 && max_value < 0 {
validates.push(
syn::Error::new_spanned(
&meta.path,
format!(
"invalid len validation: both min and max are negative for field `{}`",
field_name
),
)
.to_compile_error(),
);
continue;
} else if min_value < 0 {
let max: usize = match max_value.try_into() {
Ok(v) => v,
Err(_) => {
validates.push(
syn::Error::new_spanned(&inner.max, "value too large for usize")
.to_compile_error(),
);
continue;
}
};
if field_is_option {
validates.push(quote! {
if let Some(ref __val) = #field_name {
if __val.len() > #max {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
}
});
} else {
validates.push(quote! {
if #field_name.len() > #max {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
});
}
} else if max_value < 0 {
let min: usize = match min_value.try_into() {
Ok(v) => v,
Err(_) => {
validates.push(
syn::Error::new_spanned(&inner.min, "value too large for usize")
.to_compile_error(),
);
continue;
}
};
if field_is_option {
validates.push(quote! {
if let Some(ref __val) = #field_name {
if __val.len() < #min {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
}
});
} else {
validates.push(quote! {
if #field_name.len() < #min {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
});
}
} else {
let min: usize = match min_value.try_into() {
Ok(v) => v,
Err(_) => {
validates.push(
syn::Error::new_spanned(&inner.min, "value too large for usize")
.to_compile_error(),
);
continue;
}
};
let max: usize = match max_value.try_into() {
Ok(v) => v,
Err(_) => {
validates.push(
syn::Error::new_spanned(&inner.max, "value too large for usize")
.to_compile_error(),
);
continue;
}
};
if field_is_option {
validates.push(quote! {
let length = match &#field_name {
Some(s) => {
let __length = s.len();
if __length < #min || __length > #max {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
},
None => {},
};
});
} else {
validates.push(quote! {
if #field_name.len() < #min || #field_name.len() > #max {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
});
}
}
} else if meta.path.is_ident("of") {
let inner = match meta.parse_args::<OfValidator>() {
Ok(v) => v,
Err(e) => {
validates.push(e.to_compile_error());
continue;
}
};
let allowed_values = inner.allowed_values.clone();
let code = match inner.code.base10_parse::<i64>() {
Ok(v) => v,
Err(e) => {
validates.push(
syn::Error::new_spanned(&inner.code, format!("invalid error code: {}", e))
.to_compile_error(),
);
continue;
}
};
let err_msg = inner
.msg
.value()
.replace("${field}", &field_name.to_string());
let values_tokens: Vec<_> = allowed_values
.iter()
.map(|v| v.to_token_stream())
.collect();
validates.push(quote! {
if !matches!(#field_name, #(#values_tokens)|*) {
return Err(::afastdata::Error::validate(#code, #err_msg.to_string()));
}
});
} else if meta.path.is_ident("func") {
let inner = match meta.parse_args::<LitStr>() {
Ok(v) => v,
Err(e) => {
validates.push(e.to_compile_error());
continue;
}
};
let ident = match syn::parse_str::<syn::Ident>(&inner.value()) {
Ok(v) => v,
Err(e) => {
validates.push(
syn::Error::new_spanned(
&inner,
format!("invalid function name `{}`: {}", inner.value(), e),
)
.to_compile_error(),
);
continue;
}
};
let field = field_name.to_string();
validates.push(quote! {
match #ident(&#field_name, #field) {
Ok(()) => {},
Err(e) => return Err(e.to_afastdata_error()),
}
});
}
}
}
}
}
validates
}
fn generate_deserialize_fields(
fields: &Fields,
name: &syn::Ident,
ty_generics: &syn::TypeGenerics,
) -> (proc_macro2::TokenStream, Vec<proc_macro2::TokenStream>) {
let ty_params = ty_generics.as_turbofish();
match fields {
Fields::Named(named) => {
let mut desers = Vec::new();
let mut field_names = Vec::new();
for f in &named.named {
let fname = f.ident.as_ref().unwrap();
let ftype = &f.ty;
field_names.push(fname.clone());
let validates = parse_validations(fname, ftype, &f.attrs);
let (skip, default) = has_skip_attr(&f.attrs);
if skip {
if let Some(default) = default {
match syn::parse_str::<syn::Ident>(&default) {
Ok(ident) => {
desers.push(quote! {
let #fname: #ftype = #ident();
});
}
Err(_) => {
desers.push(quote! {
compile_error!(concat!("invalid function name in skip: ", #default));
});
}
}
} else {
desers.push(quote! {
let #fname: #ftype = #ftype::default();
});
}
} else {
desers.push(quote! {
let (__val, __new_offset) = ::afastdata::AFastDeserialize::from_bytes(&data[offset..])?;
let #fname: #ftype = __val;
#(#validates)*
offset += __new_offset;
});
}
}
let construct = quote! {
#name #ty_params { #(#field_names),* }
};
(construct, desers)
}
Fields::Unnamed(unnamed) => {
let mut desers = Vec::new();
let mut field_names = Vec::new();
for (i, f) in unnamed.unnamed.iter().enumerate() {
let fname = syn::Ident::new(&format!("__f{}", i), name.span());
let ftype = &f.ty;
let (skip, default_fn) = has_skip_attr(&f.attrs);
if skip {
if let Some(func_name) = default_fn {
match syn::parse_str::<syn::Ident>(&func_name) {
Ok(ident) => {
desers.push(quote! {
let #fname: #ftype = #ident();
});
}
Err(_) => {
desers.push(quote! {
compile_error!(concat!("invalid function name in skip: ", #func_name));
});
}
}
} else {
desers.push(quote! {
let #fname: #ftype = <#ftype as ::std::default::Default>::default();
});
}
} else {
let validates = parse_validations(&fname, ftype, &f.attrs);
desers.push(quote! {
let (__val, __new_offset) = ::afastdata::AFastDeserialize::from_bytes(&data[offset..])?;
let #fname: #ftype = __val;
#(#validates)*
offset += __new_offset;
});
}
field_names.push(fname);
}
let construct = quote! {
#name #ty_params ( #(#field_names),* )
};
(construct, desers)
}
Fields::Unit => {
let construct = quote! { #name #ty_params };
(construct, vec![])
}
}
}