xso-proc: add support for text codecs
Text codecs allow to customize the conversion of data from/to XML, in particular in two scenarios: 1. When the type for which the behaviour is to be defined comes from a foreign crate, preventing the implementation of FromXmlText/IntoXmlText. 2. When there is not one obvious, or more than one sensible, way to convert a value to XML text and back.
This commit is contained in:
parent
46584f05f9
commit
c83ff286e0
8 changed files with 258 additions and 26 deletions
|
|
@ -280,9 +280,9 @@ impl Compound {
|
|||
State::new(state_name)
|
||||
.with_field(&bound_name, field.ty())
|
||||
.with_impl(quote! {
|
||||
::core::option::Option::Some(::xso::exports::rxml::Event::Text(
|
||||
#generator.map(|value| ::xso::exports::rxml::Event::Text(
|
||||
::xso::exports::rxml::parser::EventMetrics::zero(),
|
||||
#generator,
|
||||
value,
|
||||
))
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
|
|||
use crate::scope::{FromEventsScope, IntoEventsScope};
|
||||
use crate::types::{
|
||||
default_fn, from_xml_text_fn, into_optional_xml_text_fn, into_xml_text_fn, string_ty,
|
||||
text_codec_decode_fn, text_codec_encode_fn,
|
||||
};
|
||||
|
||||
/// Code slices necessary for declaring and initializing a temporary variable
|
||||
|
|
@ -98,7 +99,10 @@ enum FieldKind {
|
|||
},
|
||||
|
||||
/// The field maps to the character data of the element.
|
||||
Text,
|
||||
Text {
|
||||
/// Optional codec to use
|
||||
codec: Option<Type>,
|
||||
},
|
||||
}
|
||||
|
||||
impl FieldKind {
|
||||
|
|
@ -143,7 +147,7 @@ impl FieldKind {
|
|||
})
|
||||
}
|
||||
|
||||
XmlFieldMeta::Text => Ok(Self::Text),
|
||||
XmlFieldMeta::Text { codec } => Ok(Self::Text { codec }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -257,10 +261,21 @@ impl FieldDef {
|
|||
})
|
||||
}
|
||||
|
||||
FieldKind::Text => {
|
||||
FieldKind::Text { ref codec } => {
|
||||
let FromEventsScope { ref text, .. } = scope;
|
||||
let field_access = scope.access_field(&self.member);
|
||||
let from_xml_text = from_xml_text_fn(self.ty.clone());
|
||||
let finalize = match codec {
|
||||
Some(codec_ty) => {
|
||||
let decode = text_codec_decode_fn(codec_ty.clone(), self.ty.clone());
|
||||
quote! {
|
||||
#decode(#field_access)?
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let from_xml_text = from_xml_text_fn(self.ty.clone());
|
||||
quote! { #from_xml_text(#field_access)? }
|
||||
}
|
||||
};
|
||||
|
||||
Ok(FieldBuilderPart::Text {
|
||||
value: FieldTempInit {
|
||||
|
|
@ -270,9 +285,7 @@ impl FieldDef {
|
|||
collect: quote! {
|
||||
#field_access.push_str(#text.as_str());
|
||||
},
|
||||
finalize: quote! {
|
||||
#from_xml_text(#field_access)?
|
||||
},
|
||||
finalize,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -318,14 +331,19 @@ impl FieldDef {
|
|||
})
|
||||
}
|
||||
|
||||
FieldKind::Text => {
|
||||
let into_xml_text = into_xml_text_fn(self.ty.clone());
|
||||
FieldKind::Text { ref codec } => {
|
||||
let generator = match codec {
|
||||
Some(codec_ty) => {
|
||||
let encode = text_codec_encode_fn(codec_ty.clone(), self.ty.clone());
|
||||
quote! { #encode(#bound_name)? }
|
||||
}
|
||||
None => {
|
||||
let into_xml_text = into_xml_text_fn(self.ty.clone());
|
||||
quote! { ::core::option::Option::Some(#into_xml_text(#bound_name)?) }
|
||||
}
|
||||
};
|
||||
|
||||
Ok(FieldIteratorPart::Text {
|
||||
generator: quote! {
|
||||
#into_xml_text(#bound_name)?
|
||||
},
|
||||
})
|
||||
Ok(FieldIteratorPart::Text { generator })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -312,7 +312,10 @@ pub(crate) enum XmlFieldMeta {
|
|||
},
|
||||
|
||||
/// `#[xml(text)]`
|
||||
Text,
|
||||
Text {
|
||||
/// The path to the optional codec type.
|
||||
codec: Option<Type>,
|
||||
},
|
||||
}
|
||||
|
||||
impl XmlFieldMeta {
|
||||
|
|
@ -393,8 +396,28 @@ impl XmlFieldMeta {
|
|||
}
|
||||
|
||||
/// Parse a `#[xml(text)]` meta.
|
||||
fn text_from_meta(_: ParseNestedMeta<'_>) -> Result<Self> {
|
||||
Ok(Self::Text)
|
||||
fn text_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
||||
let mut codec: Option<Type> = None;
|
||||
if meta.input.peek(Token![=]) {
|
||||
Ok(Self::Text {
|
||||
codec: Some(meta.value()?.parse()?),
|
||||
})
|
||||
} else if meta.input.peek(syn::token::Paren) {
|
||||
meta.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("codec") {
|
||||
if codec.is_some() {
|
||||
return Err(Error::new_spanned(meta.path, "duplicate `codec` key"));
|
||||
}
|
||||
codec = Some(meta.value()?.parse()?);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new_spanned(meta.path, "unsupported key"))
|
||||
}
|
||||
})?;
|
||||
Ok(Self::Text { codec })
|
||||
} else {
|
||||
Ok(Self::Text { codec: None })
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse [`Self`] from a nestd meta, switching on the identifier
|
||||
|
|
|
|||
|
|
@ -220,3 +220,76 @@ pub(crate) fn into_xml_text_fn(ty: Type) -> Expr {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a [`syn::TypePath`] referring to
|
||||
/// `<#codec_ty as ::xso::TextCodec::<#for_ty>>` and return the
|
||||
/// [`syn::Span`] of the `codec_ty` alongside it.
|
||||
fn text_codec_of(codec_ty: Type, for_ty: Type) -> (Span, TypePath) {
|
||||
let span = codec_ty.span();
|
||||
(
|
||||
span,
|
||||
TypePath {
|
||||
qself: Some(QSelf {
|
||||
lt_token: syn::token::Lt { spans: [span] },
|
||||
ty: Box::new(codec_ty),
|
||||
position: 2,
|
||||
as_token: Some(syn::token::As { span }),
|
||||
gt_token: syn::token::Gt { spans: [span] },
|
||||
}),
|
||||
path: Path {
|
||||
leading_colon: Some(syn::token::PathSep {
|
||||
spans: [span, span],
|
||||
}),
|
||||
segments: [
|
||||
PathSegment {
|
||||
ident: Ident::new("xso", span),
|
||||
arguments: PathArguments::None,
|
||||
},
|
||||
PathSegment {
|
||||
ident: Ident::new("TextCodec", span),
|
||||
arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
|
||||
colon2_token: Some(syn::token::PathSep {
|
||||
spans: [span, span],
|
||||
}),
|
||||
lt_token: syn::token::Lt { spans: [span] },
|
||||
args: [GenericArgument::Type(for_ty)].into_iter().collect(),
|
||||
gt_token: syn::token::Gt { spans: [span] },
|
||||
}),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Construct a [`syn::Expr`] referring to
|
||||
/// `<#codec_ty as ::xso::TextCodec::<#for_ty>>::encode`.
|
||||
pub(crate) fn text_codec_encode_fn(codec_ty: Type, for_ty: Type) -> Expr {
|
||||
let (span, mut ty) = text_codec_of(codec_ty, for_ty);
|
||||
ty.path.segments.push(PathSegment {
|
||||
ident: Ident::new("encode", span),
|
||||
arguments: PathArguments::None,
|
||||
});
|
||||
Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: ty.qself,
|
||||
path: ty.path,
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a [`syn::Expr`] referring to
|
||||
/// `<#codec_ty as ::xso::TextCodec::<#for_ty>>::decode`.
|
||||
pub(crate) fn text_codec_decode_fn(codec_ty: Type, for_ty: Type) -> Expr {
|
||||
let (span, mut ty) = text_codec_of(codec_ty, for_ty);
|
||||
ty.path.segments.push(PathSegment {
|
||||
ident: Ident::new("decode", span),
|
||||
arguments: PathArguments::None,
|
||||
});
|
||||
Expr::Path(ExprPath {
|
||||
attrs: Vec::new(),
|
||||
qself: ty.qself,
|
||||
path: ty.path,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue