Skip to main content

polyester/
user_agent.rs

1//! Stable HTTP User-Agent for Polyester SDK clients.
2//!
3//! hyper omits User-Agent by default. That accidental omission currently
4//! dodges Cloudflare error 1010 ("browser signature banned"), but a WAF rule
5//! change that bans missing/unknown signatures would break every Rust client
6//! with an error that does not mention Cloudflare. Always send an explicit
7//! Polyester identity instead.
8
9use crate::VERSION;
10
11/// Explicit SDK identity for edge WAF allowlisting.
12pub fn user_agent() -> String {
13    format!("polyester-sdk-rust/{VERSION}")
14}
15
16/// True when a response body looks like Cloudflare error 1010.
17pub fn is_cloudflare_browser_ban(body: &str) -> bool {
18    let lowered = body.to_ascii_lowercase();
19    if lowered.contains("error code: 1010") || lowered.contains("error code 1010") {
20        return true;
21    }
22    lowered.contains("attention required") && lowered.contains("cloudflare")
23}
24
25/// Explain a WAF block that is not an API authentication failure.
26pub fn cloudflare_1010_message() -> String {
27    format!(
28        "Request blocked by edge WAF (Cloudflare error 1010: browser signature banned). \
29         This is not an API authentication failure. \
30         Retry with User-Agent '{}' (set automatically by this SDK).",
31        user_agent()
32    )
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    #[test]
40    fn user_agent_is_polyester_identity() {
41        let ua = user_agent();
42        assert!(ua.starts_with("polyester-sdk-rust/"));
43        assert!(!ua.contains("hyper"));
44    }
45
46    #[test]
47    fn detects_cloudflare_1010() {
48        let body = "<!DOCTYPE html><title>Attention Required! | Cloudflare</title>error code: 1010";
49        assert!(is_cloudflare_browser_ban(body));
50        assert!(!is_cloudflare_browser_ban(
51            r#"{"code":"permission_denied"}"#
52        ));
53    }
54}