eoka_protocol/
turnstile.rs1use serde::{Deserialize, Serialize};
2
3pub const TURNSTILE_DEFAULT_TIMEOUT_MS: u64 = 30_000;
4pub const TURNSTILE_MAX_TIMEOUT_MS: u64 = 120_000;
5
6#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
7#[serde(default, deny_unknown_fields)]
8pub struct CaptchaTurnstileArgs {
9 #[serde(deserialize_with = "deserialize_timeout")]
10 #[schemars(range(min = 1, max = TURNSTILE_MAX_TIMEOUT_MS))]
11 pub timeout_ms: u64,
12}
13
14impl Default for CaptchaTurnstileArgs {
15 fn default() -> Self {
16 Self {
17 timeout_ms: TURNSTILE_DEFAULT_TIMEOUT_MS,
18 }
19 }
20}
21
22fn deserialize_timeout<'de, D: serde::Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
23 let value = u64::deserialize(d)?;
24 if !(1..=TURNSTILE_MAX_TIMEOUT_MS).contains(&value) {
25 return Err(serde::de::Error::custom(
26 "timeout_ms must be between 1 and 120000",
27 ));
28 }
29 Ok(value)
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
33pub struct CaptchaTurnstileResult {
34 pub tab_id: String,
35 pub clicks: u32,
36 pub elapsed_ms: u64,
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 #[test]
44 fn timeout_defaults_and_is_bounded() {
45 let args: CaptchaTurnstileArgs = serde_json::from_value(serde_json::json!({})).unwrap();
46 assert_eq!(args.timeout_ms, TURNSTILE_DEFAULT_TIMEOUT_MS);
47 for timeout in [0, TURNSTILE_MAX_TIMEOUT_MS + 1] {
48 assert!(serde_json::from_value::<CaptchaTurnstileArgs>(
49 serde_json::json!({ "timeout_ms": timeout })
50 )
51 .is_err());
52 }
53 }
54}