hcaptcha_no_wasm/
error.rs

1//! Error types for hcaptcha
2
3use serde::{Deserialize, Deserializer, Serialize};
4use std::collections::HashSet;
5use std::fmt;
6use std::io;
7use thiserror::Error;
8
9/// The error type for hcaptcha.
10/// Provides error types to capture error codes from the Hcaptcha API
11/// and errors output from crates used by the library.
12#[non_exhaustive]
13#[derive(Error, Debug)]
14pub enum Error {
15    /// Error(s) returned from the hcaptcha API and mapped to the [Code] enum.
16    #[error("{0:?}")]
17    Codes(HashSet<Code>),
18    /// Error returned by reqwest
19    #[error("{0}")]
20    Reqwest(#[from] reqwest::Error),
21    /// Error returned by io
22    #[error("{0}")]
23    Io(#[from] io::Error),
24    /// Error returned by serde_json
25    #[error("{0}")]
26    Json(#[from] serde_json::Error),
27    /// Error returned by serde_urlencoded
28    #[error("{0}")]
29    UrlEncoded(#[from] serde_urlencoded::ser::Error),
30    /// Error returned by uuid
31    #[error("{0}")]
32    Uuid(#[from] uuid::Error),
33    /// Error returned by url parser
34    #[error("{0}")]
35    Url(#[from] url::ParseError),
36}
37
38/// Error code mapping for the error responses from the hcaptcha API.
39/// Returned in the [enum@Error] type.
40#[non_exhaustive]
41#[derive(PartialEq, Eq, Hash, Clone, Debug)]
42pub enum Code {
43    /// Secret key is missing.
44    MissingSecret,
45    /// Secret key is invalid or malformed.
46    InvalidSecret,
47    /// User IP string is missing.
48    MissingUserIp,
49    /// User IP is invalid or malformed.
50    InvalidUserIp,
51    /// Site Key string is missing.
52    MissingSiteKey,
53    /// Site Key is invalid or malformed.
54    InvalidSiteKey,
55    /// The response parameter (verification token) is missing.
56    MissingResponse,
57    /// The response parameter (verification token) is invalid or malformed.
58    InvalidResponse,
59    /// The request is invalid or malformed.
60    BadRequest,
61    /// The response parameter has already been checked, or has another issue.
62    InvalidAlreadySeen,
63    /// The sitekey is not registered with the provided secret.
64    SiteSecretMismatch,
65    /// Extended secret check reports that the secret string is the wrong length.
66    InvalidSecretExtWrongLen,
67    /// Extended secret check reports that the secret string is not a hex string.
68    InvalidSecretExtNotHex,
69    /// Extended secret check identifies the version of secret by the first two characters.
70    /// If the secret is valid there may be a new version that is not yet known.
71    /// A false report can be worked around by dropping the `ext` feature.
72    ///
73    /// ```toml
74    /// hcaptcha-no-wasm = {version = "2.3.0", default-features = false, features = [rustls-backend]}
75    /// ```
76    SecretVersionUnknown,
77    /// Collect any new error codes issued by the API.
78    Unknown(String),
79}
80
81impl<'de> Deserialize<'de> for Code {
82    /// Custom deserialize to map the hcaptcha API error codes for reporting as
83    /// a [Code] in [enum@Error].
84    fn deserialize<D>(de: D) -> Result<Self, D::Error>
85    where
86        D: Deserializer<'de>,
87    {
88        let code = String::deserialize(de)?;
89        Ok(match &*code {
90            "missing-input-secret" => Code::MissingSecret,
91            "invalid-input-secret" => Code::InvalidSecret,
92            "missing-input-response" => Code::MissingResponse,
93            "invalid-input-response" => Code::InvalidResponse,
94            "bad-request" => Code::BadRequest,
95            "invalid-or-already-seen-response" => Code::InvalidAlreadySeen,
96            "sitekey-secret-mismatch" => Code::SiteSecretMismatch,
97            _ => Code::Unknown(code),
98        })
99    }
100}
101
102impl Serialize for Code {
103    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
104    where
105        S: serde::Serializer,
106    {
107        serializer.serialize_str(match self {
108            Code::MissingSecret => "missing-input-secret",
109            Code::InvalidSecret => "invalid-input-secret",
110            Code::MissingUserIp => "missing-input-user-ip",
111            Code::InvalidUserIp => "invalid-input-user-ip",
112            Code::MissingSiteKey => "missing-input-sitekey",
113            Code::InvalidSiteKey => "invalid-input-sitekey",
114            Code::MissingResponse => "missing-input-response",
115            Code::InvalidResponse => "invalid-input-response",
116            Code::BadRequest => "bad-request",
117            Code::InvalidAlreadySeen => "invalid-or-already-seen-response",
118            Code::SiteSecretMismatch => "sitekey-secret-mismatch",
119            Code::SecretVersionUnknown => "secret-version-unknown",
120            Code::InvalidSecretExtNotHex => "invalid-secret-ext-not-hex",
121            Code::InvalidSecretExtWrongLen => "invalid-secret-ext-wrong-len",
122            Code::Unknown(s) => s.as_str(),
123        })
124    }
125}
126
127impl fmt::Display for Code {
128    #[allow(unused_variables)]
129    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130        match self {
131            Code::MissingSecret => write!(f, "Secret key is missing."),
132            Code::InvalidSecret => write!(f, "Secret key is invalid or malformed."),
133            Code::MissingUserIp => write!(f, "User IP string is missing."),
134            Code::InvalidUserIp => write!(f, "User IP string is invalid."),
135            Code::MissingSiteKey => write!(f, "Site Key string is missing."),
136            Code::InvalidSiteKey => write!(f, "Site Key string is invalid."),
137            Code::InvalidSecretExtWrongLen => {
138                write!(f, "Secret key is not the correct length.")
139            }
140            Code::InvalidSecretExtNotHex => write!(f, "Secret key is not a hex string."),
141            Code::MissingResponse => {
142                write!(f, "The response parameter (verification token) is missing.")
143            }
144            Code::InvalidResponse => write!(
145                f,
146                "The response parameter (verification token) is invalid or malformed."
147            ),
148            Code::BadRequest => write!(f, "The request is invalid or malformed."),
149            Code::InvalidAlreadySeen => write!(
150                f,
151                "The response parameter has already been checked, or has another issue."
152            ),
153            Code::SiteSecretMismatch => {
154                write!(f, "The sitekey is not registered with the provided secret.")
155            }
156            Code::SecretVersionUnknown => {
157                write!(f, "The version of the site secret is not recognise.")
158            }
159            Code::Unknown(e) => write!(f, "Unkown error: {e}"),
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use serde_test::{assert_ser_tokens, Token};
168
169    #[test]
170    fn test_serialize_missing_secret() {
171        let code = Code::MissingSecret;
172        assert_ser_tokens(&code, &[Token::Str("missing-input-secret")]);
173    }
174
175    #[test]
176    fn test_serialize_invalid_secret() {
177        let code = Code::InvalidSecret;
178        assert_ser_tokens(&code, &[Token::Str("invalid-input-secret")]);
179    }
180
181    #[test]
182    fn test_serialize_missing_user_ip() {
183        let code = Code::MissingUserIp;
184        assert_ser_tokens(&code, &[Token::Str("missing-input-user-ip")]);
185    }
186
187    #[test]
188    fn test_serialize_invalid_user_ip() {
189        let code = Code::InvalidUserIp;
190        assert_ser_tokens(&code, &[Token::Str("invalid-input-user-ip")]);
191    }
192
193    #[test]
194    fn test_serialize_missing_site_key() {
195        let code = Code::MissingSiteKey;
196        assert_ser_tokens(&code, &[Token::Str("missing-input-sitekey")]);
197    }
198
199    #[test]
200    fn test_serialize_invalid_site_key() {
201        let code = Code::InvalidSiteKey;
202        assert_ser_tokens(&code, &[Token::Str("invalid-input-sitekey")]);
203    }
204
205    #[test]
206    fn test_serialize_missing_response() {
207        let code = Code::MissingResponse;
208        assert_ser_tokens(&code, &[Token::Str("missing-input-response")]);
209    }
210
211    #[test]
212    fn test_serialize_invalid_response() {
213        let code = Code::InvalidResponse;
214        assert_ser_tokens(&code, &[Token::Str("invalid-input-response")]);
215    }
216
217    #[test]
218    fn test_serialize_bad_request() {
219        let code = Code::BadRequest;
220        assert_ser_tokens(&code, &[Token::Str("bad-request")]);
221    }
222
223    #[test]
224    fn test_serialize_invalid_already_seen() {
225        let code = Code::InvalidAlreadySeen;
226        assert_ser_tokens(&code, &[Token::Str("invalid-or-already-seen-response")]);
227    }
228
229    #[test]
230    fn test_serialize_site_secret_mismatch() {
231        let code = Code::SiteSecretMismatch;
232        assert_ser_tokens(&code, &[Token::Str("sitekey-secret-mismatch")]);
233    }
234
235    #[test]
236    fn test_serialize_secret_version_unknown() {
237        let code = Code::SecretVersionUnknown;
238        assert_ser_tokens(&code, &[Token::Str("secret-version-unknown")]);
239    }
240
241    #[test]
242    fn test_serialize_secret_ext_not_hex() {
243        let code = Code::InvalidSecretExtNotHex;
244        assert_ser_tokens(&code, &[Token::Str("invalid-secret-ext-not-hex")]);
245    }
246
247    #[test]
248    fn test_serialize_unknown_variant() {
249        let code = Code::Unknown("unexpected-error".to_string());
250        assert_ser_tokens(&code, &[Token::Str("unexpected-error")]);
251    }
252
253    #[test]
254    fn test_serialize_invalid_secret_ext_wrong_len() {
255        let code = Code::InvalidSecretExtWrongLen;
256        assert_ser_tokens(&code, &[Token::Str("invalid-secret-ext-wrong-len")]);
257    }
258
259    #[test]
260    fn test_fmt_missing_secret() {
261        let code = Code::MissingSecret;
262        let formatted = format!("{}", code);
263        assert_eq!(formatted, "Secret key is missing.");
264    }
265
266    #[test]
267    fn test_fmt_invalid_secret() {
268        let code = Code::InvalidSecret;
269        let formatted = format!("{}", code);
270        assert_eq!(formatted, "Secret key is invalid or malformed.");
271    }
272
273    #[test]
274    fn test_fmt_missing_user_ip() {
275        let code = Code::MissingUserIp;
276        let formatted = format!("{}", code);
277        assert_eq!(formatted, "User IP string is missing.");
278    }
279
280    #[test]
281    fn test_fmt_invalid_user_ip() {
282        let code = Code::InvalidUserIp;
283        let formatted = format!("{}", code);
284        assert_eq!(formatted, "User IP string is invalid.");
285    }
286
287    #[test]
288    fn test_fmt_missing_site_key() {
289        let code = Code::MissingSiteKey;
290        let formatted = format!("{}", code);
291        assert_eq!(formatted, "Site Key string is missing.");
292    }
293
294    #[test]
295    fn test_fmt_invalid_site_key() {
296        let code = Code::InvalidSiteKey;
297        let formatted = format!("{}", code);
298        assert_eq!(formatted, "Site Key string is invalid.");
299    }
300
301    #[test]
302    fn test_fmt_invlid_secret_ext_wrong_len() {
303        let code = Code::InvalidSecretExtWrongLen;
304        let formatted = format!("{}", code);
305        assert_eq!(formatted, "Secret key is not the correct length.");
306    }
307
308    #[test]
309    fn test_fmt_invalid_secret_ext_no_hex() {
310        let code = Code::InvalidSecretExtNotHex;
311        let formatted = format!("{}", code);
312        assert_eq!(formatted, "Secret key is not a hex string.");
313    }
314
315    #[test]
316    fn test_fmt_missing_response() {
317        let code = Code::MissingResponse;
318        let formatted = format!("{}", code);
319        assert_eq!(
320            formatted,
321            "The response parameter (verification token) is missing."
322        );
323    }
324
325    #[test]
326    fn test_fmt_invalid_response() {
327        let code = Code::InvalidResponse;
328        let formatted = format!("{}", code);
329        assert_eq!(
330            formatted,
331            "The response parameter (verification token) is invalid or malformed."
332        );
333    }
334
335    #[test]
336    fn test_fmt_bad_request() {
337        let code = Code::BadRequest;
338        let formatted = format!("{}", code);
339        assert_eq!(formatted, "The request is invalid or malformed.");
340    }
341
342    #[test]
343    fn test_fmt_invalid_already_seen() {
344        let code = Code::InvalidAlreadySeen;
345        let formatted = format!("{}", code);
346        assert_eq!(
347            formatted,
348            "The response parameter has already been checked, or has another issue."
349        );
350    }
351
352    #[test]
353    fn test_fmt_site_secret_mismatch() {
354        let code = Code::SiteSecretMismatch;
355        let formatted = format!("{}", code);
356        assert_eq!(
357            formatted,
358            "The sitekey is not registered with the provided secret."
359        );
360    }
361
362    #[test]
363    fn test_fmt_secret_version_unknown() {
364        let code = Code::SecretVersionUnknown;
365        let formatted = format!("{}", code);
366        assert_eq!(
367            formatted,
368            "The version of the site secret is not recognise."
369        );
370    }
371
372    #[test]
373    fn test_fmt_invalid_secret_ext_not_hex() {
374        let code = Code::InvalidSecretExtNotHex;
375        let formatted = format!("{}", code);
376        assert_eq!(formatted, "Secret key is not a hex string.");
377    }
378
379    #[test]
380    fn test_fmt_unknown_error() {
381        let error_message = "Some unknown error occurred.";
382        let code = Code::Unknown(error_message.to_string());
383        let formatted = format!("{}", code);
384        assert_eq!(formatted, format!("Unkown error: {}", error_message));
385    }
386}