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