captcha_sdk/response.rs
1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use time::OffsetDateTime;
6
7use crate::ProviderId;
8
9/// A normalized reason explaining why a CAPTCHA was not accepted.
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12#[non_exhaustive]
13pub enum FailureReason {
14 /// The token was missing or empty.
15 MissingToken,
16 /// The token was malformed or invalid.
17 InvalidToken,
18 /// The token was already consumed.
19 AlreadyUsed,
20 /// The token expired before verification.
21 Expired,
22 /// The returned hostname did not match application policy.
23 HostnameMismatch,
24 /// The returned action did not match the requested action.
25 ActionMismatch,
26 /// The score was below application policy.
27 ScoreTooLow,
28 /// The provider returned a reason that does not have a normalized variant.
29 Other(String),
30}
31
32/// Provider-independent outcome of a CAPTCHA verification attempt.
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct VerificationResult {
35 /// Whether the provider accepted the token.
36 pub valid: bool,
37 /// Provider that evaluated the token.
38 pub provider: ProviderId,
39 /// Risk score, commonly from `0.0` to `1.0`, when provided.
40 pub score: Option<f64>,
41 /// Action returned by the provider, when provided.
42 pub action: Option<String>,
43 /// Hostname returned by the provider, when provided.
44 pub hostname: Option<String>,
45 /// Time at which the CAPTCHA challenge was issued, when provided.
46 pub challenge_time: Option<OffsetDateTime>,
47 /// Normalized reasons explaining an invalid result.
48 pub reasons: Vec<FailureReason>,
49 /// Provider-specific fields not represented by the stable schema.
50 pub metadata: BTreeMap<String, Value>,
51}
52
53impl VerificationResult {
54 /// Creates a valid result with optional fields left empty.
55 #[must_use]
56 pub fn valid(provider: ProviderId) -> Self {
57 Self::new(true, provider)
58 }
59
60 /// Creates an invalid result with the supplied normalized reasons.
61 #[must_use]
62 pub fn invalid(provider: ProviderId, reasons: Vec<FailureReason>) -> Self {
63 Self {
64 reasons,
65 ..Self::new(false, provider)
66 }
67 }
68
69 fn new(valid: bool, provider: ProviderId) -> Self {
70 Self {
71 valid,
72 provider,
73 score: None,
74 action: None,
75 hostname: None,
76 challenge_time: None,
77 reasons: Vec::new(),
78 metadata: BTreeMap::new(),
79 }
80 }
81}