ircv3_parse/
error.rs

1use std::fmt;
2
3#[derive(Clone, PartialEq, thiserror::Error)]
4pub enum IRCError {
5    #[error("Cannot parse empty message")]
6    EmptyInput,
7    #[error("{component} must be followed by a space")]
8    MissingSpace { component: &'static str },
9
10    #[error(transparent)]
11    Tag(#[from] TagError),
12    #[error(transparent)]
13    Source(#[from] SourceError),
14    #[error(transparent)]
15    Command(#[from] CommandError),
16    #[error(transparent)]
17    Param(#[from] ParamError),
18
19    #[error(transparent)]
20    Hostname(#[from] HostnameError),
21}
22
23impl fmt::Debug for IRCError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "IRC-PARSER[{}]: {}", self.code(), self)
26    }
27}
28
29impl IRCError {
30    pub fn code(&self) -> &'static str {
31        match self {
32            Self::EmptyInput => "SCAN",
33            Self::Command(cmd) => cmd.code(),
34            Self::Tag(tag) => tag.code(),
35            Self::Source(src) => src.code(),
36            Self::Param(param) => param.code(),
37            Self::Hostname(host) => host.code(),
38            Self::MissingSpace { component } => component,
39        }
40    }
41}
42
43#[derive(Debug, Clone, PartialEq, thiserror::Error)]
44pub enum TagError {
45    #[error("Tags cannot be empty")]
46    Empty,
47
48    #[error("Tag key cannot be empty")]
49    EmptyKey,
50
51    #[error("Tag key contains invalid character '{char}' at position {position}")]
52    InvalidKeyChar { char: char, position: usize },
53    #[error("Tag value contains invalid character '{char}' at position {position}")]
54    InvalidValueChar { char: char, position: usize },
55
56    #[error(transparent)]
57    Hostname(#[from] HostnameError),
58}
59
60impl TagError {
61    pub fn code(&self) -> &'static str {
62        "TAG"
63    }
64}
65
66#[derive(Debug, Clone, PartialEq, thiserror::Error)]
67pub enum SourceError {
68    #[error("Source cannot be empty")]
69    Empty,
70    #[error("Nickname cannot be empty")]
71    EmptyNick,
72    #[error("Nickname must start with letter, got '{char}'")]
73    InvalidNickFirstChar { char: char },
74    #[error("Nickname contains invalid character '{char}' at position {position} (only letters, digits, and special chars allowed)")]
75    InvalidNickChar { char: char, position: usize },
76
77    #[error("Username contains invalid character '{char}' at position {position} (control characters not allowed)")]
78    InvalidUserChar { char: char, position: usize },
79
80    #[error(transparent)]
81    Hostname(#[from] HostnameError),
82}
83
84impl SourceError {
85    pub fn code(&self) -> &'static str {
86        "SOURCE"
87    }
88}
89
90#[derive(Debug, Clone, PartialEq, thiserror::Error)]
91pub enum CommandError {
92    #[error("Command cannot be empty")]
93    Empty,
94    #[error("Command must start with letter or digit, got '{char}'")]
95    InvalidFirstChar { char: char },
96    #[error("Command must be letters or 3-digit number, got '{input}' at position {position}")]
97    InvalidCommand { input: String, position: usize },
98    #[error("Numeric command must be exactly 3 digits, got {actual} digits")]
99    WrongDigitCount { actual: usize },
100}
101impl CommandError {
102    pub fn code(&self) -> &'static str {
103        "CMD"
104    }
105}
106
107#[derive(Debug, Clone, PartialEq, thiserror::Error)]
108pub enum ParamError {
109    #[error("Parameter middle cannot be empty")]
110    EmptyMiddle,
111    #[error("Parameter middle contains invalid character '{char}' at position {position}")]
112    InvalidMiddleChar { char: char, position: usize },
113    #[error("Parameter contains forbidden control character (colon, space, CR, LF, NUL)")]
114    ContainsControlChar,
115}
116impl ParamError {
117    pub fn code(&self) -> &'static str {
118        "PARAM"
119    }
120}
121
122#[derive(Debug, Clone, PartialEq, thiserror::Error)]
123pub enum HostnameError {
124    #[error("Hostname cannot be empty")]
125    Empty,
126    #[error("Hostname label cannot be empty")]
127    EmptyLabel,
128
129    #[error("Hostname label must start with letter or digit, got '{char}'")]
130    InvalidFirstChar { char: char },
131    #[error("Hostname label cannot end with hypen, got '{char}'")]
132    InvalidLastChar { char: char },
133    #[error("Hostname label contains invalid character '{char}'")]
134    InvalidChar { char: char },
135
136    #[error("Hostname label exceeds maximum length (max {max} characters, got {actual})")]
137    LabelTooLong { max: usize, actual: usize },
138    #[error("Hostname exceeds maximum depth (max {max} labels, got {actual})")]
139    TooManyLabels { max: usize, actual: usize },
140    #[error("Hostname exceeds maximum total length (max {max} characters, got {actual})")]
141    TooLong { max: usize, actual: usize },
142}
143
144impl HostnameError {
145    pub fn code(&self) -> &'static str {
146        "RFC952"
147    }
148}