captcha-sdk 0.0.1

Unified server-side CAPTCHA verification for Rust
Documentation
use std::{
    fmt::Display,
    net::{IpAddr, Ipv4Addr},
};

use captcha_sdk::{CaptchaToken, CaptchaTokenError, MAX_CAPTCHA_TOKEN_LEN, VerificationRequest};
use serde::{Serialize, de::DeserializeOwned};
use static_assertions::assert_not_impl_any;

assert_not_impl_any!(CaptchaToken: Clone, Display, Serialize, DeserializeOwned);
assert_not_impl_any!(VerificationRequest: Clone, Display, Serialize, DeserializeOwned);

#[test]
fn captcha_tokens_validate_non_empty_bounded_input() {
    assert!(matches!(
        CaptchaToken::new(""),
        Err(CaptchaTokenError::Empty)
    ));
    assert!(CaptchaToken::new("a".repeat(MAX_CAPTCHA_TOKEN_LEN)).is_ok());
    assert!(matches!(
        CaptchaToken::new("a".repeat(MAX_CAPTCHA_TOKEN_LEN + 1)),
        Err(CaptchaTokenError::TooLong)
    ));
}

#[test]
fn request_construction_exposes_opaque_token_and_optional_remote_ip() {
    let ip = IpAddr::V4(Ipv4Addr::LOCALHOST);
    let token = CaptchaToken::new("request-test-token").expect("token should be valid");
    let request = VerificationRequest::new(token).with_remote_ip(ip);

    assert_eq!(request.remote_ip(), Some(ip));
    let _: &CaptchaToken = request.token();
}

#[test]
fn tokens_and_requests_redact_debug_output() {
    let raw_token = "debug-must-not-contain-this-token";
    let token = CaptchaToken::new(raw_token).expect("token should be valid");
    let token_debug = format!("{token:?}");
    assert!(token_debug.contains("[REDACTED]"));
    assert!(!token_debug.contains(raw_token));

    let request = VerificationRequest::new(token);
    let request_debug = format!("{request:?}");
    assert!(request_debug.contains("[REDACTED]"));
    assert!(!request_debug.contains(raw_token));
}