This allows containers to reject items based on their content, without
having to resort to a panic.
The use case is containers which are polymorphic, but don't support
different item types, as encountered e.g. in XEP-0060 PubSub publish vs.
retract events.
The Compound abstraction was incorrect, as can be seen by the huge
amount of edge cases handled in obscure match statements on the
`namesace` field.
Attempting to generalize this to allow building enums which switch on an
attribute value instead of a XML element name turned out to be horror
trip.
This new abstraction has much more clear-cut responsibilities:
- Compound (de-)structures Rust fields into/from Elements.
- StructInner handles everything related to struct-like things (enum
variants and structs), with the exception of validation/preparation.
- StructDef handles validation and preparation on top of StructInner.
And then enums get the differentiation within the type system they
deserve: Those which are fully dynamic and those which are switched on
the XML element's name. That abstraction turns out to be so useful that
it probably very straightforwardly will generalize into matching on
attribute names instead.
All in all, this should be good.
The previous approach used a lot of weird heuristics and was also not
flexible enough to handle bodies of XMPP message stanzas: Those use an
optional `xml:lang` attribute [1], the absence of which should be
preserved through a roundtrip through the Rust structs.
This sounds simple: Just invent a String newtype which does not emit an
attribute if the string is empty, right?
Yes, but: We cannot specify that type for the attribute. `extract(..)`
does not allow specifying types, it just assumes `String` for anything
text-like (text and attributes). This works because the macros add an
extra conversion layer for that special case.
That obviously breaks down under such circumstances, so this commit
revamps the entire thing.
One can now specify types on extractions, but in many cases it's
optional: they are inferred from the field type for extractions on
`#[xml(child)]` fields, and otherwise they default to String for
text-like extractions (so just like before). Only when you attempt to
extract a non-String type into a collection you'll have to specify the
type explicitly.
This also obsoletes the quirky `XmlDataItem` trait.
[1]: Actually, it's a bit more tricky: `xml:lang` is inherited from
parent elements according to the XML specification, so it is
rarely fully optional.
Dynamic namespaces are useful for elements which may reside in either of
multiple namespaces. The main use-case for this is stanzas in an XML
stream, which may be in e.g. the `jabber:client` or `jabber:server`
namespaces.
The new names hopefully improve the clarity. The "Attribute" terminology
was replaced by "Optional", because that's what it's really about: The
value is *optional* because it may not be present on the protocol level
(attributes, or extracted child texts).
Likewise, "Text" was replaced by "XmlText", in anticipation of migrating
to CData at some point later and to distinguish it from arbitrary text.
This can be represented appropriately using Extend<T>, IntoIterator and
Default from the standard library. We keep the XmlDataItem trait for
flexibility though.
This commit deserves some explanation.
1. Why move from the `macro_rules!` approach to a derive macro?
A derive macro is much more idiomatic to Rust. The `macro_rules!`-based
approach, while pretty clever and full-featured, is very opaque and
unclear to crate (but not partiuclarly Rust) newcomers. Not to mention
that the implementation, with all the variations it handles in the
rather functional macro_rules!-language, got very unwieldly and hard to
maintain.
2. Why add a `xmpp_parsers_core` crate? What is its purpose and its
relation to `xmpp_parsers` and `xmpp_parsers_macros`?
From the code generated within the derive macro, we have to refer to
some traits and/or types we define. We cannot define these in the
`xmpp_parsers_macros` crate, because proc macro crates can only export
proc macros, no other items.
We also need to refer to these types unambiguously. As we don't know
which names are imported in the scopes the derive macro is invoked in,
we have to use absolute paths (such as `::foo::bar`).
That means we need a crate name.
Now there are two non-options:
- We could use `crate::`. That has the downside that the derive macro
is only useful within the `xmpp_parsers` crate. It cannot be used
outside, unless the traits are re-imported there.
- We could use `::xmpp_parsers::..`, except, we can't: That would
prevent use of the macro within `xmpp_parsers`, because crates cannot
refer to themselves by name.
So the only way that makes sense is to have a `xmpp_parsers_core` crate
which contains the traits as well as some basic implementations needed
for the macros to work, re-export the derive macros from there, and
depend on that crate from `xmpp_parsers`.
3. Oh god this is complex, how do I learn more or hack on this??
Run `cargo doc --document-private-items`. The `xmpp_parsers_macros`
crate is fully documented in the private areas, and that makes that
documentation quite accessible in your browser.