Compare commits

...

11 Commits

Author SHA1 Message Date
Jean-Gabriel Gill-Couture
353558737f wip: Strict deserialization returns error on any unknown xml field or attribute 2024-10-14 15:03:34 -04:00
Marc-Antoine Arnaud
5cd09186fa chore: Release 2024-09-16 18:46:12 +02:00
Marc-Antoine Arnaud
8c7926f199 Merge branch 'lessu-nesting' 2024-09-16 18:41:17 +02:00
limingyi
890fdb5629 fix: fix nesting struct parsing bug
issue #192

Signed-off-by: limingyi <lessu@163.com>
2024-09-16 18:40:59 +02:00
Marc-Antoine Arnaud
e783c29c47 chore: Release 2024-08-31 17:01:30 +02:00
Marc-Antoine Arnaud
b89a55632b feat: update heck to 0.5 2024-08-31 17:00:49 +02:00
Marc-Antoine Arnaud
a067b85ee2 feat: handle same field name but different namespace
issue #186
2024-08-31 16:54:53 +02:00
Marc-Antoine Arnaud
e4aff84acb refactor: remove std:: prefix as clippy suggested 2024-06-17 21:53:41 +02:00
Marc-Antoine Arnaud
3e416bedcc chore: Release 2024-06-05 08:09:14 +02:00
Marc-Antoine Arnaud
f58d1d0de5 Merge branch 'xfront-main' 2024-06-05 08:07:40 +02:00
huangjf
58d81c7a87 feat: support generic 2024-06-05 08:07:07 +02:00
23 changed files with 629 additions and 154 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "yaserde-examples"
version = "0.10.0"
version = "0.11.1"
authors = ["Marc-Antoine Arnaud <maarnaud@media-io.com>"]
license = "MIT"
edition = "2018"
@ -8,4 +8,4 @@ description = "Examples for YaSerDe project"
documentation = "https://docs.rs/yaserde"
[dependencies]
yaserde = {version = "0.10.0", path = "../yaserde", features = ["yaserde_derive"] }
yaserde = {version = "0.11.1", path = "../yaserde", features = ["yaserde_derive"] }

View File

@ -10,7 +10,7 @@ use yaserde::*;
namespace = "html: http://www.w3.org/TR/REC-html40"
)]
struct Workbook {
#[yaserde(rename = "Worksheet")]
#[yaserde(rename = "Worksheet", prefix = "ss")]
worksheet: Worksheet,
}
@ -23,7 +23,7 @@ struct Workbook {
namespace = "html: http://www.w3.org/TR/REC-html40"
)]
struct Worksheet {
#[yaserde(rename = "Table")]
#[yaserde(rename = "Table", prefix = "ss")]
table: Table,
#[yaserde(attribute, rename = "Name", prefix = "ss")]
ws_name: String,
@ -53,7 +53,7 @@ struct Table {
#[yaserde(attribute, rename = "DefaultRowHeight", prefix = "ss")]
default_column_height: f32,
#[yaserde(rename = "Row")]
#[yaserde(rename = "Row", prefix = "ss")]
rows: Vec<Row>,
}

102
examples/src/generic.rs Normal file
View File

@ -0,0 +1,102 @@
use yaserde::*;
#[derive(YaSerialize, YaDeserialize, Debug, Default, Clone, Eq, PartialEq)]
pub struct Header {}
#[derive(YaSerialize, YaDeserialize, Debug, Default, Clone, Eq, PartialEq)]
#[yaserde(
rename = "Envelope",
namespace = "s: http://schemas.xmlsoap.org/soap/envelope/",
prefix = "s"
)]
pub struct SoapEnvelope<BODY>
where
BODY: YaSerialize + YaDeserialize + Default,
{
#[yaserde(rename = "encodingStyle", prefix = "s", attribute)]
pub encoding_style: String,
#[yaserde(rename = "u", prefix = "xmlns", attribute)]
pub tnsattr: Option<String>,
#[yaserde(rename = "urn", prefix = "xmlns", attribute)]
pub urnattr: Option<String>,
#[yaserde(rename = "xsi", prefix = "xmlns", attribute)]
pub xsiattr: Option<String>,
#[yaserde(rename = "Header", prefix = "s")]
pub header: Option<Header>,
#[yaserde(rename = "Body", prefix = "s")]
pub body: BODY,
}
#[derive(YaSerialize, YaDeserialize, Debug, Default, Clone, Eq, PartialEq)]
#[yaserde(namespace = "u: urn:schemas-upnp-org:service:AVTransport:1")]
pub struct SoapPlay {
#[yaserde(rename = "Play", prefix = "u", default)]
pub body: Play,
}
#[derive(YaSerialize, YaDeserialize, Debug, Default, Clone, Eq, PartialEq)]
#[yaserde(rename = "Play", prefix = "u")]
pub struct Play {
#[yaserde(flatten, default)]
pub parameters: Play2,
}
#[derive(YaSerialize, YaDeserialize, Debug, Default, Clone, Eq, PartialEq)]
#[yaserde(
rename = "Play",
namespace = "u: urn:schemas-upnp-org:service:AVTransport:1",
prefix = "u"
)]
pub struct Play2 {
#[yaserde(rename = "InstanceID", default)]
pub instance_id: i32,
#[yaserde(rename = "Speed", default)]
pub speed: i32,
}
#[derive(PrimitiveYaSerde, Debug, Default, Eq, PartialEq)]
struct Meters(i32);
#[test]
fn test_for_generic_newtype() {
let a = SoapEnvelope {
encoding_style: "".to_string(),
tnsattr: None,
urnattr: None,
xsiattr: None,
header: None,
body: Meters(10),
};
let s = ser::to_string(&a).unwrap();
let b: SoapEnvelope<Meters> = de::from_str(&s).unwrap();
assert_eq!(a, b);
println!("{:#?}", b);
}
#[test]
fn test_for_generic_nested_struct() {
let a = SoapEnvelope {
encoding_style: "".to_string(),
tnsattr: None,
urnattr: None,
xsiattr: None,
header: None,
body: SoapPlay {
body: Play {
parameters: Play2 {
instance_id: 20,
speed: 1,
},
},
},
};
let s = ser::to_string(&a).unwrap();
println!("{s}");
let b: SoapEnvelope<SoapPlay> = de::from_str(&s).unwrap();
assert_eq!(a, b);
println!("{:#?}", b);
}

