1use std::fmt;
8use std::io;
9
10use rxml::NcNameStr;
11
12use xso::{error::Error, fromxml::FallibleBuilder, AsXml, FromEventsBuilder, FromXml};
13
14use xmpp_parsers::{component, sasl, sm, starttls, stream_error::ReceivedStreamError};
15
16use crate::Stanza;
17
18use super::ReadError;
19
20#[derive(FromXml, AsXml, Debug)]
22#[xml()]
23pub enum XmppStreamElement {
24 #[xml(transparent)]
26 Stanza(Stanza),
27
28 #[xml(transparent)]
30 Sasl(sasl::Nonza),
31
32 #[xml(transparent)]
34 Starttls(starttls::Nonza),
35
36 #[xml(transparent)]
38 ComponentHandshake(component::Handshake),
39
40 #[xml(transparent)]
42 StreamError(ReceivedStreamError),
43
44 #[xml(transparent)]
46 SM(sm::Nonza),
47}
48
49#[derive(Debug)]
50pub enum PartialStanza {
51 Presence,
52 Iq,
53 Message,
54}
55
56impl fmt::Display for PartialStanza {
57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58 match self {
59 Self::Presence => f.write_str("presence"),
60 Self::Message => f.write_str("message"),
61 Self::Iq => f.write_str("iq"),
62 }
63 }
64}
65
66impl PartialStanza {
67 pub fn to_ncname(&self) -> &'static NcNameStr {
68 match self {
69 Self::Presence => rxml::xml_ncname!("presence"),
70 Self::Message => rxml::xml_ncname!("message"),
71 Self::Iq => rxml::xml_ncname!("iq"),
72 }
73 }
74}
75
76enum CapturedMetadata {
77 Nonza {
78 qname: rxml::QName,
79 },
80 Stanza {
81 ns: rxml::Namespace<'static>,
82 name: PartialStanza,
83 from: Option<String>,
84 to: Option<String>,
85 type_: Option<String>,
86 id: Option<String>,
87 },
88}
89
90impl CapturedMetadata {
91 pub fn new(qname: &rxml::QName, attrs: &rxml::AttrMap) -> Self {
92 let kind = match qname.1.as_str() {
93 "presence" => Some(PartialStanza::Presence),
94 "iq" => Some(PartialStanza::Iq),
95 "message" => Some(PartialStanza::Message),
96 _ => None,
97 };
98 if let Some(kind) = kind {
99 CapturedMetadata::Stanza {
100 ns: qname.0.clone(),
101 name: kind,
102 from: attrs.get(&rxml::Namespace::NONE, "from").cloned(),
103 to: attrs.get(&rxml::Namespace::NONE, "to").cloned(),
104 id: attrs.get(&rxml::Namespace::NONE, "id").cloned(),
105 type_: attrs.get(&rxml::Namespace::NONE, "type").cloned(),
106 }
107 } else {
108 CapturedMetadata::Nonza {
109 qname: qname.clone(),
110 }
111 }
112 }
113}
114
115pub struct FallibleStreamElementBuilder {
116 metadata: Option<CapturedMetadata>,
117 builder: FallibleBuilder<<XmppStreamElement as FromXml>::Builder, Error>,
118}
119
120impl FromEventsBuilder for FallibleStreamElementBuilder {
121 type Output = FallibleStreamElement;
122
123 fn feed(&mut self, ev: rxml::Event, ctx: &xso::Context) -> Result<Option<Self::Output>, Error> {
124 match self.builder.feed(ev, ctx) {
125 Ok(Some(output)) => Ok(Some(match output {
126 Ok(v) => FallibleStreamElement::Ok(v),
127
128 Err(Error::XmlError(e)) => unreachable!("feed somehow saw an rxml error: {e}"),
131
132 Err(Error::TypeMismatch) => unreachable!("feed somehow saw a TypeMismatch"),
134
135 Err(error) => FallibleStreamElement::Err(
136 match self.metadata.take().expect("feed called after completion") {
137 CapturedMetadata::Nonza { qname } => {
138 StreamElementError::InvalidNonza { qname, error }
139 }
140 CapturedMetadata::Stanza {
141 ns,
142 name,
143 from,
144 to,
145 type_,
146 id,
147 } => StreamElementError::InvalidStanza {
148 ns,
149 name,
150 header: RawStanzaHeader {
151 from,
152 to,
153 type_,
154 id,
155 },
156 error,
157 },
158 },
159 ),
160 })),
161 Ok(None) => Ok(None),
162 Err(e) => Err(e),
163 }
164 }
165}
166
167#[derive(Debug)]
169pub struct RawStanzaHeader {
170 pub from: Option<String>,
172
173 pub to: Option<String>,
175
176 pub type_: Option<String>,
178
179 pub id: Option<String>,
181}
182
183#[derive(Debug)]
186pub enum StreamElementError {
187 InvalidStanza {
190 ns: rxml::Namespace<'static>,
192
193 name: PartialStanza,
195
196 header: RawStanzaHeader,
198
199 error: xso::error::Error,
204 },
205
206 InvalidNonza {
212 qname: rxml::QName,
214
215 error: xso::error::Error,
220 },
221}
222
223impl fmt::Display for StreamElementError {
224 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225 match self {
226 Self::InvalidNonza { qname, error, .. } => write!(
227 f,
228 "invalid nonza received: <{{{}}}{}/> ({error})",
229 qname.0, qname.1
230 ),
231 Self::InvalidStanza {
232 ns, name, error, ..
233 } => write!(
234 f,
235 "invalid stanza received: <{{{}}}{}/> ({error})",
236 ns, name
237 ),
238 }
239 }
240}
241
242impl core::error::Error for StreamElementError {}
243
244#[derive(Debug)]
246pub enum FallibleStreamElement {
247 Ok(XmppStreamElement),
249
250 Err(StreamElementError),
252}
253
254impl FallibleStreamElement {
255 pub fn into_read_error(self) -> Result<XmppStreamElement, ReadError> {
260 match self {
261 Self::Ok(v) => Ok(v),
262 Self::Err(e) => Err(ReadError::HardError(io::Error::new(
263 io::ErrorKind::InvalidData,
264 e,
265 ))),
266 }
267 }
268}
269
270impl FromXml for FallibleStreamElement {
271 type Builder = FallibleStreamElementBuilder;
272
273 fn from_events(
274 qname: rxml::QName,
275 attrs: rxml::AttrMap,
276 ctx: &xso::Context,
277 ) -> Result<Self::Builder, xso::error::FromEventsError> {
278 let metadata = Some(CapturedMetadata::new(&qname, &attrs));
279 let builder =
280 <Result<XmppStreamElement, Error> as FromXml>::from_events(qname, attrs, ctx)?;
281 Ok(FallibleStreamElementBuilder { metadata, builder })
282 }
283}