1use std::{error::Error, fmt};
2
3#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
5#[non_exhaustive]
6pub enum ErrorKind {
7 Empty,
9 TooLong,
11 InvalidScheme,
13 SchemeMissing,
15 InvalidChar(char),
17 InvalidPercentEncoding,
19 InvalidAuthority,
21 AuthorityMissing,
23 InvalidHost,
25 InvalidIpv4,
27 InvalidIpv6,
29 InvalidPort,
31 PortOutOfRange,
33 InvalidPath,
35}
36
37impl fmt::Display for ErrorKind {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 ErrorKind::Empty => f.write_str("empty string"),
41 ErrorKind::TooLong => f.write_str("url is too long"),
42 ErrorKind::InvalidScheme => f.write_str("invalid scheme"),
43 ErrorKind::SchemeMissing => f.write_str("scheme missing"),
44 ErrorKind::InvalidChar(c) => write!(f, "invalid character {c:?}"),
45 ErrorKind::InvalidPercentEncoding => f.write_str("invalid percent-encoding"),
46 ErrorKind::InvalidAuthority => f.write_str("invalid authority"),
47 ErrorKind::AuthorityMissing => f.write_str("authority missing"),
48 ErrorKind::InvalidHost => f.write_str("invalid host"),
49 ErrorKind::InvalidIpv4 => f.write_str("invalid IPv4 address"),
50 ErrorKind::InvalidIpv6 => f.write_str("invalid IPv6 address"),
51 ErrorKind::InvalidPort => f.write_str("invalid port"),
52 ErrorKind::PortOutOfRange => f.write_str("port out of range"),
53 ErrorKind::InvalidPath => f.write_str("invalid path"),
54 }
55 }
56}
57
58#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
60pub struct InvalidUrl {
61 kind: ErrorKind,
62 position: Option<usize>,
63}
64
65impl InvalidUrl {
66 pub(crate) const fn new(kind: ErrorKind) -> Self {
67 InvalidUrl {
68 kind,
69 position: None,
70 }
71 }
72
73 pub(crate) const fn at(kind: ErrorKind, position: usize) -> Self {
74 InvalidUrl {
75 kind,
76 position: Some(position),
77 }
78 }
79
80 pub(crate) fn offset(mut self, offset: usize) -> Self {
82 if let Some(pos) = self.position.as_mut() {
83 *pos += offset;
84 }
85 self
86 }
87
88 pub fn kind(&self) -> ErrorKind {
90 self.kind
91 }
92
93 pub fn position(&self) -> Option<usize> {
95 self.position
96 }
97}
98
99impl fmt::Display for InvalidUrl {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 if let Some(pos) = self.position {
102 write!(f, "{} at position {pos}", self.kind)
103 } else {
104 fmt::Display::fmt(&self.kind, f)
105 }
106 }
107}
108
109impl Error for InvalidUrl {}
110
111#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
113pub struct InvalidUrlParts(InvalidUrl);
114
115impl InvalidUrlParts {
116 pub fn kind(&self) -> ErrorKind {
118 self.0.kind
119 }
120
121 pub fn into_inner(self) -> InvalidUrl {
123 self.0
124 }
125}
126
127impl From<InvalidUrl> for InvalidUrlParts {
128 fn from(err: InvalidUrl) -> Self {
129 InvalidUrlParts(err)
130 }
131}
132
133impl fmt::Display for InvalidUrlParts {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(f, "invalid url parts: {}", self.0)
136 }
137}
138
139impl Error for InvalidUrlParts {
140 fn source(&self) -> Option<&(dyn Error + 'static)> {
141 Some(&self.0)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn display() {
151 let cases = [
152 (ErrorKind::Empty, "empty string"),
153 (ErrorKind::TooLong, "url is too long"),
154 (ErrorKind::InvalidScheme, "invalid scheme"),
155 (ErrorKind::SchemeMissing, "scheme missing"),
156 (ErrorKind::InvalidChar('x'), "invalid character 'x'"),
157 (
158 ErrorKind::InvalidPercentEncoding,
159 "invalid percent-encoding",
160 ),
161 (ErrorKind::InvalidAuthority, "invalid authority"),
162 (ErrorKind::AuthorityMissing, "authority missing"),
163 (ErrorKind::InvalidHost, "invalid host"),
164 (ErrorKind::InvalidIpv4, "invalid IPv4 address"),
165 (ErrorKind::InvalidIpv6, "invalid IPv6 address"),
166 (ErrorKind::InvalidPort, "invalid port"),
167 (ErrorKind::PortOutOfRange, "port out of range"),
168 (ErrorKind::InvalidPath, "invalid path"),
169 ];
170 for (kind, msg) in cases {
171 assert_eq!(kind.to_string(), msg);
172 assert_eq!(InvalidUrl::new(kind).to_string(), msg);
173 }
174 let err = InvalidUrl::at(ErrorKind::InvalidPort, 3).offset(2);
175 assert_eq!(err.to_string(), "invalid port at position 5");
176 assert_eq!(InvalidUrl::new(ErrorKind::Empty).offset(2).position(), None);
177 let parts = InvalidUrlParts::from(err);
178 assert_eq!(parts.kind(), ErrorKind::InvalidPort);
179 assert_eq!(parts.into_inner(), err);
180 }
181}