1#[cfg(feature = "otpauth")]
2use url::ParseError;
3
4use crate::Rfc6238Error;
5
6#[derive(Debug, Eq, PartialEq)]
8pub enum TotpUrlError {
9 #[cfg(feature = "otpauth")]
11 #[cfg_attr(docsrs, doc(cfg(feature = "otpauth")))]
12 Url(ParseError),
13 Scheme(String),
15 Host(String),
17 Secret(String),
19 SecretSize(usize),
21 Algorithm(String),
23 Digits(String),
25 DigitsNumber(usize),
27 Step(String),
29 Issuer(String),
31 IssuerDecoding(String),
33 IssuerMistmatch(String, String),
35 AccountName(String),
37 AccountNameDecoding(String),
39}
40
41impl std::error::Error for TotpUrlError {}
42
43impl std::fmt::Display for TotpUrlError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 TotpUrlError::AccountName(name) => write!(
47 f,
48 "Account Name can't contain a colon. \"{}\" contains a colon",
49 name
50 ),
51 TotpUrlError::AccountNameDecoding(name) => write!(
52 f,
53 "Couldn't URL decode \"{}\"",
54 name
55 ),
56 TotpUrlError::Algorithm(algo) => write!(
57 f,
58 "Algorithm can only be SHA1, SHA256 or SHA512, not \"{}\"",
59 algo
60 ),
61 TotpUrlError::Digits(digits) => write!(
62 f,
63 "Could not parse \"{}\" as a number.",
64 digits,
65 ),
66 TotpUrlError::DigitsNumber(digits) => write!(
67 f,
68 "Implementations MUST extract a 6-digit code at a minimum and possibly 7 and 8-digit code. {} digits is not allowed",
69 digits,
70 ),
71 TotpUrlError::Host(host) => write!(
72 f,
73 "Host should be totp, not \"{}\"",
74 host
75 ),
76 TotpUrlError::Issuer(issuer) => write!(
77 f,
78 "Issuer can't contain a colon. \"{}\" contains a colon",
79 issuer
80 ),
81 TotpUrlError::IssuerDecoding(issuer) => write!(
82 f,
83 "Couldn't URL decode \"{}\"",
84 issuer
85 ),
86 TotpUrlError::IssuerMistmatch(path_issuer, issuer) => write!(
87 f,
88 "An issuer \"{}\" could be retrieved from the path, but a different issuer \"{}\" was found in the issuer URL parameter",
89 path_issuer,
90 issuer,
91 ),
92 TotpUrlError::Scheme(scheme) => write!(
93 f,
94 "Scheme should be otpauth, not \"{}\"",
95 scheme
96 ),
97 TotpUrlError::Secret(secret) => write!(
98 f,
99 "Secret \"{}\" is not a valid non-padded base32 string",
100 secret,
101 ),
102 TotpUrlError::SecretSize(bits) => write!(
103 f,
104 "The length of the shared secret MUST be at least 128 bits. {} bits is not enough",
105 bits,
106 ),
107 TotpUrlError::Step(step) => write!(
108 f,
109 "Could not parse \"{}\" as a number.",
110 step,
111 ),
112 #[cfg(feature = "otpauth")]
113 TotpUrlError::Url(e) => write!(
114 f,
115 "Error parsing URL: {}",
116 e
117 )
118 }
119 }
120}
121
122impl From<Rfc6238Error> for TotpUrlError {
123 fn from(e: Rfc6238Error) -> Self {
124 match e {
125 Rfc6238Error::InvalidDigits(digits) => TotpUrlError::DigitsNumber(digits),
126 Rfc6238Error::SecretTooSmall(bits) => TotpUrlError::SecretSize(bits),
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use crate::TotpUrlError;
134
135 #[test]
136 fn account_name() {
137 let error = TotpUrlError::AccountName("Laziz:".to_string());
138 assert_eq!(
139 error.to_string(),
140 "Account Name can't contain a colon. \"Laziz:\" contains a colon"
141 )
142 }
143
144 #[test]
145 fn account_name_decoding() {
146 let error = TotpUrlError::AccountNameDecoding("Laz&iz".to_string());
147 assert_eq!(
148 error.to_string(),
149 "Couldn't URL decode \"Laz&iz\"".to_string()
150 )
151 }
152
153 #[test]
154 fn algorithm() {
155 let error = TotpUrlError::Algorithm("SIKE".to_string());
156 assert_eq!(
157 error.to_string(),
158 "Algorithm can only be SHA1, SHA256 or SHA512, not \"SIKE\"".to_string()
159 )
160 }
161
162 #[test]
163 fn digits() {
164 let error = TotpUrlError::Digits("six".to_string());
165 assert_eq!(
166 error.to_string(),
167 "Could not parse \"six\" as a number.".to_string()
168 )
169 }
170
171 #[test]
172 fn digits_number() {
173 let error = TotpUrlError::DigitsNumber(5);
174 assert_eq!(error.to_string(), "Implementations MUST extract a 6-digit code at a minimum and possibly 7 and 8-digit code. 5 digits is not allowed".to_string())
175 }
176
177 #[test]
178 fn host() {
179 let error = TotpUrlError::Host("hotp".to_string());
180 assert_eq!(
181 error.to_string(),
182 "Host should be totp, not \"hotp\"".to_string()
183 )
184 }
185
186 #[test]
187 fn issuer() {
188 let error = TotpUrlError::Issuer("Iss:uer".to_string());
189 assert_eq!(
190 error.to_string(),
191 "Issuer can't contain a colon. \"Iss:uer\" contains a colon".to_string()
192 )
193 }
194
195 #[test]
196 fn issuer_decoding() {
197 let error = TotpUrlError::IssuerDecoding("iss&uer".to_string());
198 assert_eq!(
199 error.to_string(),
200 "Couldn't URL decode \"iss&uer\"".to_string()
201 )
202 }
203
204 #[test]
205 fn issuer_mismatch() {
206 let error = TotpUrlError::IssuerMistmatch("Google".to_string(), "Github".to_string());
207 assert_eq!(error.to_string(), "An issuer \"Google\" could be retrieved from the path, but a different issuer \"Github\" was found in the issuer URL parameter".to_string())
208 }
209
210 #[test]
211 fn scheme() {
212 let error = TotpUrlError::Scheme("https".to_string());
213 assert_eq!(
214 error.to_string(),
215 "Scheme should be otpauth, not \"https\"".to_string()
216 )
217 }
218
219 #[test]
220 fn secret() {
221 let error = TotpUrlError::Secret("YoLo".to_string());
222 assert_eq!(
223 error.to_string(),
224 "Secret \"YoLo\" is not a valid non-padded base32 string".to_string()
225 )
226 }
227
228 #[test]
229 fn secret_size() {
230 let error = TotpUrlError::SecretSize(112);
231 assert_eq!(
232 error.to_string(),
233 "The length of the shared secret MUST be at least 128 bits. 112 bits is not enough"
234 .to_string()
235 )
236 }
237
238 #[test]
239 #[cfg(feature = "otpauth")]
240 fn step() {
241 let error = TotpUrlError::Url(url::ParseError::EmptyHost);
242 assert_eq!(
243 error.to_string(),
244 "Error parsing URL: empty host".to_string()
245 )
246 }
247}