1use std::{error::Error, fmt, net::AddrParseError};
2
3#[derive(Debug)]
4pub struct IriParseError {
5 pub(crate) kind: IriParseErrorKind,
6}
7
8impl IriParseError {
9 #[must_use]
10 pub fn kind(&self) -> &IriParseErrorKind {
11 &self.kind
12 }
13}
14
15impl fmt::Display for IriParseError {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 match &self.kind {
18 IriParseErrorKind::NoScheme => write!(f, "No scheme found in an absolute IRI"),
19 IriParseErrorKind::EmptyScheme => write!(f, "Empty schemes are not allowed"),
20 IriParseErrorKind::InvalidSchemeCharacter(c) => {
21 write!(f, "Invalid character '{c}' in scheme")
22 }
23 IriParseErrorKind::InvalidHostCharacter(c) => {
24 write!(f, "Invalid character '{c}' in host")
25 }
26 IriParseErrorKind::UnmatchedHostBracket => {
27 write!(f, "'[' bracket must be paired with a closing one in host")
28 }
29 IriParseErrorKind::InvalidHostIp(e) => write!(f, "Invalid host IP ({e})"),
30 IriParseErrorKind::InvalidPortCharacter(c) => write!(f, "Invalid character '{c}'"),
31 IriParseErrorKind::InvalidIriCodePoint(c) => {
32 write!(f, "Invalid IRI code point '{c}'")
33 }
34 IriParseErrorKind::InvalidPercentEncoding(cs) => {
35 write!(f, "Invalid IRI percent encoding '{}'", cs.iter().flatten().collect::<String>())
36 }
37 IriParseErrorKind::PathStartingWithTwoSlashes => {
38 write!(f, "An IRI path is not allowed to start with //")
39 }
40 IriParseErrorKind::NonAsciiInUri => write!(f, "Non-ASCII character in URI"),
41 }
42 }
43}
44
45impl Error for IriParseError {
46 fn source(&self) -> Option<&(dyn Error + 'static)> {
47 if let IriParseErrorKind::InvalidHostIp(e) = &self.kind {
48 Some(e)
49 } else {
50 None
51 }
52 }
53}
54
55impl From<IriParseErrorKind> for IriParseError {
56 fn from(kind: IriParseErrorKind) -> Self {
57 Self { kind }
58 }
59}
60
61#[derive(Debug)]
62pub enum IriParseErrorKind {
63 NoScheme,
64 EmptyScheme,
65 InvalidSchemeCharacter(char),
66 UnmatchedHostBracket,
67 InvalidHostCharacter(char),
68 InvalidHostIp(AddrParseError),
69 InvalidPortCharacter(char),
70 InvalidIriCodePoint(char),
71 InvalidPercentEncoding([Option<char>; 3]),
72 PathStartingWithTwoSlashes,
73 NonAsciiInUri,
74}
75
76#[derive(Debug, thiserror::Error)]
77#[error("invalid IRI")]
78pub struct InvalidIri<T>(pub T);
79
80#[derive(Debug, thiserror::Error)]
81#[error("invalid IRI reference")]
82pub struct InvalidIriRef<T>(pub T);
83
84#[derive(Debug, thiserror::Error)]
85#[error("invalid URI")]
86pub struct InvalidUri<T>(pub T);
87
88#[derive(Debug, thiserror::Error)]
89#[error("invalid URI reference")]
90pub struct InvalidUriRef<T>(pub T);
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn variants_construct() {
98 let _ = IriParseError::from(IriParseErrorKind::NoScheme);
99 let _ = IriParseError::from(IriParseErrorKind::EmptyScheme);
100 let _ = IriParseError::from(IriParseErrorKind::InvalidSchemeCharacter('!'));
101 let _ = IriParseError::from(IriParseErrorKind::UnmatchedHostBracket);
102 let _ = IriParseError::from(IriParseErrorKind::InvalidHostCharacter(' '));
103 let _ = IriParseError::from(IriParseErrorKind::InvalidPortCharacter('a'));
104 let _ = IriParseError::from(IriParseErrorKind::InvalidIriCodePoint('\0'));
105 let _ = IriParseError::from(IriParseErrorKind::InvalidPercentEncoding([Some('%'), None, None]));
106 let _ = IriParseError::from(IriParseErrorKind::PathStartingWithTwoSlashes);
107 let _ = IriParseError::from(IriParseErrorKind::NonAsciiInUri);
108 let _: InvalidIri<&str> = InvalidIri("x");
109 let _: InvalidIriRef<&str> = InvalidIriRef("x");
110 let _: InvalidUri<&str> = InvalidUri("x");
111 let _: InvalidUriRef<&str> = InvalidUriRef("x");
112 }
113}