use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use rand::RngExt;
use sha2::{Digest, Sha256};
#[must_use]
pub fn generate_code_verifier() -> String {
let random_bytes: [u8; 48] = rand::rng().random();
URL_SAFE_NO_PAD.encode(random_bytes)
}
#[must_use]
pub fn generate_code_challenge(verifier: &str) -> String {
let hash = Sha256::digest(verifier.as_bytes());
URL_SAFE_NO_PAD.encode(hash)
}
#[must_use]
pub fn generate_state() -> String {
let random_bytes: [u8; 16] = rand::rng().random();
URL_SAFE_NO_PAD.encode(random_bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_code_verifier_length() {
let verifier = generate_code_verifier();
assert_eq!(verifier.len(), 64);
}
#[test]
fn test_code_verifier_url_safe() {
let verifier = generate_code_verifier();
assert!(
verifier
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
"verifier should be URL-safe: {}",
verifier
);
}
#[test]
fn test_code_verifier_uniqueness() {
let v1 = generate_code_verifier();
let v2 = generate_code_verifier();
assert_ne!(v1, v2, "verifiers should be unique");
}
#[test]
fn test_code_challenge_deterministic() {
let verifier = "test_verifier_string";
let c1 = generate_code_challenge(verifier);
let c2 = generate_code_challenge(verifier);
assert_eq!(c1, c2, "challenge should be deterministic");
}
#[test]
fn test_code_challenge_different_for_different_verifiers() {
let c1 = generate_code_challenge("verifier_1");
let c2 = generate_code_challenge("verifier_2");
assert_ne!(c1, c2);
}
#[test]
fn test_state_length() {
let state = generate_state();
assert_eq!(state.len(), 22);
}
#[test]
fn test_state_uniqueness() {
let s1 = generate_state();
let s2 = generate_state();
assert_ne!(s1, s2, "states should be unique");
}
}