Merge pull request #38 from DmitrySamoylov/feature-attribute-option
Add support for Option<struct> in 'attribute' fields
This commit is contained in:
commit
df674965f8
@ -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]
|
#[test]
|
||||||
fn de_rename() {
|
fn de_rename() {
|
||||||
#[derive(YaDeserialize, PartialEq, Debug)]
|
#[derive(YaDeserialize, PartialEq, Debug)]
|
||||||
|
|||||||
@ -124,6 +124,66 @@ fn se_attributes() {
|
|||||||
convert_and_validate!(model, content);
|
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]
|
#[test]
|
||||||
fn ser_rename() {
|
fn ser_rename() {
|
||||||
#[derive(YaSerialize, PartialEq, Debug)]
|
#[derive(YaSerialize, PartialEq, Debug)]
|
||||||
|
|||||||
@ -103,14 +103,14 @@ pub fn parse(
|
|||||||
|
|
||||||
let visitor_label = build_visitor_ident(&label_name, field.span(), None);
|
let visitor_label = build_visitor_ident(&label_name, field.span(), None);
|
||||||
|
|
||||||
get_field_type(field).and_then(|f| match f {
|
let struct_visitor = |struct_name: syn::Path| {
|
||||||
FieldType::FieldTypeStruct { struct_name } => {
|
|
||||||
let struct_id: String = struct_name
|
let struct_id: String = struct_name
|
||||||
.segments
|
.segments
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.ident.to_string())
|
.map(|s| s.ident.to_string())
|
||||||
.collect();
|
.collect();
|
||||||
let struct_ident = build_visitor_ident(&label_name, field.span(), Some(&struct_id));
|
|
||||||
|
let struct_ident = build_visitor_ident(&label_name, field.span(), Some(&struct_name));
|
||||||
|
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
#[allow(non_snake_case, non_camel_case_types)]
|
#[allow(non_snake_case, non_camel_case_types)]
|
||||||
@ -125,54 +125,29 @@ pub fn parse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
FieldType::FieldTypeOption { data_type } => match *data_type {
|
|
||||||
FieldType::FieldTypeStruct { ref struct_name } => {
|
let simple_type_visitor = |simple_type: FieldType| {
|
||||||
let struct_ident = Ident::new(
|
build_declare_visitor(
|
||||||
&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_token(&simple_type),
|
||||||
&get_simple_type_visitor(&simple_type),
|
&get_simple_type_visitor(&simple_type),
|
||||||
&visitor_label,
|
&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::FieldTypeVec { data_type } => match *data_type {
|
||||||
FieldType::FieldTypeStruct { ref struct_name } => {
|
FieldType::FieldTypeStruct { struct_name } => struct_visitor(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,
|
FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => None,
|
||||||
simple_type => build_declare_visitor(
|
simple_type => simple_type_visitor(simple_type),
|
||||||
&get_simple_type_token(&simple_type),
|
|
||||||
&get_simple_type_visitor(&simple_type),
|
|
||||||
&visitor_label,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
simple_type => build_declare_visitor(
|
simple_type => simple_type_visitor(simple_type),
|
||||||
&get_simple_type_token(&simple_type),
|
|
||||||
&get_simple_type_visitor(&simple_type),
|
|
||||||
&visitor_label,
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.filter_map(|x| x)
|
.filter_map(|x| x)
|
||||||
@ -341,9 +316,14 @@ pub fn parse(
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
FieldType::FieldTypeOption { data_type } => match *data_type {
|
FieldType::FieldTypeOption { data_type } => match *data_type {
|
||||||
FieldType::FieldTypeStruct { .. }
|
FieldType::FieldTypeOption { .. } | FieldType::FieldTypeVec { .. } => unimplemented!(),
|
||||||
| FieldType::FieldTypeOption { .. }
|
FieldType::FieldTypeStruct { struct_name } => build_call_visitor_for_attribute(
|
||||||
| FieldType::FieldTypeVec { .. } => None,
|
label,
|
||||||
|
&label_name,
|
||||||
|
"e! {= Some(value) },
|
||||||
|
"e! {visit_str},
|
||||||
|
&build_visitor_ident(&label_name, field.span(), Some(&struct_name)),
|
||||||
|
),
|
||||||
simple_type => {
|
simple_type => {
|
||||||
let visitor = get_simple_type_visitor(&simple_type);
|
let visitor = get_simple_type_visitor(&simple_type);
|
||||||
|
|
||||||
@ -356,28 +336,13 @@ pub fn parse(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FieldType::FieldTypeStruct { struct_name } => {
|
FieldType::FieldTypeStruct { struct_name } => build_call_visitor_for_attribute(
|
||||||
let struct_ident = Ident::new(
|
label,
|
||||||
&format!(
|
&label_name,
|
||||||
"__Visitor_{}_{}",
|
"e! {= value },
|
||||||
label_name,
|
"e! {visit_str},
|
||||||
struct_name.into_token_stream()
|
&build_visitor_ident(&label_name, field.span(), Some(&struct_name)),
|
||||||
),
|
),
|
||||||
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::FieldTypeVec { .. } => None,
|
FieldType::FieldTypeVec { .. } => None,
|
||||||
simple_type => {
|
simple_type => {
|
||||||
let visitor = get_simple_type_visitor(&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()))
|
.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(
|
Ident::new(
|
||||||
&format!(
|
&format!("__Visitor_{}_{}", label.replace(".", "_"), struct_id),
|
||||||
"__Visitor_{}_{}",
|
|
||||||
label.replace(".", "_"),
|
|
||||||
struct_id.unwrap_or("")
|
|
||||||
),
|
|
||||||
span,
|
span,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,10 +37,10 @@ impl FieldType {
|
|||||||
"f32" => Some(FieldType::FieldTypeF32),
|
"f32" => Some(FieldType::FieldTypeF32),
|
||||||
"f64" => Some(FieldType::FieldTypeF64),
|
"f64" => Some(FieldType::FieldTypeF64),
|
||||||
"Option" => get_sub_type(t).map(|data_type| FieldType::FieldTypeOption {
|
"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 {
|
"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 {
|
_ => Some(FieldType::FieldTypeStruct {
|
||||||
struct_name: path.clone(),
|
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 syn::PathArguments::AngleBracketed(ref args) = t.arguments {
|
||||||
if let Some(tt) = args.args.first() {
|
if let Some(tt) = args.args.first() {
|
||||||
if let syn::GenericArgument::Type(ref argument) = *tt {
|
if let syn::GenericArgument::Type(ref argument) = *tt {
|
||||||
if let Path(ref path2) = *argument {
|
if let Path(ref path) = *argument {
|
||||||
if let Some(ttt) = path2.path.segments.first() {
|
return Some(path.path.clone());
|
||||||
return Some(ttt.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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!(),
|
_ => unimplemented!(),
|
||||||
},
|
},
|
||||||
FieldType::FieldTypeStruct { .. } => {
|
FieldType::FieldTypeStruct { .. } => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user