File size: 5,878 Bytes
a21c316
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// 429 重试策略
// Duration 解析

use regex::Regex;
use once_cell::sync::Lazy;

static DURATION_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"([\d.]+)\s*(ms|s|m|h)").unwrap()
});

static RE_QUOTA_PATTERNS: Lazy<Vec<Regex>> = Lazy::new(|| {
    vec![
        Regex::new(r"(?i)quota will reset after ([^.,;\]\n]+)").unwrap(),
        Regex::new(r"(?i)retry after ([^.,;\]\n]+)").unwrap(),
        Regex::new(r#"(?i)quotaResetDelay["'=:\s]+([^\s,"}\]]+)"#).unwrap(),
    ]
});

static RETRY_HINT_KEYS: Lazy<std::collections::HashSet<&'static str>> = Lazy::new(|| {
    [
        "retryafter", "retry_after", "retrydelay", "retry_delay",
        "quotaresetdelay", "quota_reset_delay", "backofflimit", "backoff_limit"
    ].iter().cloned().collect()
});

/// 解析 Duration 字符串 (e.g., "1.5s", "200ms", "1h16m0.667s")
pub fn parse_duration_ms(duration_str: &str) -> Option<u64> {
    let mut total_ms: f64 = 0.0;
    let mut matched = false;

    for cap in DURATION_RE.captures_iter(duration_str) {
        matched = true;
        let value: f64 = cap[1].parse().ok()?;
        let unit = &cap[2];

        match unit {
            "ms" => total_ms += value,
            "s" => total_ms += value * 1000.0,
            "m" => total_ms += value * 60.0 * 1000.0,
            "h" => total_ms += value * 60.0 * 60.0 * 1000.0,
            _ => {}
        }
    }

    if !matched {
        return None;
    }

    Some(total_ms.round() as u64)
}

/// 从 429 错误中提取 retry delay (深度递归解析)
pub fn parse_retry_delay(error_text: &str) -> Option<u64> {
    use serde_json::Value;

    // 1. 尝试正则提取 (针对非 JSON 文本或嵌套不深的文本)
    for re in RE_QUOTA_PATTERNS.iter() {
        if let Some(cap) = re.captures(error_text) {
            if let Some(delay) = parse_duration_ms(&cap[1]) {
                return Some(delay);
            }
        }
    }

    // 2. 尝试结构化 JSON 解析 (递归扫描)
    let delay = if let Ok(json) = serde_json::from_str(error_text) {
        extract_structured_delay_recursive(&json, 0)
    } else {
        None
    };

    // [NEW] 引入 1500ms 官方 "Grace Window" 缓冲区
    // 官方实现会在解析出的延迟基础上强制多等 1.5s,以确保 100% 越过 Google 配额重置点。
    delay.map(|d| d + 1500)
}

/// 递归提取结构化延迟
fn extract_structured_delay_recursive(value: &serde_json::Value, depth: usize) -> Option<u64> {
    if depth > 8 { return None; }

    match value {
        serde_json::Value::Object(map) => {
            // 检查当前对象是否本身就是一个 Duration 对象 (seconds/nanos)
            if let Some(d) = parse_structured_duration_object(value) {
                return Some(d);
            }

            // 递归扫描子字段
            for (key, val) in map {
                // 模糊 Key 匹配 (转小写, 去除分隔符)
                let normalized_key = key.to_lowercase().replace('-', "").replace('_', "");
                if RETRY_HINT_KEYS.contains(normalized_key.as_str()) {
                    // 如果命中了 Hint Key,直接尝试解析其内容
                    if let Some(d) = parse_structured_duration_value(val) {
                        return Some(d);
                    }
                }
                // 继续深度搜索
                if let Some(d) = extract_structured_delay_recursive(val, depth + 1) {
                    return Some(d);
                }
            }
        }
        serde_json::Value::Array(arr) => {
            for val in arr {
                if let Some(d) = extract_structured_delay_recursive(val, depth + 1) {
                    return Some(d);
                }
            }
        }
        serde_json::Value::String(s) => {
            return parse_duration_ms(s);
        }
        _ => {}
    }
    None
}

/// 解析强类型的 Duration 对象 (Google 格式: {seconds: 1, nanos: 0})
fn parse_structured_duration_object(value: &serde_json::Value) -> Option<u64> {
    let obj = value.as_object()?;
    let seconds = obj.get("seconds").or_else(|| obj.get("Seconds"))
        .and_then(|v| v.as_f64()).unwrap_or(0.0);
    let nanos = obj.get("nanos").or_else(|| obj.get("Nanos"))
        .and_then(|v| v.as_f64()).unwrap_or(0.0);
    
    if seconds > 0.0 || nanos > 0.0 {
        let total_ms = (seconds * 1000.0) + (nanos / 1_000_000.0);
        return Some(total_ms.round() as u64);
    }
    None
}

/// 解析各种可能包含时长信息的 Value
fn parse_structured_duration_value(value: &serde_json::Value) -> Option<u64> {
    match value {
        serde_json::Value::String(s) => parse_duration_ms(s),
        serde_json::Value::Number(n) => n.as_f64().map(|f| (f * 1000.0).round() as u64),
        serde_json::Value::Object(_) => parse_structured_duration_object(value),
        _ => None,
    }
}

/// [NEW] 判断是否应当执行 Grace Retry (原地重试)
/// 当 429 报错提示的重置时间在接受范围内(如 2s 内),则原地重试比切换账号更有利。
pub fn should_grace_retry(duration_ms: u64) -> bool {
    // 默认阈值:2000ms
    duration_ms > 0 && duration_ms <= 2000
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_duration_ms() {
        assert_eq!(parse_duration_ms("1.5s"), Some(1500));
        assert_eq!(parse_duration_ms("200ms"), Some(200));
        assert_eq!(parse_duration_ms("1h16m0.667s"), Some(4560667));
        assert_eq!(parse_duration_ms("invalid"), None);
    }

    #[test]
    fn test_parse_retry_delay() {
        let error_json = r#"{
            "error": {
                "details": [{
                    "@type": "type.googleapis.com/google.rpc.RetryInfo",
                    "retryDelay": "1.203608125s"
                }]
            }
        }"#;

        assert_eq!(parse_retry_delay(error_json), Some(1204));
    }
}