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
|
|
@ -34,6 +34,7 @@ such:
|
|||
- *path*: A Rust path, like `some_crate::foo::Bar`. Note that `foo` on its own
|
||||
is also a path.
|
||||
- *string literal*: A string literal, like `"hello world!"`.
|
||||
- *type*: A Rust type.
|
||||
- flag: Has no value. The key's mere presence has relevance and it must not be
|
||||
followed by a `=` sign.
|
||||
|
||||
|
|
@ -137,14 +138,27 @@ assert_eq!(foo, Foo {
|
|||
#### `text` meta
|
||||
|
||||
The `text` meta causes the field to be mapped to the text content of the
|
||||
element. For `FromXml`, the field's type must implement [`FromXmlText`] and
|
||||
for `IntoXml`, the field's type must implement [`IntoXmlText`].
|
||||
element.
|
||||
|
||||
The `text` meta supports no options or value. Only a single field per struct
|
||||
may be annotated with `#[xml(text)]` at a time, to avoid parsing ambiguities.
|
||||
This is also true if only `IntoXml` is derived on a field, for consistency.
|
||||
| Key | Value type | Description |
|
||||
| --- | --- | --- |
|
||||
| `codec` | *type* | Optional [`TextCodec`] implementation which is used to encode or decode the field. |
|
||||
|
||||
##### Example
|
||||
If `codec` is given, the given `codec` must implement
|
||||
[`TextCodec<T>`][`TextCodec`] where `T` is the type of the field.
|
||||
|
||||
If `codec` is *not* given, the field's type must implement [`FromXmlText`] for
|
||||
`FromXml` and for `IntoXml`, the field's type must implement [`IntoXmlText`].
|
||||
|
||||
The `text` meta also supports a shorthand syntax, `#[xml(text = ..)]`, where
|
||||
the value is treated as the value for the `codec` key (with optional prefix as
|
||||
described above, and unnamespaced otherwise).
|
||||
|
||||
Only a single field per struct may be annotated with `#[xml(text)]` at a time,
|
||||
to avoid parsing ambiguities. This is also true if only `IntoXml` is derived on
|
||||
a field, for consistency.
|
||||
|
||||
##### Example without codec
|
||||
|
||||
```rust
|
||||
# use xso::FromXml;
|
||||
|
|
@ -160,3 +174,20 @@ assert_eq!(foo, Foo {
|
|||
a: "hello".to_string(),
|
||||
});
|
||||
```
|
||||
|
||||
##### Example with codec
|
||||
|
||||
```rust
|
||||
# use xso::FromXml;
|
||||
#[derive(FromXml, Debug, PartialEq)]
|
||||
#[xml(namespace = "urn:example", name = "foo")]
|
||||
struct Foo {
|
||||
#[xml(text = xso::text::EmptyAsNone)]
|
||||
a: Option<String>,
|
||||
};
|
||||
|
||||
let foo: Foo = xso::from_bytes(b"<foo xmlns='urn:example'/>").unwrap();
|
||||
assert_eq!(foo, Foo {
|
||||
a: None,
|
||||
});
|
||||
```
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub mod error;
|
|||
#[cfg(feature = "minidom")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "minidom")))]
|
||||
pub mod minidom_compat;
|
||||
mod text;
|
||||
pub mod text;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod exports {
|
||||
|
|
@ -35,6 +35,9 @@ pub mod exports {
|
|||
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use text::TextCodec;
|
||||
|
||||
#[doc = include_str!("from_xml_doc.md")]
|
||||
#[doc(inline)]
|
||||
#[cfg(feature = "macros")]
|
||||
|
|
|
|||
|
|
@ -103,3 +103,60 @@ convert_via_fromstr_and_display! {
|
|||
#[cfg(feature = "jid")]
|
||||
jid::BareJid,
|
||||
}
|
||||
|
||||
/// Represent a way to encode/decode text data into a Rust type.
|
||||
///
|
||||
/// This trait can be used in scenarios where implementing [`FromXmlText`]
|
||||
/// and/or [`IntoXmlText`] on a type is not feasible or sensible, such as the
|
||||
/// following:
|
||||
///
|
||||
/// 1. The type originates in a foreign crate, preventing the implementation
|
||||
/// of foreign traits.
|
||||
///
|
||||
/// 2. There is more than one way to convert a value to/from XML.
|
||||
///
|
||||
/// The codec to use for a text can be specified in the attributes understood
|
||||
/// by `FromXml` and `IntoXml` derive macros. See the documentation of the
|
||||
/// [`FromXml`][`macro@crate::FromXml`] derive macro for details.
|
||||
pub trait TextCodec<T> {
|
||||
/// Decode a string value into the type.
|
||||
fn decode(s: String) -> Result<T, Error>;
|
||||
|
||||
/// Encode the type as string value.
|
||||
///
|
||||
/// If this returns `None`, the string value is not emitted at all.
|
||||
fn encode(value: T) -> Result<Option<String>, Error>;
|
||||
}
|
||||
|
||||
/// Text codec which does no transform.
|
||||
pub struct Plain;
|
||||
|
||||
impl TextCodec<String> for Plain {
|
||||
fn decode(s: String) -> Result<String, Error> {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn encode(value: String) -> Result<Option<String>, Error> {
|
||||
Ok(Some(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Text codec which returns None instead of the empty string.
|
||||
pub struct EmptyAsNone;
|
||||
|
||||
impl TextCodec<Option<String>> for EmptyAsNone {
|
||||
fn decode(s: String) -> Result<Option<String>, Error> {
|
||||
if s.len() == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(s))
|
||||
}
|
||||
}
|
||||
|
||||
fn encode(value: Option<String>) -> Result<Option<String>, Error> {
|
||||
Ok(match value {
|
||||
Some(v) if v.len() > 0 => Some(v),
|
||||
Some(_) | None => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue