Add support for Option<struct> in 'attribute' fields

This commit is contained in:
Dmitry Samoylov 2020-02-12 15:45:30 +07:00
parent 264fa06b8b
commit cad7f88c4e
5 changed files with 226 additions and 101 deletions

View File

@ -172,6 +172,53 @@ fn de_attributes() {
);
}
#[test]
fn de_attributes_complex() {
mod other_mod {
use super::*;
#[derive(YaDeserialize, PartialEq, Debug)]
pub enum AttrEnum {
#[yaserde(rename = "variant 1")]
Variant1,
#[yaserde(rename = "variant 2")]
Variant2,
}
impl Default for AttrEnum {
fn default() -> AttrEnum {
AttrEnum::Variant1
}
}
}
#[derive(Default, YaDeserialize, PartialEq, Debug)]
pub struct Struct {
#[yaserde(attribute)]
attr_option_string: Option<std::string::String>,
#[yaserde(attribute)]
attr_option_enum: Option<other_mod::AttrEnum>,
}
convert_and_validate!(
r#"<Struct />"#,
Struct,
Struct {
attr_option_string: None,
attr_option_enum: None
}
);
convert_and_validate!(
r#"<Struct attr_option_string="some value" attr_option_enum="variant 2" />"#,
Struct,
Struct {
attr_option_string: Some("some value".to_string()),
attr_option_enum: Some(other_mod::AttrEnum::Variant2)
}
);
}
#[test]
fn de_rename() {
#[derive(YaDeserialize, PartialEq, Debug)]

View File

@ -124,6 +124,66 @@ fn se_attributes() {
convert_and_validate!(model, content);
}
#[test]
fn se_attributes_complex() {
mod other_mod {
use super::*;
#[derive(YaSerialize, PartialEq, Debug)]
pub enum AttrEnum {
#[yaserde(rename = "variant 1")]
Variant1,
#[yaserde(rename = "variant 2")]
Variant2,
}
impl Default for AttrEnum {
fn default() -> AttrEnum {
AttrEnum::Variant1
}
}
}
#[derive(YaSerialize, PartialEq, Debug)]
pub struct Struct {
#[yaserde(attribute)]
attr_option_string: Option<std::string::String>,
#[yaserde(attribute)]
attr_option_enum: Option<other_mod::AttrEnum>,
}
impl Default for Struct {
fn default() -> Struct {
Struct {
attr_option_string: None,
attr_option_enum: None,
}
}
}
convert_and_validate!(
Struct {
attr_option_string: None,
attr_option_enum: None,
},
r#"
<?xml version="1.0" encoding="utf-8"?>
<Struct />
"#
);
convert_and_validate!(
Struct {
attr_option_string: Some("some value".to_string()),
attr_option_enum: Some(other_mod::AttrEnum::Variant2),
},
r#"
<?xml version="1.0" encoding="utf-8"?>
<Struct attr_option_string="some value" attr_option_enum="variant 2" />
"#
);
}
#[test]
fn ser_rename() {
#[derive(YaSerialize, PartialEq, Debug)]

View File

@ -103,76 +103,51 @@ pub fn parse(
let visitor_label = build_visitor_ident(&label_name, field.span(), None);
get_field_type(field).and_then(|f| match f {
FieldType::FieldTypeStruct { struct_name } => {
let struct_id: String = struct_name
.segments
.iter()
.map(|s| s.ident.to_string())
.collect();
let struct_ident = build_visitor_ident(&label_name, field.span(), Some(&struct_id));
let struct_visitor = |struct_name: syn::Path| {
let struct_id: String = struct_name
.segments
.iter()
.map(|s| s.ident.to_string())
.collect();
Some(quote! {
#[allow(non_snake_case, non_camel_case_types)]
struct #struct_ident;
impl<'de> Visitor<'de> for #struct_ident {
type Value = #struct_name;
let struct_ident = build_visitor_ident(&label_name, field.span(), Some(&struct_name));
fn visit_str(self, v: &str) -> Result<Self::Value, String> {
let content = "<".to_string() + #struct_id + ">" + v + "</" + #struct_id + ">";
let value : Result<#struct_name, String> = yaserde::de::from_str(&content);
value
}
Some(quote! {
#[allow(non_snake_case, non_camel_case_types)]
struct #struct_ident;
impl<'de> Visitor<'de> for #struct_ident {
type Value = #struct_name;
fn visit_str(self, v: &str) -> Result<Self::Value, String> {
let content = "<".to_string() + #struct_id + ">" + v + "</" + #struct_id + ">";
let value : Result<#struct_name, String> = yaserde::de::from_str(&content);
value
}
})
}
FieldType::FieldTypeOption { data_type } => match *data_type {
FieldType::FieldTypeStruct { ref struct_name } => {
let struct_ident = Ident::new(
&format!("{}", struct_name.into_token_stream()),
field.span(),
);
Some(quote! {
#[allow(non_snake_case, non_camel_case_types)]
struct #visitor_label;
impl<'de> Visitor<'de> for #visitor_label {
type Value = #struct_ident;
}
})
}
FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => None,
simple_type => build_declare_visitor(
&get_simple_type_token(&simple_type),
&get_simple_type_visitor(&simple_type),
&visitor_label,
),
},
FieldType::FieldTypeVec { data_type } => match *data_type {
FieldType::FieldTypeStruct { ref struct_name } => {
let struct_ident = Ident::new(
&format!("{}", struct_name.into_token_stream()),
field.span(),
);
Some(quote! {
#[allow(non_snake_case, non_camel_case_types)]
struct #visitor_label;
impl<'de> Visitor<'de> for #visitor_label {
type Value = #struct_ident;
}
})
}
FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => None,
simple_type => build_declare_visitor(
&get_simple_type_token(&simple_type),
&get_simple_type_visitor(&simple_type),
&visitor_label,
),
},
simple_type => build_declare_visitor(
})
};
let simple_type_visitor = |simple_type: FieldType| {
build_declare_visitor(
&get_simple_type_token(&simple_type),
&get_simple_type_visitor(&simple_type),
&visitor_label,
),
)
};
get_field_type(field).and_then(|f| match f {
FieldType::FieldTypeStruct { struct_name } => struct_visitor(struct_name),
FieldType::FieldTypeOption { data_type } => match *data_type {
FieldType::FieldTypeStruct { struct_name } => struct_visitor(struct_name),
FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => None,
simple_type => simple_type_visitor(simple_type),
},
FieldType::FieldTypeVec { data_type } => match *data_type {
FieldType::FieldTypeStruct { struct_name } => struct_visitor(struct_name),
FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => None,
simple_type => simple_type_visitor(simple_type),
},
simple_type => simple_type_visitor(simple_type),
})
})
.filter_map(|x| x)
@ -341,9 +316,14 @@ pub fn parse(
}
}),
FieldType::FieldTypeOption { data_type } => match *data_type {
FieldType::FieldTypeStruct { .. }
| FieldType::FieldTypeOption { .. }
| FieldType::FieldTypeVec { .. } => None,
FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => unimplemented!(),
FieldType::FieldTypeStruct { struct_name } => build_call_visitor_for_attribute(
label,
&label_name,
&quote! {= Some(value) },
&quote! {visit_str},
&build_visitor_ident(&label_name, field.span(), Some(&struct_name)),
),
simple_type => {
let visitor = get_simple_type_visitor(&simple_type);
@ -356,28 +336,13 @@ pub fn parse(
)
}
},
FieldType::FieldTypeStruct { struct_name } => {
let struct_ident = Ident::new(
&format!(
"__Visitor_{}_{}",
label_name,
struct_name.into_token_stream()
),
field.span(),
);
Some(quote! {
for attr in attributes {
if attr.name.local_name == #label_name {
let visitor = #struct_ident{};
match visitor.visit_str(&attr.value) {
Ok(value) => {#label = value;}
Err(msg) => {return Err(msg);}
}
}
}
})
}
FieldType::FieldTypeStruct { struct_name } => build_call_visitor_for_attribute(
label,
&label_name,
&quote! {= value },
&quote! {visit_str},
&build_visitor_ident(&label_name, field.span(), Some(&struct_name)),
),
FieldType::FieldTypeVec { .. } => None,
simple_type => {
let visitor = get_simple_type_visitor(&simple_type);
@ -646,13 +611,20 @@ fn get_value_label(ident: &Option<syn::Ident>) -> Option<syn::Ident> {
.map(|ident| syn::Ident::new(&format!("__{}_value", ident.to_string()), ident.span()))
}
fn build_visitor_ident(label: &str, span: Span, struct_id: Option<&str>) -> Ident {
fn build_visitor_ident(label: &str, span: Span, struct_name: Option<&syn::Path>) -> Ident {
let struct_id = struct_name.map_or_else(
|| "".to_string(),
|struct_name| {
struct_name
.segments
.iter()
.map(|s| s.ident.to_string())
.collect()
},
);
Ident::new(
&format!(
"__Visitor_{}_{}",
label.replace(".", "_"),
struct_id.unwrap_or("")
),
&format!("__Visitor_{}_{}", label.replace(".", "_"), struct_id),
span,
)
}

View File

@ -37,10 +37,10 @@ impl FieldType {
"f32" => Some(FieldType::FieldTypeF32),
"f64" => Some(FieldType::FieldTypeF64),
"Option" => get_sub_type(t).map(|data_type| FieldType::FieldTypeOption {
data_type: Box::new(FieldType::from_ident(&syn::Path::from(data_type)).unwrap()),
data_type: Box::new(FieldType::from_ident(&data_type).unwrap()),
}),
"Vec" => get_sub_type(t).map(|data_type| FieldType::FieldTypeVec {
data_type: Box::new(FieldType::from_ident(&syn::Path::from(data_type)).unwrap()),
data_type: Box::new(FieldType::from_ident(&data_type).unwrap()),
}),
_ => Some(FieldType::FieldTypeStruct {
struct_name: path.clone(),
@ -58,14 +58,12 @@ pub fn get_field_type(field: &syn::Field) -> Option<FieldType> {
}
}
fn get_sub_type(t: &syn::PathSegment) -> Option<syn::PathSegment> {
fn get_sub_type(t: &syn::PathSegment) -> Option<syn::Path> {
if let syn::PathArguments::AngleBracketed(ref args) = t.arguments {
if let Some(tt) = args.args.first() {
if let syn::GenericArgument::Type(ref argument) = *tt {
if let Path(ref path2) = *argument {
if let Some(ttt) = path2.path.segments.first() {
return Some(ttt.clone());
}
if let Path(ref path) = *argument {
return Some(path.path.clone());
}
}
}

View File

@ -176,6 +176,54 @@ pub fn serialize(
})
}
}
FieldType::FieldTypeStruct { .. } => {
if let Some(ref d) = field_attrs.default {
let default_function = Ident::new(&d, field.span());
Some(quote! {
let struct_start_event = if let Some(ref value) = self.#label {
if *value != #default_function() {
struct_start_event.attr(#label_name, &*{
use std::mem;
match yaserde::ser::to_string_content(value) {
Ok(value) => {
unsafe {
let ret : &'static str = mem::transmute(&value as &str);
mem::forget(value);
ret
}
},
Err(msg) => return Err("Unable to serialize content".to_owned()),
}
})
} else {
struct_start_event
}
} else {
struct_start_event
};
})
} else {
Some(quote! {
let struct_start_event = if let Some(ref value) = self.#label {
struct_start_event.attr(#label_name, &*{
use std::mem;
match yaserde::ser::to_string_content(value) {
Ok(value) => {
unsafe {
let ret : &'static str = mem::transmute(&value as &str);
mem::forget(value);
ret
}
},
Err(msg) => return Err("Unable to serialize content".to_owned()),
}
})
} else {
struct_start_event
};
})
}
}
_ => unimplemented!(),
},
FieldType::FieldTypeStruct { .. } => {