View File

@ -1,4 +1,6 @@
mod bbigras_namespace;
mod boscop;
mod generic;
mod ln_dom;
mod same_element_different_namespaces;
mod svd;

View File

@ -0,0 +1,37 @@
// related to issue https://github.com/media-io/yaserde/issues/186
use yaserde::*;
#[derive(YaDeserialize, Debug, PartialEq)]
#[yaserde(
namespace = "myns: http://my_namespace_1/",
namespace = "ext: http://my_namespace_2/",
prefix = "myns"
)]
pub struct ErrorType {
#[yaserde(rename = "reasonCode", prefix = "myns")]
pub reason_code: Option<u16>,
#[yaserde(rename = "reasonCode", prefix = "ext")]
pub ext_reason_code: Option<u16>,
}
#[test]
fn same_element_different_namespaces() {
use yaserde::de::from_str;
let content = r#"
<error_type xmlns="http://my_namespace_1/" xmlns:ext="http://my_namespace_2/">
<reasonCode>12</reasonCode>
<ext:reasonCode>32</ext:reasonCode>
</error_type>
"#;
let loaded: ErrorType = from_str(content).unwrap();
println!("{:?}", loaded);
let reference = ErrorType {
reason_code: Some(12),
ext_reason_code: Some(32),
};
assert_eq!(loaded, reference);
}

View File

