captcha-sdk 0.0.1

Unified server-side CAPTCHA verification for Rust
Documentation
use captcha_sdk::{
    InvalidProviderId, MAX_PROVIDER_ID_LEN, MAX_PROVIDER_ID_SEGMENT_LEN, ProviderId,
};

#[test]
fn provider_ids_reject_invalid_syntax_and_bounds() {
    let oversized_id = format!("com.{}", "a".repeat(MAX_PROVIDER_ID_LEN));
    let oversized_segment = format!(
        "com.{}.captcha",
        "a".repeat(MAX_PROVIDER_ID_SEGMENT_LEN + 1)
    );
    let cases = [
        ("", InvalidProviderId::Empty),
        ("example", InvalidProviderId::InvalidSyntax),
        (".example", InvalidProviderId::InvalidSyntax),
        ("com.example.", InvalidProviderId::InvalidSyntax),
        ("com..example", InvalidProviderId::InvalidSyntax),
        ("Com.example", InvalidProviderId::InvalidSyntax),
        ("com.exámple", InvalidProviderId::InvalidSyntax),
        ("com.-example", InvalidProviderId::InvalidSyntax),
        ("com.example-", InvalidProviderId::InvalidSyntax),
        ("com.example_name", InvalidProviderId::InvalidSyntax),
    ];

    for (value, expected) in cases {
        assert_eq!(ProviderId::new(value), Err(expected), "value: {value:?}");
    }
    assert_eq!(
        ProviderId::new(oversized_id),
        Err(InvalidProviderId::TooLong)
    );
    assert_eq!(
        ProviderId::new(oversized_segment),
        Err(InvalidProviderId::SegmentTooLong)
    );
}

#[test]
fn provider_ids_accept_boundaries_hyphens_and_external_namespaces() {
    let boundary_segment = "a".repeat(MAX_PROVIDER_ID_SEGMENT_LEN);
    let values = [
        "com.example.captcha.product".to_owned(),
        "io.example.my-captcha.v2".to_owned(),
        format!("io.{boundary_segment}"),
    ];

    for value in values {
        let id = ProviderId::try_from(value.as_str()).expect("identifier should be accepted");
        assert_eq!(id.as_str(), value);
        assert_eq!(id.to_string(), value);
    }
}

#[test]
fn provider_ids_publish_stable_builtins_and_reserve_their_authorities() {
    let builtins = [
        (ProviderId::ALTCHA_CUSTOM, "altcha.custom"),
        (ProviderId::ALTCHA_SENTINEL, "altcha.sentinel"),
        (ProviderId::CAPTCHAFOX_STANDARD, "captchafox.standard"),
        (ProviderId::CLOUDFLARE_TURNSTILE, "cloudflare.turnstile"),
        (ProviderId::FRIENDLYCAPTCHA_V2, "friendlycaptcha.v2"),
        (
            ProviderId::GOOGLE_RECAPTCHA_ENTERPRISE,
            "google.recaptcha.enterprise",
        ),
        (ProviderId::GOOGLE_RECAPTCHA_V2, "google.recaptcha.v2"),
        (ProviderId::GOOGLE_RECAPTCHA_V3, "google.recaptcha.v3"),
        (ProviderId::HCAPTCHA_ENTERPRISE, "hcaptcha.enterprise"),
        (ProviderId::HCAPTCHA_STANDARD, "hcaptcha.standard"),
        (ProviderId::MTCAPTCHA_STANDARD, "mtcaptcha.standard"),
        (ProviderId::PROSOPO_PROCAPTCHA, "prosopo.procaptcha"),
    ];

    for (provider, identifier) in builtins {
        assert_eq!(provider.as_str(), identifier);
        let json = serde_json::to_string(&provider).expect("provider should serialize");
        assert_eq!(json, format!("\"{identifier}\""));
        assert_eq!(
            serde_json::from_str::<ProviderId>(&json).expect("provider should deserialize"),
            provider
        );
    }

    assert_eq!(
        ProviderId::new("google.unrecognized-product"),
        Err(InvalidProviderId::ReservedAuthority)
    );
}