captcha-sdk 0.0.1

Unified server-side CAPTCHA verification for Rust
Documentation
use std::{error::Error, fmt};

use zeroize::Zeroizing;

/// Maximum accepted CAPTCHA token length, in bytes.
pub const MAX_CAPTCHA_TOKEN_LEN: usize = 16 * 1024;

/// An opaque token produced by a CAPTCHA provider's client-side widget.
///
/// The token is intentionally not serializable and its [`Debug`](fmt::Debug)
/// output is redacted.
pub struct CaptchaToken(Zeroizing<String>);

impl CaptchaToken {
    /// Validates and stores a non-empty CAPTCHA token.
    ///
    /// # Errors
    ///
    /// Returns [`CaptchaTokenError`] when the token is empty or exceeds
    /// [`MAX_CAPTCHA_TOKEN_LEN`].
    pub fn new(value: impl Into<String>) -> Result<Self, CaptchaTokenError> {
        let value = value.into();
        if value.is_empty() {
            return Err(CaptchaTokenError::Empty);
        }
        if value.len() > MAX_CAPTCHA_TOKEN_LEN {
            return Err(CaptchaTokenError::TooLong);
        }
        Ok(Self(Zeroizing::new(value)))
    }

    /// Exposes the token only to provider adapters while constructing a request.
    #[allow(dead_code)]
    pub(crate) fn expose(&self) -> &str {
        self.0.as_str()
    }
}

impl fmt::Debug for CaptchaToken {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("CaptchaToken([REDACTED])")
    }
}

/// Reason a CAPTCHA token could not be accepted.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CaptchaTokenError {
    /// The token was empty.
    Empty,
    /// The token exceeded [`MAX_CAPTCHA_TOKEN_LEN`].
    TooLong,
}

impl fmt::Display for CaptchaTokenError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let message = match self {
            Self::Empty => "CAPTCHA token must not be empty",
            Self::TooLong => "CAPTCHA token is too long",
        };
        formatter.write_str(message)
    }
}

impl Error for CaptchaTokenError {}