Skip to main content

urly/
error.rs

1use std::{error::Error, fmt};
2
3/// The reason a URL or URL component is invalid.
4#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
5#[non_exhaustive]
6pub enum ErrorKind {
7    /// The input is empty.
8    Empty,
9    /// The URL is longer than 65535 bytes.
10    TooLong,
11    /// The scheme contains invalid characters.
12    InvalidScheme,
13    /// The URL has no scheme, but the operation requires one.
14    SchemeMissing,
15    /// The character is not allowed at this position.
16    InvalidChar(char),
17    /// A `%` is not followed by two hex digits.
18    InvalidPercentEncoding,
19    /// The authority is malformed.
20    InvalidAuthority,
21    /// The URL has no authority, but the operation requires one.
22    AuthorityMissing,
23    /// The host is malformed.
24    InvalidHost,
25    /// The host looks like an IPv4 address but isn't a valid one.
26    InvalidIpv4,
27    /// The bracketed host isn't a valid IPv6 address.
28    InvalidIpv6,
29    /// The port contains non-digit characters.
30    InvalidPort,
31    /// The port is greater than 65535.
32    PortOutOfRange,
33    /// The path, file name or extension is malformed.
34    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/// An error returned when a URL or URL component is invalid.
59#[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    /// Shifts the error position by `offset` bytes.
81    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    /// Returns the reason the input is invalid.
89    pub fn kind(&self) -> ErrorKind {
90        self.kind
91    }
92
93    /// Returns the byte offset of the error in the input, if known.
94    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/// An error returned by [`Url::from_parts`](crate::Url::from_parts).
112#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
113pub struct InvalidUrlParts(InvalidUrl);
114
115impl InvalidUrlParts {
116    /// Returns the reason the parts are invalid.
117    pub fn kind(&self) -> ErrorKind {
118        self.0.kind
119    }
120
121    /// Returns the underlying error.
122    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}