1
0
Fork 0
mirror of https://github.com/azalea-rs/simdnbt.git synced 2025-08-02 07:26:04 +00:00

add flatten, deny_unknown_fields, generics, and support more types of HashMap keys

This commit is contained in:
mat 2023-11-19 13:58:28 -06:00
parent 331d135910
commit 57dfc2c1a9
5 changed files with 88 additions and 15 deletions

3
simdnbt-derive/README.md Normal file
View file

@ -0,0 +1,3 @@
Add support for `#[derive(Serialize, Deserialize)]` to sidmnbt.
You can use `#[simdnbt(rename = "Value")]` to have fields be serialized with a name other than the field name.

View file

@ -3,6 +3,12 @@ use syn::parse::{Parse, ParseStream};
#[derive(Default, Debug)]
pub struct FieldAttrs {
pub rename: Option<String>,
pub flatten: bool,
}
#[derive(Default, Debug)]
pub struct StructAttrs {
pub deny_unknown_fields: bool,
}
impl Parse for FieldAttrs {
@ -18,6 +24,27 @@ impl Parse for FieldAttrs {
attrs.rename = Some(rename.value());
}
"flatten" => {
attrs.flatten = true;
}
_ => todo!(),
}
}
Ok(attrs)
}
}
impl Parse for StructAttrs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut attrs = Self::default();
while !input.is_empty() {
let attr = input.parse::<proc_macro2::Ident>()?;
match attr.to_string().as_str() {
"deny_unknown_fields" => {
attrs.deny_unknown_fields = true;
}
_ => todo!(),
}
}
@ -40,3 +67,18 @@ pub fn parse_field_attrs(attrs: &[syn::Attribute]) -> FieldAttrs {
field_attrs
}
pub fn parse_struct_attrs(attrs: &[syn::Attribute]) -> StructAttrs {
let mut struct_attrs = StructAttrs::default();
for attr in attrs.iter().filter(|attr| attr.path().is_ident("simdnbt")) {
let new_attr = attr
.parse_args::<StructAttrs>()
.expect("invalid simdnbt attr");
if new_attr.deny_unknown_fields {
struct_attrs.deny_unknown_fields = true;
}
}
struct_attrs
}

View file

@ -25,11 +25,17 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
.take()
.unwrap_or_else(|| struct_field_name.to_string());
field_deserializers.push(quote! {
#struct_field_name: simdnbt::FromNbtTag::from_optional_nbt_tag(
nbt.take(#field_name)
)?.ok_or(simdnbt::DeserializeError::MismatchedFieldType)?
});
if field_attrs.flatten {
field_deserializers.push(quote! {
#struct_field_name: simdnbt::Deserialize::from_compound(nbt)?,
})
} else {
field_deserializers.push(quote! {
#struct_field_name: simdnbt::FromNbtTag::from_optional_nbt_tag(
nbt.take(#field_name)
)?.ok_or(simdnbt::DeserializeError::MismatchedFieldType)?
});
}
}
}
syn::Fields::Unnamed(_) => todo!(),
@ -39,12 +45,27 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
syn::Data::Union(_) => todo!(),
}
let generics = input.generics;
let struct_attrs = attrs::parse_struct_attrs(&input.attrs);
let extra_checks = if struct_attrs.deny_unknown_fields {
quote! {
if !nbt.is_empty() {
return Err(simdnbt::DeserializeError::UnknownField(nbt.keys().next().unwrap().clone()));
}
}
} else {
quote! {}
};
let output = quote! {
impl simdnbt::Deserialize for #ident {
impl #generics simdnbt::Deserialize for #ident #generics {
fn from_compound(mut nbt: simdnbt::owned::NbtCompound) -> Result<Self, simdnbt::DeserializeError> {
Ok(Self {
let value = Self {
#(#field_deserializers),*
})
};
#extra_checks
Ok(value)
}
}
};
@ -87,8 +108,11 @@ pub fn serialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
syn::Data::Union(_) => todo!(),
}
let generics = input.generics;
let struct_attrs = attrs::parse_struct_attrs(&input.attrs);
let output = quote! {
impl simdnbt::Serialize for #ident {
impl #generics simdnbt::Serialize for #ident #generics {
fn to_compound(self) -> simdnbt::owned::NbtCompound {
let mut nbt = simdnbt::owned::NbtCompound::new();
#(#field_serializers)*

View file

@ -20,4 +20,6 @@ pub enum DeserializeError {
MissingField,
#[error("Mismatched type")]
MismatchedFieldType,
#[error("Unknown fields {0:?}")]
UnknownField(Vec<String>),
}

View file

@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::{collections::HashMap, fmt::Display, hash::Hash, hash::Hasher, str::FromStr};
use crate::DeserializeError;
@ -37,26 +37,28 @@ pub trait ToNbtTag: Sized {
}
}
impl<T: FromNbtTag> Deserialize for HashMap<String, T> {
impl<K: Display + FromStr + Eq + Hash, V: FromNbtTag> Deserialize for HashMap<K, V> {
fn from_compound(compound: crate::owned::NbtCompound) -> Result<Self, DeserializeError> {
let mut hashmap = HashMap::with_capacity(compound.values.len());
for (k, v) in compound.values {
hashmap.insert(
k.to_string(),
T::from_nbt_tag(v).ok_or(DeserializeError::MismatchedFieldType)?,
k.to_str()
.parse()
.map_err(|_| DeserializeError::MismatchedFieldType)?,
V::from_nbt_tag(v).ok_or(DeserializeError::MismatchedFieldType)?,
);
}
Ok(hashmap)
}
}
impl<T: ToNbtTag> Serialize for HashMap<String, T> {
impl<K: Display + FromStr + Eq + Hash, V: ToNbtTag> Serialize for HashMap<K, V> {
fn to_compound(self) -> crate::owned::NbtCompound {
let mut compound = crate::owned::NbtCompound::new();
for (k, v) in self {
compound.insert(k, v.to_nbt_tag());
compound.insert(k.to_string(), v.to_nbt_tag());
}
compound