captcha-sdk 0.0.1

Unified server-side CAPTCHA verification for Rust
Documentation
use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};
use serde_json::Value;
use time::OffsetDateTime;

use crate::ProviderId;

/// A normalized reason explaining why a CAPTCHA was not accepted.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum FailureReason {
    /// The token was missing or empty.
    MissingToken,
    /// The token was malformed or invalid.
    InvalidToken,
    /// The token was already consumed.
    AlreadyUsed,
    /// The token expired before verification.
    Expired,
    /// The returned hostname did not match application policy.
    HostnameMismatch,
    /// The returned action did not match the requested action.
    ActionMismatch,
    /// The score was below application policy.
    ScoreTooLow,
    /// The provider returned a reason that does not have a normalized variant.
    Other(String),
}

/// Provider-independent outcome of a CAPTCHA verification attempt.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VerificationResult {
    /// Whether the provider accepted the token.
    pub valid: bool,
    /// Provider that evaluated the token.
    pub provider: ProviderId,
    /// Risk score, commonly from `0.0` to `1.0`, when provided.
    pub score: Option<f64>,
    /// Action returned by the provider, when provided.
    pub action: Option<String>,
    /// Hostname returned by the provider, when provided.
    pub hostname: Option<String>,
    /// Time at which the CAPTCHA challenge was issued, when provided.
    pub challenge_time: Option<OffsetDateTime>,
    /// Normalized reasons explaining an invalid result.
    pub reasons: Vec<FailureReason>,
    /// Provider-specific fields not represented by the stable schema.
    pub metadata: BTreeMap<String, Value>,
}

impl VerificationResult {
    /// Creates a valid result with optional fields left empty.
    #[must_use]
    pub fn valid(provider: ProviderId) -> Self {
        Self::new(true, provider)
    }

    /// Creates an invalid result with the supplied normalized reasons.
    #[must_use]
    pub fn invalid(provider: ProviderId, reasons: Vec<FailureReason>) -> Self {
        Self {
            reasons,
            ..Self::new(false, provider)
        }
    }

    fn new(valid: bool, provider: ProviderId) -> Self {
        Self {
            valid,
            provider,
            score: None,
            action: None,
            hostname: None,
            challenge_time: None,
            reasons: Vec::new(),
            metadata: BTreeMap::new(),
        }
    }
}