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}