1use std::{
2 convert::Infallible,
3 fmt::{self, Debug},
4 str::{from_utf8, FromStr},
5 sync::Arc,
6};
7
8use quick_xml::{
9 events::{BytesStart, Event},
10 name::ResolveResult,
11 NsReader,
12};
13
14use crate::{message::ReadError, session::SessionId};
15
16use super::{xmlns, ReadXml};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Errors {
20 inner: Vec<Error>,
21}
22
23impl Errors {
24 pub(super) const fn new() -> Self {
25 Self { inner: Vec::new() }
26 }
27
28 #[must_use]
29 pub fn is_empty(&self) -> bool {
30 self.inner.is_empty()
31 }
32
33 #[must_use]
34 pub fn len(&self) -> usize {
35 self.inner.len()
36 }
37
38 pub(super) fn push(&mut self, err: Error) {
39 self.inner.push(err);
40 }
41
42 pub fn iter(&self) -> impl Iterator<Item = &Error> {
43 self.inner.iter()
44 }
45}
46
47impl fmt::Display for Errors {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 self.iter().try_for_each(|err| writeln!(f, "{err}"))
50 }
51}
52
53impl std::error::Error for Errors {}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct Error {
57 error_type: Type,
58 error_tag: Tag,
59 severity: Severity,
60 app_tag: Option<AppTag>,
61 path: Option<Path>,
62 message: Option<Message>,
63 info: Info,
64}
65
66impl ReadXml for Error {
67 #[tracing::instrument(skip_all, fields(tag = ?start.local_name()), level = "debug")]
68 fn read_xml(reader: &mut NsReader<&[u8]>, start: &BytesStart<'_>) -> Result<Self, ReadError> {
69 let end = start.to_end();
70 let mut error_type = None;
71 let mut error_tag = None;
72 let mut severity = None;
73 let mut app_tag = None;
74 let mut path = None;
75 let mut message = None;
76 let mut info = None;
77 loop {
78 match reader.read_resolved_event()? {
79 (ResolveResult::Bound(ns), Event::Start(tag))
80 if ns == xmlns::BASE
81 && tag.local_name().as_ref() == b"error-type"
82 && error_type.is_none() =>
83 {
84 tracing::debug!(?tag);
85 error_type = Some(reader.read_text(tag.to_end().name())?.trim().parse()?);
86 }
87 (ResolveResult::Bound(ns), Event::Start(tag))
88 if ns == xmlns::BASE
89 && tag.local_name().as_ref() == b"error-tag"
90 && error_tag.is_none() =>
91 {
92 tracing::debug!(?tag);
93 error_tag = Some(reader.read_text(tag.to_end().name())?.trim().parse()?);
94 }
95 (ResolveResult::Bound(ns), Event::Start(tag))
96 if ns == xmlns::BASE
97 && tag.local_name().as_ref() == b"error-severity"
98 && severity.is_none() =>
99 {
100 tracing::debug!(?tag);
101 severity = Some(reader.read_text(tag.to_end().name())?.trim().parse()?);
102 }
103 (ResolveResult::Bound(ns), Event::Start(tag))
104 if ns == xmlns::BASE
105 && tag.local_name().as_ref() == b"error-app-tag"
106 && app_tag.is_none() =>
107 {
108 tracing::debug!(?tag);
109 app_tag = Some(
110 reader
111 .read_text(tag.to_end().name())?
112 .trim()
113 .parse()
114 .unwrap_or_else(|_| unreachable!()),
115 );
116 }
117 (ResolveResult::Bound(ns), Event::Start(tag))
118 if ns == xmlns::BASE
119 && tag.local_name().as_ref() == b"error-path"
120 && path.is_none() =>
121 {
122 tracing::debug!(?tag);
123 path = Some(
124 reader
125 .read_text(tag.to_end().name())?
126 .trim()
127 .parse()
128 .unwrap_or_else(|_| unreachable!()),
129 );
130 }
131 (ResolveResult::Bound(ns), Event::Start(tag))
132 if ns == xmlns::BASE
133 && tag.local_name().as_ref() == b"error-message"
134 && message.is_none() =>
135 {
136 tracing::debug!(?tag);
137 message = Some(
138 reader
139 .read_text(tag.to_end().name())?
140 .trim()
141 .parse()
142 .unwrap_or_else(|_| unreachable!()),
143 );
144 }
145 (ResolveResult::Bound(ns), Event::Start(tag))
146 if ns == xmlns::BASE
147 && tag.local_name().as_ref() == b"error-info"
148 && info.is_none() =>
149 {
150 tracing::debug!(?tag);
151 info = Some(Info::read_xml(reader, &tag)?);
152 }
153 (_, Event::Comment(_)) => continue,
154 (_, Event::End(tag)) if tag == end => break,
155 (ns, event) => {
156 tracing::error!(?event, ?ns, "unexpected xml event");
157 return Err(ReadError::UnexpectedXmlEvent(event.into_owned()));
158 }
159 }
160 }
161 Ok(Self {
162 error_type: error_type
163 .ok_or_else(|| ReadError::missing_element("rpc-error", "error-type"))?,
164 error_tag: error_tag
165 .ok_or_else(|| ReadError::missing_element("rpc-error", "error-tag"))?,
166 severity: severity
167 .ok_or_else(|| ReadError::missing_element("rpc-error", "error-severity"))?,
168 app_tag,
169 path,
170 message,
171 info: info.unwrap_or_else(Info::new),
172 })
173 }
174}
175
176impl fmt::Display for Error {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 write!(
179 f,
180 "{} {}: {}",
181 self.error_type, self.severity, self.error_tag
182 )
183 }
184}
185
186impl std::error::Error for Error {}
187
188#[derive(Debug, Copy, Clone, PartialEq, Eq)]
189pub enum Type {
190 Transport,
191 Rpc,
192 Protocol,
193 Application,
194}
195
196impl FromStr for Type {
197 type Err = ReadError;
198
199 fn from_str(s: &str) -> Result<Self, Self::Err> {
200 match s {
201 "transport" => Ok(Self::Transport),
202 "rpc" => Ok(Self::Rpc),
203 "protocol" => Ok(Self::Protocol),
204 "application" => Ok(Self::Application),
205 _ => Err(Self::Err::UnknownErrorType(s.to_string())),
206 }
207 }
208}
209
210impl fmt::Display for Type {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 let ty = match self {
213 Self::Transport => "transport",
214 Self::Rpc => "rpc",
215 Self::Protocol => "protocol",
216 Self::Application => "application",
217 };
218 f.write_str(ty)
219 }
220}
221
222#[derive(Debug, Copy, Clone, PartialEq, Eq)]
223pub enum Tag {
224 InUse,
225 InvalidValue,
226 TooBig,
227 MissingAttribute,
228 BadAttribute,
229 UnknownAttribute,
230 MissingElement,
231 BadElement,
232 UnknownElement,
233 UnknownNamespace,
234 AccessDenied,
235 LockDenied,
236 ResourceDenied,
237 RollbackFailed,
238 DataExists,
239 DataMissing,
240 OperationNotSupported,
241 OperationFailed,
242 MalformedMessage,
243
244 PartialOperation,
246}
247
248impl FromStr for Tag {
249 type Err = ReadError;
250
251 fn from_str(s: &str) -> Result<Self, Self::Err> {
252 match s {
253 "in-use" => Ok(Self::InUse),
254 "invalid-value" => Ok(Self::InvalidValue),
255 "too-big" => Ok(Self::TooBig),
256 "missing-attribute" => Ok(Self::MissingAttribute),
257 "bad-attribute" => Ok(Self::BadAttribute),
258 "unknown-attribute" => Ok(Self::UnknownAttribute),
259 "missing-element" => Ok(Self::MissingElement),
260 "bad-element" => Ok(Self::BadElement),
261 "unknown-element" => Ok(Self::UnknownElement),
262 "unknown-namespace" => Ok(Self::UnknownNamespace),
263 "access-denied" => Ok(Self::AccessDenied),
264 "lock-denied" => Ok(Self::LockDenied),
265 "resource-denied" => Ok(Self::ResourceDenied),
266 "rollback-failed" => Ok(Self::RollbackFailed),
267 "data-exists" => Ok(Self::DataExists),
268 "data-missing" => Ok(Self::DataMissing),
269 "operation-not-supported" => Ok(Self::OperationNotSupported),
270 "operation-failed" => Ok(Self::OperationFailed),
271 "malformed-message" => Ok(Self::MalformedMessage),
272 "partial-operation" => Ok(Self::PartialOperation),
273 _ => Err(Self::Err::UnknownErrorTag(s.to_string())),
274 }
275 }
276}
277
278impl fmt::Display for Tag {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 let msg = match self {
281 Self::InUse => "The request requires a resource that already is in use",
282 Self::InvalidValue => "The request specifies an unacceptable value for one or more parameters",
283 Self::TooBig => "The request or response (that would be generated) is too large for the implementation to handle",
284 Self::MissingAttribute => "An expected attribute is missing",
285 Self::BadAttribute => "An attribute value is not correct; e.g., wrong type, out of range, pattern mismatch",
286 Self::UnknownAttribute => "An unexpected attribute is present",
287 Self::MissingElement => "An expected element is missing",
288 Self::BadElement => "An element value is not correct; e.g., wrong type, out of range, pattern mismatch",
289 Self::UnknownElement => "An unexpected element is present",
290 Self::UnknownNamespace => "An unexpected namespace is present",
291 Self::AccessDenied => "Access to the requested protocol operation or data model is denied because authorization failed",
292 Self::LockDenied => "Access to the requested lock is denied because the lock is currently held by another entity",
293 Self::ResourceDenied => "Request could not be completed because of insufficient resources",
294 Self::RollbackFailed => "Request to roll back some configuration change (via rollback-on-error or <discard-changes> operations) was not completed for some reason",
295 Self::DataExists => "Request could not be completed because the relevant data model content already exists",
296 Self::DataMissing => "Request could not be completed because the relevant data model content does not exist",
297 Self::OperationNotSupported => "Request could not be completed because the requested operation is not supported by this implementation",
298 Self::OperationFailed => "Request could not be completed because the requested operation failed for some reason not covered by any other error condition",
299 Self::MalformedMessage => "A message could not be handled because it failed to be parsed correctly",
300 Self::PartialOperation => "Some part of the requested operation failed or was not attempted for some reason",
301 };
302 f.write_str(msg)
303 }
304}
305
306#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307pub enum Severity {
308 Warning,
309 Error,
310}
311
312impl FromStr for Severity {
313 type Err = ReadError;
314
315 fn from_str(s: &str) -> Result<Self, Self::Err> {
316 match s {
317 "error" => Ok(Self::Error),
318 "warning" => Ok(Self::Warning),
319 _ => Err(Self::Err::UnknownErrorSeverity(s.to_string())),
320 }
321 }
322}
323
324impl fmt::Display for Severity {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 let severity = match self {
327 Self::Error => "error",
328 Self::Warning => "warning",
329 };
330 f.write_str(severity)
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
335pub struct AppTag {
336 inner: Arc<str>,
337}
338
339impl FromStr for AppTag {
340 type Err = Infallible;
341
342 fn from_str(s: &str) -> Result<Self, Self::Err> {
343 Ok(Self { inner: s.into() })
344 }
345}
346
347#[derive(Debug, Clone, PartialEq, Eq)]
348pub struct Path {
349 inner: Arc<str>,
350}
351
352impl FromStr for Path {
353 type Err = Infallible;
354
355 fn from_str(s: &str) -> Result<Self, Self::Err> {
356 Ok(Self { inner: s.into() })
357 }
358}
359
360#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct Message {
362 lang: (),
364 inner: Arc<str>,
365}
366
367impl FromStr for Message {
368 type Err = Infallible;
369
370 fn from_str(s: &str) -> Result<Self, Self::Err> {
371 Ok(Self {
372 lang: (),
373 inner: s.into(),
374 })
375 }
376}
377
378#[derive(Debug, Clone, PartialEq, Eq)]
379pub struct Info {
380 inner: Vec<InfoElement>,
381}
382
383impl Info {
384 const fn new() -> Self {
385 Self { inner: Vec::new() }
386 }
387}
388
389impl ReadXml for Info {
390 #[tracing::instrument(skip_all, fields(tag = ?start.local_name()), level = "debug")]
391 fn read_xml(reader: &mut NsReader<&[u8]>, start: &BytesStart<'_>) -> Result<Self, ReadError> {
392 let end = start.to_end();
393 let mut inner = Vec::new();
394 tracing::debug!("expecting error-info element");
395 loop {
396 match reader.read_resolved_event()? {
397 (ResolveResult::Bound(ns), Event::Start(tag)) if ns == xmlns::BASE => {
398 match tag.local_name().as_ref() {
399 b"bad-attribute" => inner.push(InfoElement::BadAttribute(
400 reader.read_text(tag.to_end().name())?.as_ref().into(),
401 )),
402 b"bad-element" => inner.push(InfoElement::BadElement(
403 reader.read_text(tag.to_end().name())?.as_ref().into(),
404 )),
405 b"bad-namespace" => inner.push(InfoElement::BadNamespace(
406 reader.read_text(tag.to_end().name())?.as_ref().into(),
407 )),
408 b"session-id" => inner.push(InfoElement::SessionId(
409 reader
410 .read_text(tag.to_end().name())?
411 .as_ref()
412 .parse()
413 .map_err(ReadError::SessionIdParse)
414 .map(|session_id| SessionId::new(session_id).ok())?,
415 )),
416 b"ok-element" => inner.push(InfoElement::OkElement(
417 reader.read_text(tag.to_end().name())?.as_ref().into(),
418 )),
419 b"err-element" => inner.push(InfoElement::ErrElement(
420 reader.read_text(tag.to_end().name())?.as_ref().into(),
421 )),
422 b"noop-element" => inner.push(InfoElement::NoopElement(
423 reader.read_text(tag.to_end().name())?.as_ref().into(),
424 )),
425 name => {
426 return Err(ReadError::UnknownErrorInfo(from_utf8(name)?.to_string()))
427 }
428 }
429 }
430 (_, Event::Comment(_)) => continue,
431 (_, Event::End(tag)) if tag == end => break,
432 (ns, event) => {
433 tracing::error!(?event, ?ns, "unexpected xml event");
434 return Err(ReadError::UnexpectedXmlEvent(event.into_owned()));
435 }
436 }
437 }
438 Ok(Self { inner })
439 }
440}
441
442#[derive(Debug, Clone, PartialEq, Eq)]
443pub enum InfoElement {
444 BadAttribute(Arc<str>),
445 BadElement(Arc<str>),
446 BadNamespace(Arc<str>),
447 SessionId(Option<SessionId>),
448
449 OkElement(Arc<str>),
451 ErrElement(Arc<str>),
452 NoopElement(Arc<str>),
453}
454
455#[cfg(test)]
456mod tests {
457 use std::io::Write;
458
459 use quick_xml::Writer;
460
461 use super::*;
462 use crate::{
463 capabilities::Requirements,
464 message::{
465 rpc::{operation, EmptyReply, MessageId, Operation, PartialReply, Reply},
466 ServerMsg, WriteError, WriteXml,
467 },
468 };
469
470 #[derive(Debug, PartialEq)]
471 struct Dummy;
472
473 impl Operation for Dummy {
474 const NAME: &'static str = "dummy";
475 const REQUIRED_CAPABILITIES: Requirements = Requirements::None;
476 type Builder<'a> = Builder;
477 type Reply = EmptyReply;
478 }
479
480 impl WriteXml for Dummy {
481 fn write_xml<W: Write>(&self, _: &mut Writer<W>) -> Result<(), WriteError> {
482 Ok(())
483 }
484 }
485
486 #[derive(Debug)]
487 struct Builder;
488
489 impl operation::Builder<'_, Dummy> for Builder {
490 fn new(_: &crate::session::Context) -> Self {
491 Self
492 }
493
494 fn finish(self) -> Result<Dummy, crate::Error> {
495 Ok(Dummy)
496 }
497 }
498
499 #[test]
500 fn deserialize_error_reply_rfc6241_s1_example1() {
501 let data = r#"
503 <rpc-reply message-id="101"
504 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
505 <rpc-error>
506 <error-type>rpc</error-type>
507 <error-tag>missing-attribute</error-tag>
508 <error-severity>error</error-severity>
509 <error-info>
510 <bad-attribute>message-id</bad-attribute>
511 <bad-element>rpc</bad-element>
512 </error-info>
513 </rpc-error>
514 </rpc-reply>
515 "#;
516 let expect: Reply<Dummy> = Reply {
517 message_id: MessageId(101),
518 inner: EmptyReply::Errs(Errors {
519 inner: vec![Error {
520 error_type: Type::Rpc,
521 error_tag: Tag::MissingAttribute,
522 severity: Severity::Error,
523 app_tag: None,
524 path: None,
525 message: None,
526 info: Info {
527 inner: vec![
528 InfoElement::BadAttribute("message-id".into()),
529 InfoElement::BadElement("rpc".into()),
530 ],
531 },
532 }],
533 }),
534 };
535 assert_eq!(
536 expect,
537 PartialReply::from_xml(data)
538 .and_then(Reply::try_from)
539 .unwrap()
540 );
541 }
542 #[test]
543 fn deserialize_error_reply_rfc6241_s1_example2() {
544 let data = r#"
545 <rpc-reply message-id="101"
546 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
547 xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
548 <rpc-error>
549 <error-type>application</error-type>
550 <error-tag>invalid-value</error-tag>
551 <error-severity>error</error-severity>
552 <error-path xmlns:t="http://example.com/schema/1.2/config">
553 /t:top/t:interface[t:name="Ethernet0/0"]/t:mtu
554 </error-path>
555 <error-message xml:lang="en">
556 MTU value 25000 is not within range 256..9192
557 </error-message>
558 </rpc-error>
559 <rpc-error>
560 <error-type>application</error-type>
561 <error-tag>invalid-value</error-tag>
562 <error-severity>error</error-severity>
563 <error-path xmlns:t="http://example.com/schema/1.2/config">
564 /t:top/t:interface[t:name="Ethernet1/0"]/t:address/t:name
565 </error-path>
566 <error-message xml:lang="en">
567 Invalid IP address for interface Ethernet1/0
568 </error-message>
569 </rpc-error>
570 </rpc-reply>
571 "#;
572 let expect: Reply<Dummy> = Reply {
573 message_id: MessageId(101),
574 inner: EmptyReply::Errs(Errors {
575 inner: vec![
576 Error {
577 error_type: Type::Application,
578 error_tag: Tag::InvalidValue,
579 severity: Severity::Error,
580 app_tag: None,
581 path: Some(Path {
582 inner: r#"/t:top/t:interface[t:name="Ethernet0/0"]/t:mtu"#.into(),
583 }),
584 message: Some(Message {
585 lang: (),
586 inner: "MTU value 25000 is not within range 256..9192".into(),
587 }),
588 info: Info::new(),
589 },
590 Error {
591 error_type: Type::Application,
592 error_tag: Tag::InvalidValue,
593 severity: Severity::Error,
594 app_tag: None,
595 path: Some(Path {
596 inner: r#"/t:top/t:interface[t:name="Ethernet1/0"]/t:address/t:name"#
597 .into(),
598 }),
599 message: Some(Message {
600 lang: (),
601 inner: "Invalid IP address for interface Ethernet1/0".into(),
602 }),
603 info: Info::new(),
604 },
605 ],
606 }),
607 };
608 assert_eq!(
609 expect,
610 PartialReply::from_xml(data)
611 .and_then(Reply::try_from)
612 .unwrap()
613 );
614 }
615}