From 9ca941b97e6aec0702c7b9744d2e3b8f9779ab7f Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sat, 21 Dec 2024 15:11:11 +0100 Subject: [PATCH] some changes to rustical_xml --- crates/xml/README.md | 1 + crates/xml/derive/src/de/attrs.rs | 24 +- crates/xml/derive/src/de/de_enum.rs | 5 +- crates/xml/derive/src/de/de_struct.rs | 404 ++++++++++++++++---------- crates/xml/derive/src/de/mod.rs | 2 +- crates/xml/derive/src/lib.rs | 4 +- crates/xml/src/de.rs | 45 +-- crates/xml/src/lib.rs | 19 ++ crates/xml/src/value.rs | 33 +++ crates/xml/tests/de_enum.rs | 3 +- crates/xml/tests/de_struct.rs | 108 +++++-- 11 files changed, 441 insertions(+), 207 deletions(-) create mode 100644 crates/xml/README.md create mode 100644 crates/xml/src/value.rs diff --git a/crates/xml/README.md b/crates/xml/README.md new file mode 100644 index 0000000..9228b19 --- /dev/null +++ b/crates/xml/README.md @@ -0,0 +1 @@ +# rustical_xml diff --git a/crates/xml/derive/src/de/attrs.rs b/crates/xml/derive/src/de/attrs.rs index 169a121..8662934 100644 --- a/crates/xml/derive/src/de/attrs.rs +++ b/crates/xml/derive/src/de/attrs.rs @@ -1,39 +1,49 @@ use darling::{util::Flag, FromDeriveInput, FromField, FromMeta, FromVariant}; use syn::LitByteStr; -#[derive(Default, FromMeta)] +#[derive(Default, FromMeta, Clone)] pub struct ContainerAttrs { pub ns_strict: Flag, } -#[derive(Default, FromMeta)] +#[derive(Default, FromMeta, Clone)] pub struct TagAttrs { pub rename: Option, pub ns_strict: Flag, pub ns: Option, } -#[derive(Default, FromVariant)] +#[derive(Default, FromVariant, Clone)] #[darling(attributes(xml))] pub struct VariantAttrs { #[darling(flatten)] pub common: TagAttrs, } -#[derive(Default, FromDeriveInput)] +#[derive(Default, FromDeriveInput, Clone)] #[darling(attributes(xml))] pub struct EnumAttrs { #[darling(flatten)] container: ContainerAttrs, } -#[derive(Default, FromDeriveInput)] +#[derive(Default, FromDeriveInput, Clone)] #[darling(attributes(xml))] pub struct StructAttrs { #[darling(flatten)] pub container: ContainerAttrs, pub root: Option, + pub allow_invalid: Flag, +} + +#[derive(Default, FromMeta, PartialEq)] +pub enum FieldType { + #[default] + Tag, + Attr, + Text, + Untagged, } #[derive(Default, FromField)] @@ -41,8 +51,8 @@ pub struct StructAttrs { pub struct FieldAttrs { #[darling(flatten)] pub common: TagAttrs, - pub text: Flag, - pub untagged: Flag, pub flatten: Flag, pub default: Option, + #[darling(default, rename = "ty")] + pub xml_ty: FieldType, } diff --git a/crates/xml/derive/src/de/de_enum.rs b/crates/xml/derive/src/de/de_enum.rs index 3e7f756..4d28e73 100644 --- a/crates/xml/derive/src/de/de_enum.rs +++ b/crates/xml/derive/src/de/de_enum.rs @@ -1,15 +1,14 @@ +use crate::de::attrs::VariantAttrs; use darling::FromVariant; use heck::ToKebabCase; use quote::quote; use syn::{DataEnum, DeriveInput, Fields, FieldsUnnamed, Variant}; -use crate::de::attrs::VariantAttrs; - pub fn enum_variant_branch(variant: &Variant) -> proc_macro2::TokenStream { let ident = &variant.ident; match &variant.fields { - Fields::Named(named) => { + Fields::Named(_) => { panic!("struct variants are not supported, please use a tuple variant with a struct") } Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { diff --git a/crates/xml/derive/src/de/de_struct.rs b/crates/xml/derive/src/de/de_struct.rs index 70d74fd..c0544d8 100644 --- a/crates/xml/derive/src/de/de_struct.rs +++ b/crates/xml/derive/src/de/de_struct.rs @@ -1,6 +1,5 @@ +use super::attrs::{FieldAttrs, FieldType}; use crate::de::attrs::StructAttrs; - -use super::attrs::FieldAttrs; use core::panic; use darling::{FromDeriveInput, FromField}; use heck::ToKebabCase; @@ -23,16 +22,46 @@ fn get_generic_type(ty: &syn::Type) -> Option<&syn::Type> { None } +fn invalid_field_branch(allow: bool) -> proc_macro2::TokenStream { + if allow { + quote! { + _ => { + // ignore because of allow_invalid flag + } + } + } else { + quote! { + _ => { + // invalid field name + return Err(XmlDeError::InvalidFieldName) + } + } + } +} + +fn wrap_option_if_no_default( + value: proc_macro2::TokenStream, + has_default: bool, +) -> proc_macro2::TokenStream { + if has_default { + value + } else { + quote! {Some(#value)} + } +} + pub struct Field { pub field: syn::Field, pub attrs: FieldAttrs, + pub struct_attrs: StructAttrs, } impl Field { - fn from_syn_field(field: syn::Field) -> Self { + fn from_syn_field(field: syn::Field, struct_attrs: StructAttrs) -> Self { Self { attrs: FieldAttrs::from_field(&field).unwrap(), field, + struct_attrs, } } fn de_name(&self) -> LitByteStr { @@ -46,6 +75,11 @@ impl Field { )) } + fn ns_strict(&self) -> bool { + self.attrs.common.ns_strict.is_present() + || self.struct_attrs.container.ns_strict.is_present() + } + fn field_ident(&self) -> &syn::Ident { self.field .ident @@ -60,94 +94,90 @@ impl Field { fn builder_field(&self) -> proc_macro2::TokenStream { let field_ident = self.field_ident(); let ty = self.ty(); - if self.attrs.default.is_some() { - quote! { - #field_ident: #ty, - } - } else if self.attrs.flatten.is_present() { - let generic_type = get_generic_type(ty).expect("flatten attribute only implemented for explicit generics (rustical_xml will assume the first generic as the inner type)"); - quote! { - #field_ident: Vec<#generic_type>, - } - } else { - quote! { - #field_ident: Option<#ty>, + match (self.attrs.flatten.is_present(), &self.attrs.default) { + (_, Some(_default)) => quote! { #field_ident: #ty, }, + (true, None) => { + let generic_type = get_generic_type(ty).expect("flatten attribute only implemented for explicit generics (rustical_xml will assume the first generic as the inner type)"); + quote! { #field_ident: Vec<#generic_type>, } } + (false, None) => quote! { #field_ident: Option<#ty>, }, } } fn builder_field_init(&self) -> proc_macro2::TokenStream { let field_ident = self.field_ident(); - if let Some(default) = &self.attrs.default { - quote! { - #field_ident: #default(), - } - } else if self.attrs.flatten.is_present() { - quote! { - #field_ident: vec![], - } - } else { - quote! { - #field_ident: None, - } + match (self.attrs.flatten.is_present(), &self.attrs.default) { + (_, Some(default)) => quote! { #field_ident: #default(), }, + (true, None) => quote! { #field_ident: vec![], }, + (false, None) => quote! { #field_ident: None, }, } } fn builder_field_build(&self) -> proc_macro2::TokenStream { let field_ident = self.field_ident(); - if self.attrs.flatten.is_present() { - quote! { + match ( + self.attrs.flatten.is_present(), + self.attrs.default.is_some(), + ) { + (true, _) => quote! { #field_ident: FromIterator::from_iter(builder.#field_ident.into_iter()) - } - } else if self.attrs.default.is_some() { - quote! { + }, + (false, true) => quote! { #field_ident: builder.#field_ident, - } - } else { - quote! { + }, + (false, false) => quote! { #field_ident: builder.#field_ident.expect("todo: handle missing field"), - } + }, } } fn named_branch(&self) -> Option { - if self.attrs.text.is_present() { - return None; - } - if self.attrs.untagged.is_present() { + if self.attrs.xml_ty != FieldType::Tag { return None; } + + let namespace_match = if self.ns_strict() { + if let Some(ns) = &self.attrs.common.ns { + quote! {quick_xml::name::ResolveResult::Bound(quick_xml::name::Namespace(#ns))} + } else { + quote! {quick_xml::name::ResolveResult::Unbound} + } + } else { + quote! {_} + }; + let field_name = self.de_name(); let field_ident = self.field_ident(); let deserializer = self.ty(); - Some(if self.attrs.default.is_some() { - quote! { - #field_name => { - builder.#field_ident = <#deserializer as rustical_xml::XmlDeserialize>::deserialize(reader, &start, empty)?; - }, - } - } else if self.attrs.flatten.is_present() { - let deserializer = get_generic_type(self.ty()).expect("flatten attribute only implemented for explicit generics (rustical_xml will assume the first generic as the inner type)"); - quote! { - #field_name => { - builder.#field_ident.push(<#deserializer as rustical_xml::XmlDeserialize>::deserialize(reader, &start, empty)?); - }, - } - } else { - quote! { - #field_name => { - builder.#field_ident = Some(<#deserializer as rustical_xml::XmlDeserialize>::deserialize(reader, &start, empty)?); - }, + let value = quote! { <#deserializer as rustical_xml::XmlDeserialize>::deserialize(reader, &start, empty)? }; + let assignment = match (self.attrs.flatten.is_present(), &self.attrs.default) { + (true, _) => { + // TODO: Make nicer, watch out with deserializer typing + let deserializer = get_generic_type(self.ty()).expect("flatten attribute only implemented for explicit generics (rustical_xml will assume the first generic as the inner type)"); + quote! { + builder.#field_ident.push(<#deserializer as rustical_xml::XmlDeserialize>::deserialize(reader, &start, empty)?); + } } + (false, Some(_default)) => quote! { + builder.#field_ident = #value; + }, + (false, None) => quote! { + builder.#field_ident = Some(#value); + }, + }; + + Some(quote! { + (#namespace_match, #field_name) => { #assignment; }, }) } fn untagged_branch(&self) -> Option { - if !self.attrs.untagged.is_present() { + if self.attrs.xml_ty != FieldType::Untagged { return None; } let field_ident = self.field_ident(); let deserializer = self.ty(); + Some(if self.attrs.flatten.is_present() { let deserializer = get_generic_type(self.ty()).expect("flatten attribute only implemented for explicit generics (rustical_xml will assume the first generic as the inner type)"); quote! { @@ -165,128 +195,190 @@ impl Field { } fn text_branch(&self) -> Option { - if !self.attrs.text.is_present() { + if self.attrs.xml_ty != FieldType::Text { return None; } let field_ident = self.field_ident(); + let value = wrap_option_if_no_default( + quote! { + rustical_xml::Value::deserialize(text.as_ref())? + }, + self.attrs.default.is_some(), + ); Some(quote! { - builder.#field_ident = Some(text.to_owned().into()); + builder.#field_ident = #value; + }) + } + + fn attr_branch(&self) -> Option { + if self.attrs.xml_ty != FieldType::Attr { + return None; + } + let field_ident = self.field_ident(); + let field_name = self.de_name(); + + let value = wrap_option_if_no_default( + quote! { + rustical_xml::Value::deserialize(attr.unescape_value()?.as_ref())? + }, + self.attrs.default.is_some(), + ); + + Some(quote! { + #field_name => { + builder.#field_ident = #value; + } }) } } -pub fn impl_de_struct(input: &DeriveInput, data: &DataStruct) -> proc_macro2::TokenStream { - let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); - let name = &input.ident; +impl NamedStruct { + pub fn parse(input: &DeriveInput, data: &DataStruct) -> Self { + let attrs = StructAttrs::from_derive_input(input).unwrap(); - let struct_attrs = StructAttrs::from_derive_input(input).unwrap(); - - let fields: Vec<_> = data - .fields - .iter() - .map(|field| Field::from_syn_field(field.to_owned())) - .collect(); - - let builder_fields = fields.iter().map(Field::builder_field); - let builder_field_inits = fields.iter().map(Field::builder_field_init); - let named_field_branches = fields.iter().filter_map(Field::named_branch); - let untagged_field_branches: Vec<_> = - fields.iter().filter_map(Field::untagged_branch).collect(); - if untagged_field_branches.len() > 1 { - panic!("Currently only one untagged field supported!"); + match &data.fields { + syn::Fields::Named(named) => NamedStruct { + fields: named + .named + .iter() + .map(|field| Field::from_syn_field(field.to_owned(), attrs.clone())) + .collect(), + attrs, + ident: input.ident.to_owned(), + generics: input.generics.to_owned(), + }, + syn::Fields::Unnamed(_) => panic!("not implemented for tuple struct"), + syn::Fields::Unit => NamedStruct { + fields: vec![], + attrs, + ident: input.ident.to_owned(), + generics: input.generics.to_owned(), + }, + } } - let text_field_branches = fields.iter().filter_map(Field::text_branch); - let builder_field_builds = fields.iter().map(Field::builder_field_build); +} - let xml_root_impl = if let Some(root) = struct_attrs.root { - quote! { - impl #impl_generics ::rustical_xml::XmlRoot for #name #type_generics #where_clause { - fn root_tag() -> &'static [u8] { - #root +pub struct NamedStruct { + attrs: StructAttrs, + fields: Vec, + ident: syn::Ident, + generics: syn::Generics, +} + +impl NamedStruct { + pub fn impl_de(&self) -> proc_macro2::TokenStream { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let ident = &self.ident; + + let builder_fields = self.fields.iter().map(Field::builder_field); + let builder_field_inits = self.fields.iter().map(Field::builder_field_init); + let named_field_branches = self.fields.iter().filter_map(Field::named_branch); + let untagged_field_branches: Vec<_> = self + .fields + .iter() + .filter_map(Field::untagged_branch) + .collect(); + if untagged_field_branches.len() > 1 { + panic!("Currently only one untagged field supported!"); + } + let text_field_branches = self.fields.iter().filter_map(Field::text_branch); + let attr_field_branches = self.fields.iter().filter_map(Field::attr_branch); + + let builder_field_builds = self.fields.iter().map(Field::builder_field_build); + + let xml_root_impl = if let Some(root) = &self.attrs.root { + quote! { + impl #impl_generics ::rustical_xml::XmlRoot for #ident #type_generics #where_clause { + fn root_tag() -> &'static [u8] { #root } } } - } - } else { - quote! {} - }; + } else { + quote! {} + }; - quote! { - #xml_root_impl + let invalid_field_branch = invalid_field_branch(self.attrs.allow_invalid.is_present()); - impl #impl_generics ::rustical_xml::XmlDeserialize for #name #type_generics #where_clause { - fn deserialize( - reader: &mut quick_xml::NsReader, - start: &quick_xml::events::BytesStart, - empty: bool - ) -> Result { - use quick_xml::events::Event; - use rustical_xml::XmlDeError; + quote! { + #xml_root_impl - let mut buf = Vec::new(); + impl #impl_generics ::rustical_xml::XmlDeserialize for #ident #type_generics #where_clause { + fn deserialize( + reader: &mut quick_xml::NsReader, + start: &quick_xml::events::BytesStart, + empty: bool + ) -> Result { + use quick_xml::events::Event; + use rustical_xml::XmlDeError; - // initialise fields - struct StructBuilder { - #(#builder_fields)* - } + let mut buf = Vec::new(); - let mut builder = StructBuilder { - #(#builder_field_inits)* - }; + // initialise fields + struct StructBuilder #type_generics #where_clause { + #(#builder_fields)* + } - if !empty { - loop { - let event = reader.read_event_into(&mut buf)?; - match &event { - Event::End(e) if e.name() == start.name() => { - break; - } - Event::Eof => return Err(XmlDeError::Eof), - // start of a child element - Event::Start(start) | Event::Empty(start) => { - let empty = matches!(event, Event::Empty(_)); - let (_ns, name) = reader.resolve_element(start.name()); - match name.as_ref() { - #(#named_field_branches)* - #(#untagged_field_branches)* - _ => { - // invalid field name - return Err(XmlDeError::InvalidFieldName) + let mut builder = StructBuilder { + #(#builder_field_inits)* + }; + + for attr in start.attributes() { + let attr = attr?; + match attr.key.as_ref() { + #(#attr_field_branches)* + #invalid_field_branch + } + } + + if !empty { + loop { + let event = reader.read_event_into(&mut buf)?; + match &event { + Event::End(e) if e.name() == start.name() => { + break; + } + Event::Eof => return Err(XmlDeError::Eof), + // start of a child element + Event::Start(start) | Event::Empty(start) => { + let empty = matches!(event, Event::Empty(_)); + let (ns, name) = reader.resolve_element(start.name()); + match (ns, name.as_ref()) { + #(#named_field_branches)* + #(#untagged_field_branches)* + #invalid_field_branch } } - } - Event::Text(bytes_text) => { - let text = bytes_text.unescape()?; - #(#text_field_branches)* - } - Event::CData(cdata) => { - return Err(XmlDeError::UnsupportedEvent("CDATA")); - } - Event::Comment(_) => { - // ignore - } - Event::Decl(_) => { - // Error: not supported - return Err(XmlDeError::UnsupportedEvent("Declaration")); - } - Event::PI(_) => { - // Error: not supported - return Err(XmlDeError::UnsupportedEvent("Processing instruction")); - } - Event::DocType(doctype) => { - // Error: start of new document - return Err(XmlDeError::UnsupportedEvent("Doctype in the middle of the document")); - } - Event::End(end) => { - // Error: premature end - return Err(XmlDeError::Other("Unexpected closing tag for wrong element".to_owned())); + Event::Text(bytes_text) => { + let text = bytes_text.unescape()?; + #(#text_field_branches)* + } + Event::CData(cdata) => { + return Err(XmlDeError::UnsupportedEvent("CDATA")); + } + Event::Comment(_) => { /* ignore */ } + Event::Decl(_) => { + // Error: not supported + return Err(XmlDeError::UnsupportedEvent("Declaration")); + } + Event::PI(_) => { + // Error: not supported + return Err(XmlDeError::UnsupportedEvent("Processing instruction")); + } + Event::DocType(doctype) => { + // Error: start of new document + return Err(XmlDeError::UnsupportedEvent("Doctype in the middle of the document")); + } + Event::End(end) => { + // Error: premature end + return Err(XmlDeError::Other("Unexpected closing tag for wrong element".to_owned())); + } } } } - } - Ok(Self { - #(#builder_field_builds)* - }) + Ok(Self { + #(#builder_field_builds)* + }) + } } } } diff --git a/crates/xml/derive/src/de/mod.rs b/crates/xml/derive/src/de/mod.rs index 9666e9e..6ebd221 100644 --- a/crates/xml/derive/src/de/mod.rs +++ b/crates/xml/derive/src/de/mod.rs @@ -3,4 +3,4 @@ mod de_enum; mod de_struct; pub use de_enum::impl_de_enum; -pub use de_struct::impl_de_struct; +pub use de_struct::NamedStruct; diff --git a/crates/xml/derive/src/lib.rs b/crates/xml/derive/src/lib.rs index 5d355f8..205e4d3 100644 --- a/crates/xml/derive/src/lib.rs +++ b/crates/xml/derive/src/lib.rs @@ -3,7 +3,7 @@ use syn::{parse_macro_input, DeriveInput}; mod de; -use de::{impl_de_enum, impl_de_struct}; +use de::{impl_de_enum, NamedStruct}; #[proc_macro_derive(XmlDeserialize, attributes(xml))] pub fn derive_xml_deserialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -11,7 +11,7 @@ pub fn derive_xml_deserialize(input: proc_macro::TokenStream) -> proc_macro::Tok match &input.data { syn::Data::Enum(e) => impl_de_enum(&input, e), - syn::Data::Struct(s) => impl_de_struct(&input, s), + syn::Data::Struct(s) => NamedStruct::parse(&input, s).impl_de(), syn::Data::Union(_) => panic!("Union not supported"), } .into() diff --git a/crates/xml/src/de.rs b/crates/xml/src/de.rs index cccb850..f1b0f33 100644 --- a/crates/xml/src/de.rs +++ b/crates/xml/src/de.rs @@ -10,6 +10,8 @@ pub enum XmlDeError { QuickXmlDeError(#[from] quick_xml::de::DeError), #[error(transparent)] QuickXmlError(#[from] quick_xml::Error), + #[error(transparent)] + QuickXmlAttrError(#[from] quick_xml::events::attributes::AttrError), #[error("Unknown error")] UnknownError, #[error("Invalid tag {0}. Expected {1}")] @@ -26,6 +28,8 @@ pub enum XmlDeError { InvalidVariant(String), #[error("Invalid field name: ")] InvalidFieldName, + #[error(transparent)] + InvalidValue(#[from] crate::value::ParseValueError), } pub trait XmlDeserialize: Sized { @@ -36,12 +40,16 @@ pub trait XmlDeserialize: Sized { ) -> Result; } -pub trait XmlRoot: XmlDeserialize { - fn parse(mut reader: quick_xml::NsReader) -> Result { +pub trait XmlRoot { + fn parse(mut reader: quick_xml::NsReader) -> Result + where + Self: XmlDeserialize, + { let mut buf = Vec::new(); let event = reader.read_event_into(&mut buf)?; + let empty = event.is_empty(); match event { - Event::Start(start) => { + Event::Start(start) | Event::Empty(start) => { let (_ns, name) = reader.resolve_element(start.name()); if name.as_ref() != Self::root_tag() { return Err(XmlDeError::InvalidTag( @@ -49,32 +57,33 @@ pub trait XmlRoot: XmlDeserialize { String::from_utf8_lossy(Self::root_tag()).to_string(), )); }; + // TODO: check namespace - return Self::deserialize(&mut reader, &start, false); - } - Event::Empty(start) => { - let (_ns, name) = reader.resolve_element(start.name()); - if name.as_ref() != Self::root_tag() { - return Err(XmlDeError::InvalidTag( - String::from_utf8_lossy(name.as_ref()).to_string(), - String::from_utf8_lossy(Self::root_tag()).to_string(), - )); - }; - // TODO: check namespace - - return Self::deserialize(&mut reader, &start, true); + return Self::deserialize(&mut reader, &start, empty); } _ => {} }; Err(XmlDeError::UnknownError) } - fn parse_str(input: &str) -> Result { - let mut reader = quick_xml::NsReader::from_str(input); + fn parse_reader(input: R) -> Result + where + Self: XmlDeserialize, + { + let mut reader = quick_xml::NsReader::from_reader(input); reader.config_mut().trim_text(true); Self::parse(reader) } fn root_tag() -> &'static [u8]; } + +pub trait XmlRootParseStr<'i>: XmlRoot + XmlDeserialize { + #[inline] + fn parse_str(s: &'i str) -> Result { + Self::parse_reader(s.as_bytes()) + } +} + +impl<'i, T: XmlRoot + XmlDeserialize> XmlRootParseStr<'i> for T {} diff --git a/crates/xml/src/lib.rs b/crates/xml/src/lib.rs index 55b34af..860150e 100644 --- a/crates/xml/src/lib.rs +++ b/crates/xml/src/lib.rs @@ -2,10 +2,12 @@ use quick_xml::events::{BytesStart, Event}; use std::io::BufRead; pub mod de; +mod value; pub use de::XmlDeError; pub use de::XmlDeserialize; pub use de::XmlRoot; +pub use value::Value; impl XmlDeserialize for Option { fn deserialize( @@ -17,6 +19,7 @@ impl XmlDeserialize for Option { } } +#[derive(Debug, Clone, PartialEq)] pub struct Unit; impl XmlDeserialize for Unit { @@ -65,3 +68,19 @@ impl XmlDeserialize for String { Ok(content) } } + +// TODO: actually implement +pub struct Unparsed(BytesStart<'static>); + +impl XmlDeserialize for Unparsed { + fn deserialize( + reader: &mut quick_xml::NsReader, + start: &BytesStart, + _empty: bool, + ) -> Result { + // let reader_cloned = NsReader::from_reader(reader.get_ref().to_owned()); + let mut buf = vec![]; + reader.read_to_end_into(start.name(), &mut buf)?; + Ok(Self(start.to_owned())) + } +} diff --git a/crates/xml/src/value.rs b/crates/xml/src/value.rs new file mode 100644 index 0000000..2d26820 --- /dev/null +++ b/crates/xml/src/value.rs @@ -0,0 +1,33 @@ +use std::convert::Infallible; +use std::num::ParseIntError; +use std::str::FromStr; +use thiserror::Error; + +use crate::XmlDeError; + +#[derive(Debug, Error)] +pub enum ParseValueError { + #[error(transparent)] + Infallible(#[from] Infallible), + #[error(transparent)] + ParseIntError(#[from] ParseIntError), +} + +pub trait Value: Sized { + fn serialize(&self) -> String; + fn deserialize(val: &str) -> Result; +} + +impl + ToString> Value for T +where + ParseValueError: From, +{ + fn serialize(&self) -> String { + self.to_string() + } + fn deserialize(val: &str) -> Result { + val.parse() + .map_err(ParseValueError::from) + .map_err(XmlDeError::from) + } +} diff --git a/crates/xml/tests/de_enum.rs b/crates/xml/tests/de_enum.rs index db3f540..21d13fc 100644 --- a/crates/xml/tests/de_enum.rs +++ b/crates/xml/tests/de_enum.rs @@ -1,4 +1,5 @@ use rustical_xml::XmlRoot; +use std::io::BufRead; use xml_derive::XmlDeserialize; #[test] @@ -11,7 +12,7 @@ fn test_struct_untagged_enum() { #[derive(Debug, XmlDeserialize, PartialEq)] struct Prop { - #[xml(untagged)] + #[xml(ty = "untagged")] prop: PropEnum, } diff --git a/crates/xml/tests/de_struct.rs b/crates/xml/tests/de_struct.rs index 6722b40..e194579 100644 --- a/crates/xml/tests/de_struct.rs +++ b/crates/xml/tests/de_struct.rs @@ -1,24 +1,19 @@ +use rustical_xml::de::XmlRootParseStr; +use rustical_xml::{Unit, Unparsed, XmlDeserialize}; use std::collections::HashSet; - -use rustical_xml::XmlRoot; -use xml_derive::XmlDeserialize; +use std::io::BufRead; #[test] fn test_struct_text_field() { #[derive(Debug, XmlDeserialize, PartialEq)] + #[xml(root = b"document")] struct Document { - #[xml(text)] + #[xml(ty = "text")] text: String, - #[xml(text)] + #[xml(ty = "text")] text2: String, } - impl XmlRoot for Document { - fn root_tag() -> &'static [u8] { - b"document" - } - } - let doc = Document::parse_str(r#"Hello!"#).unwrap(); assert_eq!( doc, @@ -32,22 +27,17 @@ fn test_struct_text_field() { #[test] fn test_struct_document() { #[derive(Debug, XmlDeserialize, PartialEq)] + #[xml(root = b"document")] struct Document { child: Child, } #[derive(Debug, XmlDeserialize, PartialEq, Default)] struct Child { - #[xml(text)] + #[xml(ty = "text")] text: String, } - impl XmlRoot for Document { - fn root_tag() -> &'static [u8] { - b"document" - } - } - let doc = Document::parse_str(r#"Hello!"#).unwrap(); assert_eq!( doc, @@ -70,7 +60,7 @@ fn test_struct_rename_field() { #[derive(Debug, XmlDeserialize, PartialEq, Default)] struct Child { - #[xml(text)] + #[xml(ty = "text")] text: String, } @@ -159,3 +149,83 @@ fn test_struct_set() { } ); } + +#[test] +fn test_struct_ns() { + #[derive(Debug, XmlDeserialize, PartialEq)] + #[xml(root = b"document", ns_strict)] + struct Document { + #[xml(ns = b"hello")] + child: Unit, + } + + let doc = Document::parse_str(r#""#).unwrap(); + assert_eq!(doc, Document { child: Unit }); +} + +#[test] +fn test_struct_attr() { + #[derive(Debug, XmlDeserialize, PartialEq)] + #[xml(root = b"document", ns_strict)] + struct Document { + #[xml(ns = b"hello")] + child: Unit, + #[xml(ty = "attr", default = "Default::default")] + test: String, + #[xml(ty = "attr")] + number: usize, + } + + let doc = Document::parse_str( + r#""#, + ) + .unwrap(); + assert_eq!( + doc, + Document { + child: Unit, + test: "hello!".to_owned(), + number: 2 + } + ); +} + +#[test] +fn test_struct_generics() { + #[derive(XmlDeserialize)] + #[xml(root = b"document", ns_strict)] + struct Document { + child: T, + } + + let doc = Document::::parse_str( + r#" + + + Hello!

Nice

+
+
+ "#, + ) + .unwrap(); +} + +#[test] +fn test_struct_unparsed() { + #[derive(XmlDeserialize)] + #[xml(root = b"document", ns_strict)] + struct Document { + child: Unparsed, + } + + let doc = Document::parse_str( + r#" + + + Hello!

Nice

+
+
+ "#, + ) + .unwrap(); +}