xmlstream: remove T: AsXml bound from XmlStream type

While the FromXml bound is necessary for the key implementation of the
XmlStream (the `impl Stream`), the AsXml bound is not as intrinsic: the
Sink implementation can take any AsXml implementation on each separate
send invocation.

By removing this bound, we can add extra logic *only* to the parsing,
without having to worry about effects on the sending side.

Concretely, we'll be able to create a wrapper type for XmppStreamElement
which allows us to capture errors during parsing, without having to
worry about what happens during serialisation.
This commit is contained in:
Jonas Schäfer 2026-06-06 13:51:36 +02:00
commit bda08407b0
4 changed files with 15 additions and 14 deletions

View file

@ -25,6 +25,7 @@ Version NEXT:
- Update hickory-dns to 0.26 (!671)
- Drive the XMPP client stream in the background (!631)
- Add option to split XMPP client into read and write half (!631)
- Remove T: AsXml bound from XmlStream (!675)
Version 5.0.0:
2025-10-28 pep <pep@bouah.net>

View file

@ -18,7 +18,7 @@ use xmpp_parsers::{
stream_features::StreamFeatures,
};
use xso::{AsXml, FromXml};
use xso::FromXml;
use super::{
common::{RawXmlStream, ReadXso, ReadXsoError, StreamHeader},
@ -127,7 +127,7 @@ impl<Io: AsyncBufRead + AsyncWrite + Unpin> PendingFeaturesRecv<Io> {
/// If the peer sends any payload which is neither stream features nor
/// a stream error, an [`io::Error`][`std::io::Error`] with
/// [`InvalidData`][`io::ErrorKind::InvalidData`] kind is returned.
pub async fn recv_features<T: FromXml + AsXml>(
pub async fn recv_features<T: FromXml>(
self,
) -> Result<(StreamFeatures, XmlStream<Io, T>), RecvFeaturesError> {
let Self {
@ -170,7 +170,7 @@ impl<Io: AsyncBufRead + AsyncWrite + Unpin> PendingFeaturesRecv<Io> {
/// down the road (because the feature stream element cannot be handled).
/// The only place where this is useful is in
/// [XEP-0114](https://xmpp.org/extensions/xep-0114.html) connections.
pub fn skip_features<T: FromXml + AsXml>(self) -> XmlStream<Io, T> {
pub fn skip_features<T: FromXml>(self) -> XmlStream<Io, T> {
XmlStream::wrap(self.stream)
}
}

View file

@ -259,7 +259,7 @@ impl<Io, T: FromXml> XmlStream<Io, T> {
}
}
impl<Io: AsyncBufRead, T: FromXml + AsXml> XmlStream<Io, T> {
impl<Io: AsyncBufRead, T: FromXml> XmlStream<Io, T> {
fn wrap(inner: RawXmlStream<Io>) -> Self {
Self {
inner,
@ -281,7 +281,7 @@ impl<Io: AsyncBufRead, T: FromXml + AsXml> XmlStream<Io, T> {
}
}
impl<Io: AsyncBufRead + AsyncWrite + Unpin, T: FromXml + AsXml + fmt::Debug> XmlStream<Io, T> {
impl<Io: AsyncBufRead + AsyncWrite + Unpin, T: FromXml + fmt::Debug> XmlStream<Io, T> {
/// Initiate a stream reset
///
/// To actually send the stream header, call
@ -329,7 +329,7 @@ impl<Io: AsyncBufRead + AsyncWrite + Unpin, T: FromXml + AsXml + fmt::Debug> Xml
///
/// In addition, attempting to reset a stream which has been closed by
/// either side or which has had an I/O error will also cause a panic.
pub async fn accept_reset(mut self, barrier: &T) -> io::Result<AcceptedStream<Io>> {
pub async fn accept_reset<U: AsXml>(mut self, barrier: &U) -> io::Result<AcceptedStream<Io>> {
self.assert_retypable();
self.send(barrier).await?;
@ -361,7 +361,7 @@ impl<Io: AsyncBufRead + AsyncWrite + Unpin, T: FromXml + AsXml + fmt::Debug> Xml
}
}
impl<Io: AsyncBufRead, T: FromXml + AsXml + fmt::Debug> Stream for XmlStream<Io, T> {
impl<Io: AsyncBufRead, T: FromXml + fmt::Debug> Stream for XmlStream<Io, T> {
type Item = Result<T, ReadError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
@ -401,7 +401,7 @@ impl<Io: AsyncBufRead, T: FromXml + AsXml + fmt::Debug> Stream for XmlStream<Io,
}
}
impl<Io: AsyncWrite, T: FromXml + AsXml> XmlStream<Io, T> {
impl<Io: AsyncWrite, T: FromXml> XmlStream<Io, T> {
/// Initiate stream shutdown and poll for completion.
///
/// Please see [`Self::shutdown`] for details.
@ -443,7 +443,7 @@ impl<Io: AsyncWrite, T: FromXml + AsXml> XmlStream<Io, T> {
}
}
impl<Io: AsyncWrite + Unpin, T: FromXml + AsXml> XmlStream<Io, T> {
impl<Io: AsyncWrite + Unpin, T: FromXml> XmlStream<Io, T> {
/// Send the stream footer and close the sender side of the underlying
/// transport.
///
@ -459,7 +459,7 @@ impl<Io: AsyncWrite + Unpin, T: FromXml + AsXml> XmlStream<Io, T> {
}
}
impl<'x, Io: AsyncWrite, T: FromXml + AsXml, U: AsXml> Sink<&'x U> for XmlStream<Io, T> {
impl<'x, Io: AsyncWrite, T: FromXml, U: AsXml> Sink<&'x U> for XmlStream<Io, T> {
type Error = io::Error;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@ -489,11 +489,11 @@ impl<'x, Io: AsyncWrite, T: FromXml + AsXml, U: AsXml> Sink<&'x U> for XmlStream
/// Future implementing [`XmlStream::shutdown`] using
/// [`XmlStream::poll_shutdown`].
pub struct Shutdown<'a, Io: AsyncWrite, T: FromXml + AsXml> {
pub struct Shutdown<'a, Io: AsyncWrite, T: FromXml> {
stream: Pin<&'a mut XmlStream<Io, T>>,
}
impl<Io: AsyncWrite, T: FromXml + AsXml> Future for Shutdown<'_, Io, T> {
impl<Io: AsyncWrite, T: FromXml> Future for Shutdown<'_, Io, T> {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {

View file

@ -14,7 +14,7 @@ use tokio::io::{AsyncBufRead, AsyncWrite};
use xmpp_parsers::{stream_error::StreamError, stream_features::StreamFeatures};
use xso::{AsXml, FromXml};
use xso::FromXml;
use super::{
common::{RawXmlStream, StreamHeader},
@ -78,7 +78,7 @@ impl<Io: AsyncBufRead + AsyncWrite + Unpin> PendingFeaturesSend<Io> {
/// After the stream features have been sent, the stream can be used for
/// exchanging stream-level elements (stanzas or "nonzas"). The Rust type
/// for these elements must be given as type parameter `T`.
pub async fn send_features<T: FromXml + AsXml>(
pub async fn send_features<T: FromXml>(
self,
features: &'_ StreamFeatures,
) -> io::Result<XmlStream<Io, T>> {