use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{spanned::Spanned, *};
use crate::{
attrs, duplicate_mode::DuplicateMode, utils::{elide_type_lifetimes, Context}
};
use bitflags::bitflags;
type Result<T, E = Vec<syn::Error>> = std::result::Result<T, E>;
pub enum Element {
Struct { name: Ident, fields: Fields },
Enum { name: Ident, variants: Vec<Fields> },
}
pub enum Fields {
Named {
tag: LitStr,
strict: StrictMode,
name: Ident,
fields: Vec<Field>,
},
Newtype {
tags: Vec<LitStr>,
name: Ident,
ty: Box<Type>,
},
}
pub enum Field {
Attribute {
name: TokenStream,
bind: Ident,
duplicate_last: bool,
ty: Type,
with: Option<ExprPath>,
tag: LitStr,
default: bool,
},
Child {
name: TokenStream,
bind: Ident,
duplicate_last: bool,
ty: Type,
default: bool,
tags: Vec<LitStr>,
},
Text {
name: TokenStream,
bind: Ident,
ty: Type,
with: Option<ExprPath>,
is_cdata: bool,
},
FlattenText {
name: TokenStream,
bind: Ident,
duplicate_last: bool,
ty: Type,
with: Option<ExprPath>,
default: bool,
tag: LitStr,
is_cdata: bool,
},
}
pub enum Type {
CowStr,
OptionCowStr,
VecCowStr,
T(syn::Type),
OptionT(syn::Type),
VecT(syn::Type),
Bool,
VecBool,
OptionBool,
}
impl Element {
pub fn parse(input: DeriveInput) -> Result<Element> {
let mut ctx = Context::default();
let element = match input.data {
Data::Struct(data) => Element::Struct {
name: input.ident.clone(),
fields: Fields::parse(&mut ctx, data.fields, input.attrs, input.ident),
},
Data::Enum(data) => Element::Enum {
name: input.ident,
variants: data
.variants
.into_iter()
.map(|variant| {
Fields::parse(&mut ctx, variant.fields, variant.attrs, variant.ident)
})
.collect(),
},
Data::Union(_) => {
return Err(vec![syn::Error::new_spanned(
input,
"hard-xml doesn't support union",
)]);
}
};
ctx.check().map(|_| element)
}
}
impl Fields {
pub fn parse(
ctx: &mut Context,
mut fields: syn::Fields,
attrs: Vec<Attribute>,
name: Ident,
) -> Fields {
let attrs::Container {
mut tags,
strict_mode,
} = attrs::Container::parse(ctx, attrs);
if tags.is_empty() {
ctx.push_spanned_error(&name, "missing `tag` attribute");
}
if let syn::Fields::Unnamed(ref mut fields) = fields {
if is_new_type(fields) {
let ty = fields.unnamed.pop().unwrap().into_value().ty;
let ty = Box::new(Type::parse(ty));
return Fields::Newtype { tags, name, ty };
}
}
let fields = match fields {
syn::Fields::Unit => Vec::new(),
syn::Fields::Unnamed(fields) => fields
.unnamed
.into_iter()
.enumerate()
.filter_map(|(index, field)| {
let index = syn::Index::from(index);
let bind = format_ident!("__self_{}", index);
Field::parse(ctx, quote!(#index), bind, field)
})
.collect(),
syn::Fields::Named(_) => fields
.into_iter()
.filter_map(|field| {
let name = field.ident.clone().unwrap();
let bind = format_ident!("__self_{}", name);
Field::parse(ctx, quote!(#name), bind, field)
})
.collect(),
};
let tag = if !tags.is_empty() {
tags.swap_remove(0)
} else {
LitStr::new("", Span::call_site())
};
Fields::Named {
tag,
strict: strict_mode,
name,
fields,
}
}
}
fn is_new_type(fields: &FieldsUnnamed) -> bool {
fields.unnamed.len() == 1
&& fields.unnamed[0]
.attrs
.iter()
.all(|attr| attrs::get_xml_meta(attr).is_none())
}
impl Field {
pub fn parse(
ctx: &mut Context,
name: TokenStream,
bind: Ident,
field: syn::Field,
) -> Option<Field> {
let span = field.span();
let mut attrs = attrs::Field::parse(ctx, field.attrs);
let with = attrs.with.take();
let kind = FieldKind::from_attributes(ctx, attrs, span)?;
let span = field.ty.span();
let ty = Type::parse(field.ty);
kind.into_field(ctx, name, bind, ty, with, span)
}
}
enum FieldKind {
Attribute {
default: bool,
duplicate_last: bool,
tag: LitStr,
},
Child {
default: bool,
duplicate_last: bool,
tags: Vec<LitStr>,
},
FlattenText {
tag: LitStr,
cdata: bool,
default: bool,
duplicate_last: bool,
},
Text {
cdata: bool,
},
}
impl FieldKind {
fn into_field(
self,
ctx: &mut Context,
name: TokenStream,
bind: Ident,
ty: Type,
with: Option<ExprPath>,
span: Span,
) -> Option<Field> {
self.verify_type(ctx, &ty, span).then(|| match self {
FieldKind::Attribute { default, duplicate_last, tag } => Field::Attribute {
name,
bind,
duplicate_last,
ty,
with,
tag,
default,
},
FieldKind::Child { default, duplicate_last, tags } => Field::Child {
name,
bind,
duplicate_last,
ty,
default,
tags,
},
FieldKind::FlattenText {
tag,
cdata,
default,
duplicate_last,
} => Field::FlattenText {
name,
bind,
duplicate_last,
ty,
with,
default,
tag,
is_cdata: cdata,
},
FieldKind::Text { cdata } => Field::Text {
name,
bind,
ty,
with,
is_cdata: cdata,
},
})
}
fn from_attributes(ctx: &mut Context, attrs: attrs::Field, span: Span) -> Option<Self> {
let attrs::Field {
attr_tag,
child_tags,
flatten_text_tag,
is_text,
..
} = attrs;
match (attr_tag, child_tags.as_slice(), flatten_text_tag, is_text) {
(Some(tag), &[], None, false) => Some(Self::Attribute {
default: attrs.default,
duplicate_last: match attrs.duplicate_mode {
Some(DuplicateMode::Error) => false,
Some(DuplicateMode::Last) => true,
None => false,
},
tag,
}),
(None, &[_, ..], None, false) => Some(Self::Child {
default: attrs.default,
duplicate_last: match attrs.duplicate_mode {
Some(DuplicateMode::Error) => false,
Some(DuplicateMode::Last) => true,
None => true, },
tags: child_tags,
}),
(None, &[], Some(tag), false) => Some(Self::FlattenText {
tag,
cdata: attrs.is_cdata,
default: attrs.default,
duplicate_last: match attrs.duplicate_mode {
Some(DuplicateMode::Error) => false,
Some(DuplicateMode::Last) => true,
None => true, },
}),
(None, &[], None, true) => Some(Self::Text {
cdata: attrs.is_cdata,
}),
(None, &[], None, false) => {
ctx.push_new_error(
span,
"field should have one of `attr`, `child`, `text` or `flatten_text` attribute",
);
None
}
_ => {
ctx.push_new_error(
span,
"the attributes `attr`, `child`, `text` and `flatten_text` are mutually exclusive",
);
None
}
}
}
fn verify_type(&self, ctx: &mut Context, ty: &Type, span: Span) -> bool {
match self {
FieldKind::Attribute { .. } if ty.is_vec() => {
ctx.push_new_error(span, "`attr` attribute doesn't support Vec");
false
}
FieldKind::Child { .. }
if !matches!(ty, Type::OptionT(_) | Type::T(_) | Type::VecT(_)) =>
{
ctx.push_new_error(
span,
"`child` attribute only supports Vec<T>, Option<T>, and T",
);
false
}
FieldKind::Text { .. } if ty.is_vec() => {
ctx.push_new_error(span, "`text` attribute doesn't support Vec");
false
}
_ => true,
}
}
}
impl Type {
pub fn is_option(&self) -> bool {
matches!(
self,
Type::OptionCowStr | Type::OptionT(_) | Type::OptionBool
)
}
pub fn is_vec(&self) -> bool {
matches!(self, Type::VecCowStr | Type::VecT(_) | Type::VecBool)
}
fn parse(mut ty: syn::Type) -> Self {
fn is_vec(ty: &syn::Type) -> Option<&syn::Type> {
let path = match ty {
syn::Type::Path(ty) => &ty.path,
_ => return None,
};
let seg = path.segments.last()?;
let args = match &seg.arguments {
PathArguments::AngleBracketed(bracketed) => &bracketed.args,
_ => return None,
};
if seg.ident == "Vec" && args.len() == 1 {
match args[0] {
GenericArgument::Type(ref arg) => Some(arg),
_ => None,
}
} else {
None
}
}
fn is_option(ty: &syn::Type) -> Option<&syn::Type> {
let path = match ty {
syn::Type::Path(ty) => &ty.path,
_ => return None,
};
let seg = path.segments.last()?;
let args = match &seg.arguments {
PathArguments::AngleBracketed(bracketed) => &bracketed.args,
_ => return None,
};
if seg.ident == "Option" && args.len() == 1 {
match &args[0] {
GenericArgument::Type(arg) => Some(arg),
_ => None,
}
} else {
None
}
}
fn is_cow_str(ty: &syn::Type) -> bool {
let path = match ty {
syn::Type::Path(ty) => &ty.path,
_ => return false,
};
let seg = match path.segments.last() {
Some(seg) => seg,
None => return false,
};
let args = match &seg.arguments {
PathArguments::AngleBracketed(bracketed) => &bracketed.args,
_ => return false,
};
if seg.ident == "Cow" && args.len() == 2 {
match &args[1] {
GenericArgument::Type(syn::Type::Path(ty)) => ty.path.is_ident("str"),
_ => false,
}
} else {
false
}
}
fn is_bool(ty: &syn::Type) -> bool {
matches!(ty, syn::Type::Path(ty) if ty.path.is_ident("bool"))
}
elide_type_lifetimes(&mut ty);
if let Some(ty) = is_vec(&ty) {
if is_cow_str(ty) {
Type::VecCowStr
} else if is_bool(ty) {
Type::VecBool
} else {
Type::VecT(ty.clone())
}
} else if let Some(ty) = is_option(&ty) {
if is_cow_str(ty) {
Type::OptionCowStr
} else if is_bool(ty) {
Type::OptionBool
} else {
Type::OptionT(ty.clone())
}
} else if is_cow_str(&ty) {
Type::CowStr
} else if is_bool(&ty) {
Type::Bool
} else {
Type::T(ty)
}
}
}
bitflags! {
#[derive(Copy,Clone)]
pub struct StrictMode: u8 {
const UNKNOWN_ATTRIBUTE = 0b0000_0001;
const UNKNOWN_ELEMENT = 0b0000_0010;
}
}