Skip to main content

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}