@ -1,124 +1,123 @@
use yaserde::YaSerialize;
#[derive(PartialEq, Debug, YaSerialize)]
struct CpuDef {
#[yaserde(child)]
name: String,
#[yaserde(child)]
revision: String,
#[yaserde(child)]
endian: String, // enum {LE, BE, ME}
#[yaserde(child)]
mpupresent: bool,
#[yaserde(child)]
fpupresent: bool,
//#[yaserde(child)]
//nvicpriobits: enum {8, 16, 32, 64, 128},
#[yaserde(child)]
vendorsystickconfig: bool,
}
#[derive(PartialEq, Debug, YaSerialize)]
struct Field {
name: String,
#[yaserde(child)]
description: String,
#[yaserde(child)]
bitrange: String,
#[yaserde(child)]
access: String,
}
#[derive(PartialEq, Debug, YaSerialize)]
struct Register {
#[yaserde(child)]
name: String,
#[yaserde(child)]
description: String,
#[yaserde(child)]
addressoffset: String,
#[yaserde(child)]
size: u8,
#[yaserde(child)]
access: String,
#[yaserde(child)]
resetvalue: String,
#[yaserde(child)]
resetmask: String,
#[yaserde(child)]
fields: Vec<Field>,
}
#[derive(PartialEq, Debug, YaSerialize)]
struct Peripheral {
#[yaserde(child)]
name: String,
#[yaserde(child)]
version: String,
#[yaserde(child)]
description: String,
#[yaserde(child)]
groupname: String,
#[yaserde(child)]
baseaddress: String,
#[yaserde(child)]
size: u8,
#[yaserde(child)]
access: String,
#[yaserde(child)]
registers: Vec<Register>,
}
#[derive(PartialEq, Debug, YaSerialize)]
struct DevAttrs {
#[yaserde(child)]
vendor: String,
#[yaserde(child)]
vendorid: String,
#[yaserde(child)]
name: String,
#[yaserde(child)]
series: String,
#[yaserde(child)]
version: String,
#[yaserde(child)]
description: String,
#[yaserde(child)]
licensetext: String,
#[yaserde(child)]
cpu: CpuDef,
#[yaserde(child)]
addressunitbits: u8,
#[yaserde(child)]
width: u8,
#[yaserde(child)]
size: u8,
#[yaserde(child)]
access: String,
#[yaserde(child)]
resetvalue: String,
#[yaserde(child)]
resetmask: String,
#[yaserde(child)]
peripherals: Vec<Peripheral>,
}
#[derive(PartialEq, Debug, YaSerialize)]
#[yaserde(rename = "device")]
struct Device {
#[yaserde(attribute)]
schemaversion: String,
#[yaserde(attribute)]
xmlns: String,
#[yaserde(attribute)]
xsnonamespaceschemalocation: String,
#[yaserde(child)]
devattributes: DevAttrs,
}
#[test]
fn parsing_svd() {
use std::fs;
use yaserde::YaSerialize;
#[derive(PartialEq, Debug, YaSerialize)]
struct CpuDef {
#[yaserde(child)]
name: String,
#[yaserde(child)]
revision: String,
#[yaserde(child)]
endian: String, // enum {LE, BE, ME}
#[yaserde(child)]
mpupresent: bool,
#[yaserde(child)]
fpupresent: bool,
//#[yaserde(child)]
//nvicpriobits: enum {8, 16, 32, 64, 128},
#[yaserde(child)]
vendorsystickconfig: bool,
}
#[derive(PartialEq, Debug, YaSerialize)]
struct Field {
name: String,
#[yaserde(child)]
description: String,
#[yaserde(child)]
bitrange: String,
#[yaserde(child)]
access: String,
}
#[derive(PartialEq, Debug, YaSerialize)]
struct Register {
#[yaserde(child)]
name: String,
#[yaserde(child)]
description: String,
#[yaserde(child)]
addressoffset: String,
#[yaserde(child)]
size: u8,
#[yaserde(child)]
access: String,
#[yaserde(child)]
resetvalue: String,
#[yaserde(child)]
resetmask: String,
#[yaserde(child)]
fields: Vec<Field>,
}
#[derive(PartialEq, Debug, YaSerialize)]
struct Peripheral {
#[yaserde(child)]
name: String,
#[yaserde(child)]
version: String,
#[yaserde(child)]
description: String,
#[yaserde(child)]
groupname: String,
#[yaserde(child)]
baseaddress: String,
#[yaserde(child)]
size: u8,
#[yaserde(child)]
access: String,
#[yaserde(child)]
registers: Vec<Register>,
}
#[derive(PartialEq, Debug, YaSerialize)]
struct DevAttrs {
#[yaserde(child)]
vendor: String,
#[yaserde(child)]
vendorid: String,
#[yaserde(child)]
name: String,
#[yaserde(child)]
series: String,
#[yaserde(child)]
version: String,
#[yaserde(child)]
description: String,
#[yaserde(child)]
licensetext: String,
#[yaserde(child)]
cpu: CpuDef,
#[yaserde(child)]
addressunitbits: u8,
#[yaserde(child)]
width: u8,
#[yaserde(child)]
size: u8,
#[yaserde(child)]
access: String,
#[yaserde(child)]
resetvalue: String,
#[yaserde(child)]
resetmask: String,
#[yaserde(child)]
peripherals: Vec<Peripheral>,
}
#[derive(PartialEq, Debug, YaSerialize)]
#[yaserde(rename = "device")]
struct Device {
#[yaserde(attribute)]
schemaversion: String,
#[yaserde(attribute)]
xmlns: String,
#[yaserde(attribute)]
xsnonamespaceschemalocation: String,
#[yaserde(child)]
devattributes: DevAttrs,
}
let register = Register {
name: "PRCMD".to_string(),

View File

@ -1,6 +1,6 @@
[package]
name = "yaserde"
version = "0.10.0"
version = "0.11.1"
authors = ["Marc-Antoine Arnaud <arnaud.marcantoine@gmail.com>"]
description = "Serialization and deserialization library"
keywords = ["Serialization", "Deserialization", "XML"]
@ -12,13 +12,13 @@ readme = "../README.md"
edition = "2018"
[dependencies]
yaserde_derive = { version = "0.10.0", path = "../yaserde_derive", optional = true }
yaserde_derive = { version = "0.11.1", path = "../yaserde_derive", optional = true }
xml-rs = "0.8.3"
log = "0.4"
[dev-dependencies]
env_logger = "0.11.0"
yaserde_derive = { version = "0.10.0", path = "../yaserde_derive" }
yaserde_derive = { version = "0.11.1", path = "../yaserde_derive" }
[badges]
travis-ci = { repository = "media-io/yaserde" }

View File

@ -94,6 +94,7 @@ use std::io::{Read, Write};
use xml::writer::XmlEvent;
pub mod de;
pub mod primitives;
pub mod ser;
/// A **data structure** that can be deserialized from any data format supported by YaSerDe.

51
yaserde/src/primitives.rs Normal file
View File

@ -0,0 +1,51 @@
use std::{io::Read, io::Write};
use crate::{de, ser};
pub fn serialize_primitives<S, W: Write>(
self_bypass: &S,
default_name: &str,
writer: &mut ser::Serializer<W>,
serialize_function: impl FnOnce(&S) -> String,
) -> Result<(), String> {
let name = writer
.get_start_event_name()
.unwrap_or_else(|| default_name.to_string());
if !writer.skip_start_end() {
writer
.write(xml::writer::XmlEvent::start_element(name.as_str()))
.map_err(|_e| format!("Start element {name:?} write failed"))?;
}
writer
.write(xml::writer::XmlEvent::characters(
serialize_function(self_bypass).as_str(),
))
.map_err(|_e| format!("Element value {name:?} write failed"))?;
if !writer.skip_start_end() {
writer
.write(xml::writer::XmlEvent::end_element())
.map_err(|_e| format!("End element {name:?} write failed"))?;
}
Ok(())
}
pub fn deserialize_primitives<S, R: Read>(
reader: &mut de::Deserializer<R>,
deserialize_function: impl FnOnce(&str) -> Result<S, String>,
) -> Result<S, String> {
if let Ok(xml::reader::XmlEvent::StartElement { .. }) = reader.peek() {
reader.next_event()?;
} else {
return Err("Start element not found".to_string());
}
if let Ok(xml::reader::XmlEvent::Characters(ref text)) = reader.peek() {
deserialize_function(text)
} else {
deserialize_function("")
}
}

View File

@ -1117,3 +1117,20 @@ fn de_nested_macro_rules() {
float_attrs!(f32);
}
#[test]
fn de_strict() {
init();
#[derive(PartialEq, Debug, YaDeserialize)]
pub struct Struct {
id: i32,
}
let xml_content = r#"<?xml version="1.0" encoding="utf-8"?>
<Struct>
<id>123</id>
<NonExistentAttrShouldCrash></NonExistentAttrShouldCrash>
</Struct>"#;
let load: Result<Struct, String> = from_str(xml_content);
assert!(load.is_err());
}

41
yaserde/tests/generic.rs Normal file
View File

@ -0,0 +1,41 @@
#[macro_use]
extern crate yaserde;
use yaserde::{YaDeserialize, YaSerialize};
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
fn generic() {
init();
#[derive(Debug, PartialEq, YaDeserialize, YaSerialize)]
#[yaserde(rename = "base")]
pub struct Base<G>
where
G: YaSerialize + YaDeserialize + Default,
{
background: G,
}
#[derive(Debug, Default, PartialEq, YaDeserialize, YaSerialize)]
pub struct Generic {
#[yaserde(attribute)]
color: String,
}
let content = r#"<base><background color="blue" /></base>"#;
let model = Base {
background: Generic {
color: "blue".to_string(),
},
};
serialize_and_validate!(model, content);
log::debug!("deserialize_and_validate @ {}:{}", file!(), line!());
let loaded: Result<Base<Generic>, String> = yaserde::de::from_str(content);
assert_eq!(loaded, Ok(model));
}

View File

@ -47,7 +47,7 @@ fn skip_serializing_if_for_struct() {
}
fn check_f32_function(&self, value: &f32) -> bool {
(value - 0.0).abs() < std::f32::EPSILON
(value - 0.0).abs() < f32::EPSILON
}
}
@ -104,7 +104,7 @@ fn skip_serializing_if_for_struct_attributes() {
}
fn check_f32_function(&self, value: &f32) -> bool {
(value - 0.0).abs() < std::f32::EPSILON
(value - 0.0).abs() < f32::EPSILON
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "yaserde_derive"
version = "0.10.0"
version = "0.11.1"
authors = ["Marc-Antoine Arnaud <arnaud.marcantoine@gmail.com>"]
description = "Serialization and deserialization macros"
keywords = ["Serialization", "Deserialization"]
@ -12,7 +12,7 @@ readme = "../README.md"
edition = "2018"
[dependencies]
heck = "0.4.0"
heck = "0.5"
syn = { version = "~1.0", features = ["visit", "extra-traits"] }
proc-macro2 = "~1.0"
quote = "~1.0"

View File

@ -90,6 +90,13 @@ impl YaSerdeField {
},
);
let prefix = self
.attributes
.prefix
.clone()
.map(|p| format!("{}_", p.to_upper_camel_case()))
.unwrap_or_default();
let attribute = self
.attributes
.attribute
@ -98,7 +105,8 @@ impl YaSerdeField {
Ident::new(
&format!(
"__Visitor_{attribute}{}_{}",
"__Visitor_{attribute}{}{}_{}",
prefix,
label.replace('.', "_").to_upper_camel_case(),
struct_id
),
@ -130,6 +138,20 @@ impl YaSerdeField {
.map(|skip_serializing_if| Ident::new(skip_serializing_if, self.get_span()))
}
pub fn prefix_namespace(&self, root_attributes: &YaSerdeAttribute) -> String {
root_attributes
.namespaces
.iter()
.find_map(|(prefix, namespace)| {
if self.attributes.prefix.eq(prefix) {
Some(namespace.clone())
} else {
None
}
})
.unwrap_or_default()
}
pub fn get_namespace_matching(
&self,
root_attributes: &YaSerdeAttribute,
@ -261,7 +283,7 @@ impl From<&syn::PathSegment> for Field {
return Field::from(&path.path);
}
Some(syn::GenericArgument::Type(syn::Type::Group(syn::TypeGroup { elem, .. }))) => {
if let syn::Type::Path(ref group) = elem.as_ref() {
if let Path(ref group) = elem.as_ref() {
return Field::from(&group.path);
}
}

View File

@ -1,13 +1,14 @@
use crate::common::{Field, YaSerdeAttribute, YaSerdeField};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{DataEnum, Fields, Ident};
use syn::{DataEnum, Fields, Generics, Ident};
pub fn parse(
data_enum: &DataEnum,
name: &Ident,
root: &str,
root_attributes: &YaSerdeAttribute,
generics: &Generics,
) -> TokenStream {
let namespaces_matching = root_attributes.get_namespace_matching(
&None,
@ -23,6 +24,7 @@ pub fn parse(
.collect();
let flatten = root_attributes.flatten;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let element_name = if let Some(tag) = &root_attributes.tag {
quote! {
@ -39,7 +41,7 @@ pub fn parse(
};
quote! {
impl ::yaserde::YaDeserialize for #name {
impl #impl_generics ::yaserde::YaDeserialize for #name #ty_generics #where_clause {
#[allow(unused_variables)]
fn deserialize<R: ::std::io::Read>(
reader: &mut ::yaserde::de::Deserializer<R>,

View File

@ -2,13 +2,15 @@ use super::build_default_value::{build_default_value, build_default_vec_value};
use crate::common::{Field, YaSerdeAttribute, YaSerdeField};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{DataStruct, Ident};
use syn::{DataStruct, Generics, Ident};
pub fn parse(
data_struct: &DataStruct,
name: &Ident,
root_namespace: &str,
root: &str,
root_attributes: &YaSerdeAttribute,
generics: &Generics,
) -> TokenStream {
let namespaces_matching = root_attributes.get_namespace_matching(
&None,
@ -49,6 +51,17 @@ pub fn parse(
.fields
.iter()
.map(|field| YaSerdeField::new(field.clone()))
.filter(|field| {
if field.is_attribute() {
return true;
};
match field.get_type() {
Field::FieldVec { data_type } => !matches!(*data_type, Field::FieldStruct { .. }),
Field::FieldOption { data_type } => !matches!(*data_type, Field::FieldStruct { .. }),
Field::FieldStruct { .. } => false,
_ => true,
}
})
.filter_map(|field| {
let struct_visitor = |struct_name: syn::Path| {
let struct_id: String = struct_name
@ -136,9 +149,11 @@ pub fn parse(
let value_label = field.get_value_label();
let label_name = field.renamed_label_without_namespace();
let namespace = field.prefix_namespace(root_attributes);
let visit_struct = |struct_name: syn::Path, action: TokenStream| {
Some(quote! {
#label_name => {
(#namespace, #label_name) => {
if depth == 0 {
// Don't count current struct's StartElement as substruct's StartElement
let _root = reader.next_event();
@ -369,9 +384,10 @@ pub fn parse(
};
let flatten = root_attributes.flatten;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
impl ::yaserde::YaDeserialize for #name {
impl #impl_generics ::yaserde::YaDeserialize for #name #ty_generics #where_clause {
#[allow(unused_variables)]
fn deserialize<R: ::std::io::Read>(
reader: &mut ::yaserde::de::Deserializer<R>,
@ -404,15 +420,20 @@ pub fn parse(
);
match event {
::yaserde::__xml::reader::XmlEvent::StartElement{ref name, ref attributes, ..} => {
if depth == 0 && name.local_name == #root {
let namespace = name.namespace.clone().unwrap_or_default();
if depth == 0 && name.local_name == #root && namespace.as_str() == #root_namespace {
// Consume root element. We must do this first. In the case it shares a name with a child element, we don't
// want to prematurely match the child element below.
let event = reader.next_event()?;
#write_unused
} else {
match name.local_name.as_str() {
match (namespace.as_str(), name.local_name.as_str()) {
#call_visitors
_ => {
::yaserde::__derive_trace!("SKIPPINGSKIPPING Skipping element {:?}", name.local_name);
return Err(format!("Found unauthorized element {}", name.local_name));
let event = reader.next_event()?;
#write_unused
@ -480,8 +501,10 @@ fn build_call_visitor(
quote!(name.local_name.as_str()),
);
let namespace = field.prefix_namespace(root_attributes);
Some(quote! {
#label_name => {
(#namespace, #label_name) => {
let visitor = #visitor_label{};
#namespaces_matching

View File

@ -10,21 +10,34 @@ pub fn expand_derive_deserialize(ast: &syn::DeriveInput) -> Result<TokenStream,
let name = &ast.ident;
let attrs = &ast.attrs;
let data = &ast.data;
let generics = &ast.generics;
let root_attributes = YaSerdeAttribute::parse(attrs);
let root_name = format!(
"{}{}",
root_attributes.prefix_namespace(),
root_attributes.xml_element_name(name)
);
let root_name = root_attributes.xml_element_name(name);
let root_namespace = root_attributes
.namespaces
.iter()
.find_map(|(prefix, namespace)| {
if root_attributes.prefix.eq(prefix) {
Some(namespace.clone())
} else {
None
}
})
.unwrap_or_default();
let impl_block = match *data {
syn::Data::Struct(ref data_struct) => {
expand_struct::parse(data_struct, name, &root_name, &root_attributes)
}
syn::Data::Struct(ref data_struct) => expand_struct::parse(
data_struct,
name,
&root_namespace,
&root_name,
&root_attributes,
generics,
),
syn::Data::Enum(ref data_enum) => {
expand_enum::parse(data_enum, name, &root_name, &root_attributes)
expand_enum::parse(data_enum, name, &root_name, &root_attributes, generics)
}
syn::Data::Union(ref _data_union) => unimplemented!(),
};

View File

@ -5,9 +5,13 @@ extern crate proc_macro;
mod common;
mod de;
mod primitives;
mod ser;
use primitives::{hexbinary_serde, primitive_serde, primitive_yaserde};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
#[proc_macro_derive(YaDeserialize, attributes(yaserde))]
pub fn derive_deserialize(input: TokenStream) -> TokenStream {
@ -26,3 +30,37 @@ pub fn derive_serialize(input: TokenStream) -> TokenStream {
Err(msg) => panic!("{}", msg),
}
}
// Serialize & Deserialize a struct using it's UpperHex implementation
#[proc_macro_derive(HexBinaryYaSerde)]
pub fn derive_hexbinary(input: TokenStream) -> TokenStream {
let serde: TokenStream2 = hexbinary_serde(input.clone()).into();
let yaserde: TokenStream2 = primitive_yaserde(input).into();
quote! {
use ::std::str::FromStr as _;
#serde
#yaserde
}
.into()
}
// Serialize & Deserialize a primitive newtype by generating a FromStr & Display implementation
#[proc_macro_derive(PrimitiveYaSerde)]
pub fn derive_primitive(input: TokenStream) -> TokenStream {
let serde: TokenStream2 = primitive_serde(input.clone()).into();
let yaserde: TokenStream2 = primitive_yaserde(input).into();
quote! {
use ::std::str::FromStr as _;
#serde
#yaserde
}
.into()
}
// Serialize & Deserialize a type using it's existing FromStr & Display implementation
#[proc_macro_derive(DefaultYaSerde)]
pub fn derive_default(input: TokenStream) -> TokenStream {
primitive_yaserde(input)
}

View File

@ -0,0 +1,118 @@
// Adds YaSerialize and YaDeserialize implementations for types that support FromStr and Display traits.
// Code originally from `xsd-parser-rs`
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn primitive_yaserde(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let struct_name = &ast.ident;
let struct_name_literal = &ast.ident.to_string();
let serde = quote! {
impl ::yaserde::YaSerialize for #struct_name {
fn serialize<W: ::std::io::Write>(
&self,
writer: &mut ::yaserde::ser::Serializer<W>,
) -> ::std::result::Result<(), ::std::string::String> {
::yaserde::primitives::serialize_primitives(
self,
#struct_name_literal,
writer, |s| s.to_string(),
)
}
fn serialize_attributes(
&self,
attributes: ::std::vec::Vec<::yaserde::__xml::attribute::OwnedAttribute>,
namespace: ::yaserde::__xml::namespace::Namespace,
) -> ::std::result::Result<
(
::std::vec::Vec<::yaserde::__xml::attribute::OwnedAttribute>,
::yaserde::__xml::namespace::Namespace,
),
::std::string::String,
> {
Ok((attributes, namespace))
}
}
impl ::yaserde::YaDeserialize for #struct_name {
fn deserialize<R: ::std::io::Read>(
reader: &mut ::yaserde::de::Deserializer<R>,
) -> ::std::result::Result<Self, ::std::string::String> {
::yaserde::primitives::deserialize_primitives(
reader,
|s| #struct_name::from_str(s).map_err(|e| e.to_string()),
)
}
}
};
serde.into()
}
pub fn hexbinary_serde(input: TokenStream) -> TokenStream {
let first = input.clone();
let DeriveInput { ident, .. } = parse_macro_input!(first);
// Calculate number digits to determine whether leading zero should be added
quote! {
impl std::fmt::Display for #ident {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02X}", self.0)
}
}
impl ::std::str::FromStr for #ident {
type Err = ::std::string::String;
fn from_str(s: &::std::primitive::str) -> ::std::result::Result<Self, Self::Err> {
Self::from_bits(
s.parse()
.map_err(|_| String::from("Failed to parse Bitflag integer"))?,
)
.ok_or(String::from("Unknown bits were set in Bitflag"))
}
}
}
.into()
}
pub fn primitive_serde(input: TokenStream) -> TokenStream {
let first = input.clone();
let ref di @ DeriveInput { ref ident, .. } = parse_macro_input!(first);
let fromstr = extract_full_path(di).unwrap();
quote! {
impl std::fmt::Display for #ident {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl ::std::str::FromStr for #ident {
type Err = ::std::string::String;
fn from_str(s: &::std::primitive::str) -> ::std::result::Result<Self, Self::Err> {
Ok(#ident(#fromstr))
}
}
}
.into()
}
fn extract_full_path(ast: &syn::DeriveInput) -> Result<TokenStream2, syn::Error> {
if let syn::Data::Struct(data_struct) = &ast.data {
if let syn::Fields::Unnamed(fields) = &data_struct.fields {
if let Some(syn::Type::Path(path)) = &fields.unnamed.first().map(|f| &f.ty) {
return Ok(
quote! { <#path as ::std::str::FromStr>::from_str(s).map_err(|e| e.to_string())? },
);
}
}
}
Err(syn::Error::new_spanned(ast, "Unable to extract full path"))
}

View File

@ -2,15 +2,16 @@ use crate::common::{Field, YaSerdeAttribute, YaSerdeField};
use crate::ser::{implement_serializer::implement_serializer, label::build_label_name};
use proc_macro2::TokenStream;
use quote::quote;
use syn::DataEnum;
use syn::Fields;
use syn::Ident;
use syn::{DataEnum, Generics};
pub fn serialize(
data_enum: &DataEnum,
name: &Ident,
root: &str,
root_attributes: &YaSerdeAttribute,
generics: &Generics,
) -> TokenStream {
let inner_enum_inspector = inner_enum_inspector(data_enum, name, root_attributes);
@ -108,6 +109,7 @@ pub fn serialize(
quote!(match self {
#inner_enum_inspector
}),
generics,
)
}

View File

@ -3,14 +3,15 @@ use crate::common::{Field, YaSerdeAttribute, YaSerdeField};
use crate::ser::{element::*, implement_serializer::implement_serializer};
use proc_macro2::TokenStream;
use quote::quote;
use syn::DataStruct;
use syn::Ident;
use syn::{DataStruct, Generics};
pub fn serialize(
data_struct: &DataStruct,
name: &Ident,
root: &str,
root_attributes: &YaSerdeAttribute,
generics: &Generics,
) -> TokenStream {
let append_attributes: TokenStream = data_struct
.fields
@ -348,5 +349,6 @@ pub fn serialize(
root_attributes,
append_attributes,
struct_inspector,
generics,
)
}

View File

@ -3,6 +3,7 @@ use crate::ser::namespace::generate_namespaces_definition;
use proc_macro2::Ident;
use proc_macro2::TokenStream;
use quote::quote;
use syn::Generics;
pub fn implement_serializer(
name: &Ident,
@ -10,12 +11,15 @@ pub fn implement_serializer(
attributes: &YaSerdeAttribute,
append_attributes: TokenStream,
inner_inspector: TokenStream,
generics: &Generics,
) -> TokenStream {
let namespaces_definition = generate_namespaces_definition(attributes);
let flatten = attributes.flatten;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
impl ::yaserde::YaSerialize for #name {
impl #impl_generics ::yaserde::YaSerialize for #name #ty_generics #where_clause {
#[allow(unused_variables)]
fn serialize<W: ::std::io::Write>(
&self,

View File

@ -13,6 +13,7 @@ pub fn expand_derive_serialize(ast: &syn::DeriveInput) -> Result<TokenStream, St
let name = &ast.ident;
let attrs = &ast.attrs;
let data = &ast.data;
let generics = &ast.generics;
let root_attributes = YaSerdeAttribute::parse(attrs);
@ -24,10 +25,10 @@ pub fn expand_derive_serialize(ast: &syn::DeriveInput) -> Result<TokenStream, St
let impl_block = match *data {
syn::Data::Struct(ref data_struct) => {
expand_struct::serialize(data_struct, name, &root_name, &root_attributes)
expand_struct::serialize(data_struct, name, &root_name, &root_attributes, generics)
}
syn::Data::Enum(ref data_enum) => {
expand_enum::serialize(data_enum, name, &root_name, &root_attributes)
expand_enum::serialize(data_enum, name, &root_name, &root_attributes, generics)
}
syn::Data::Union(ref _data_union) => unimplemented!(),
};