Skip to main content

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}