mikrotik_proto/error.rs
1//! Error types for the MikroTik protocol implementation.
2//!
3//! This module provides a unified error hierarchy covering all levels of
4//! protocol processing: wire-format decoding, word parsing, sentence parsing,
5//! response parsing, connection state, and login.
6
7use core::{fmt, num::ParseIntError};
8
9use thiserror::Error;
10
11use crate::response::TrapResponse;
12use crate::word::Word;
13
14// ── Wire-format codec errors ──
15
16/// Errors from the wire-format codec (length prefix decoding).
17#[derive(Error, Debug, Clone, PartialEq, Eq)]
18pub enum DecodeError {
19 /// An invalid length prefix byte was encountered.
20 #[error("invalid length prefix byte: 0x{0:02x}")]
21 InvalidLengthPrefix(u8),
22}
23
24// ── Sentence-level errors ──
25
26/// Errors that can occur while processing a byte sequence into words within a sentence.
27#[derive(Error, Debug, PartialEq, Clone)]
28pub enum SentenceError {
29 /// A sequence of bytes could not be parsed into a valid [`Word`].
30 #[error("Word error: {0}")]
31 WordError(#[from] crate::word::WordError),
32 /// The prefix length of a sentence is incorrect or corrupt.
33 #[error("Invalid prefix length")]
34 PrefixLength,
35}
36
37// ── Protocol-level response parsing errors ──
38
39/// Errors that can occur while parsing a [`CommandResponse`](crate::response::CommandResponse)
40/// from a decoded sentence.
41#[derive(Error, Debug, Clone)]
42pub enum ProtocolError {
43 /// Error within the sentence structure (word parsing or length prefix).
44 #[error("Sentence error: {0}")]
45 Sentence(#[from] SentenceError),
46 /// The response is missing required words to be valid.
47 #[error("Incomplete response: {0}")]
48 Incomplete(#[from] MissingWord),
49 /// An unexpected word type was encountered in the response sequence.
50 #[error("Unexpected word type: found {word:?}, expected one of {expected:?}")]
51 WordSequence {
52 /// The unexpected [`WordType`] that was encountered.
53 word: WordType,
54 /// The expected [`WordType`] variants.
55 expected: alloc::vec::Vec<WordType>,
56 },
57 /// Error parsing or identifying a trap response category.
58 #[error("Trap category error: {0}")]
59 TrapCategory(#[from] TrapCategoryError),
60}
61
62/// Types of words that can be missing from a response.
63#[derive(Error, Debug, Clone, Copy)]
64pub enum MissingWord {
65 /// Missing `.tag` — all tagged responses must have a tag.
66 #[error("missing tag")]
67 Tag,
68 /// Missing category (`!done`, `!re`, `!trap`, `!fatal`, `!empty`).
69 #[error("missing category")]
70 Category,
71 /// Missing message in a fatal response.
72 #[error("missing message")]
73 Message,
74}
75
76/// Discriminant for word types, used in error reporting.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum WordType {
79 /// Tag word (`.tag=...`).
80 Tag,
81 /// Category word (`!done`, `!re`, etc.).
82 Category,
83 /// Attribute word (`=key=value`).
84 Attribute,
85 /// Message word (free-form text).
86 Message,
87}
88
89impl fmt::Display for WordType {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 WordType::Tag => write!(f, "tag"),
93 WordType::Category => write!(f, "category"),
94 WordType::Attribute => write!(f, "attribute"),
95 WordType::Message => write!(f, "message"),
96 }
97 }
98}
99
100impl From<Word<'_>> for WordType {
101 fn from(word: Word) -> Self {
102 match word {
103 Word::Tag(_) => WordType::Tag,
104 Word::Category(_) => WordType::Category,
105 Word::Attribute(_) => WordType::Attribute,
106 Word::Message(_) => WordType::Message,
107 }
108 }
109}
110
111// ── Trap category errors ──
112
113/// Errors that can occur while parsing trap categories in response sentences.
114#[derive(Error, Debug, Clone)]
115pub enum TrapCategoryError {
116 /// An invalid numeric value was encountered while parsing a trap category.
117 #[error("Invalid trap category value: {0}")]
118 Invalid(#[source] ParseIntError),
119 /// The trap category number is out of the valid range (0-7).
120 #[error("Trap category out of range: {0} (valid range: 0-7)")]
121 OutOfRange(u8),
122 /// An unexpected attribute was found in a trap response.
123 #[error("Invalid trap attribute: key={key}, value={value:?}")]
124 InvalidAttribute {
125 /// The key of the invalid attribute.
126 key: alloc::string::String,
127 /// The value of the invalid attribute, if present.
128 value: Option<alloc::string::String>,
129 },
130 /// The required `message` attribute is missing from a trap response.
131 #[error("Missing message attribute in trap response")]
132 MissingMessageAttribute,
133}
134
135// ── Connection state machine errors ──
136
137/// Errors from the [`Connection`](crate::connection::Connection) state machine.
138#[derive(Error, Debug, Clone)]
139pub enum ConnectionError {
140 /// A wire-format decoding error occurred.
141 #[error("decode error: {0}")]
142 Decode(#[from] DecodeError),
143 /// A protocol-level parsing error occurred.
144 #[error("protocol error: {0}")]
145 Protocol(#[from] ProtocolError),
146 /// The connection has been fatally shut down and cannot accept new operations.
147 #[error("connection is closed")]
148 Closed,
149}
150
151// ── Login handshake errors ──
152
153/// Errors from the login handshake process.
154#[derive(Error, Debug, Clone)]
155pub enum LoginError {
156 /// The router rejected the login credentials.
157 #[error("authentication failed: {0}")]
158 Authentication(TrapResponse),
159 /// A fatal error occurred during login.
160 #[error("fatal error during login: {0}")]
161 Fatal(alloc::string::String),
162 /// A protocol error occurred during login.
163 #[error("protocol error during login: {0}")]
164 Protocol(#[from] ProtocolError),
165 /// A connection error occurred during login.
166 #[error("connection error during login: {0}")]
167 Connection(#[from] ConnectionError),
168}