{"record":{"id":"8270255cb545e8ae","repo":"BigPizzaV3/CodexPlusPlus","slug":"error-827025","errorCode":null,"errorMessage":"倍率接口缺少观测时间","messagePattern":"倍率接口缺少观测时间","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/sub2api.rs","lineNumber":125,"sourceCode":"    {\n        anyhow::bail!(\"倍率接口返回结构不是 sub2api.key_billing\");\n    }\n    for value in [\n        response.group_rate_multiplier,\n        response.resolved_rate_multiplier,\n        response.effective_rate_multiplier,\n    ] {\n        if !value.is_finite() || value < 0.0 {\n            anyhow::bail!(\"倍率接口返回了无效倍率\");\n        }\n    }\n    if let Some(value) = response.user_rate_multiplier {\n        if !value.is_finite() || value < 0.0 {\n            anyhow::bail!(\"倍率接口返回了无效用户倍率\");\n        }\n    }\n    if response.observed_at.trim().is_empty() {\n        anyhow::bail!(\"倍率接口缺少观测时间\");\n    }\n    Ok(())\n}\n\nfn sub2api_error_message(body: &str) -> Option<String> {\n    let payload: Value = serde_json::from_str(body).ok()?;\n    for path in [&[\"error\", \"message\"][..], &[\"message\"][..], &[\"error\"][..]] {\n        let mut current = &payload;\n        for key in path {\n            current = current.get(*key)?;\n        }\n        if let Some(message) = current\n            .as_str()\n            .map(str::trim)\n            .filter(|value| !value.is_empty())\n        {\n            return Some(message.to_string());\n        }","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/sub2api.rs#L107-L143","documentation":"validate_sub2api_billing_response checks that a sub2api billing/user-info API response carries all required fields before the app trusts it. Besides validating the user rate multiplier, it requires a non-empty `observed_at` field: without an observation timestamp the billing data cannot be presented as current, so the library bails instead of displaying stale or untraceable rate info.","triggerScenarios":"Calling fetch_sub2api_billing_info against a sub2api endpoint whose JSON response omits `observed_at` or returns it as an empty/whitespace-only string. The multiplier check may pass (field optional) but the missing timestamp still aborts.","commonSituations":"Pointing the app at an older sub2api server version that predates the `observed_at` field; a reverse proxy or mock that strips/renames fields; hand-written test fixtures that only include `user_rate_multiplier`.","solutions":["Upgrade the sub2api server to a version whose billing endpoint returns `observed_at`.","Inspect the raw JSON response (curl the billing endpoint) and confirm the field name is exactly `observed_at` and non-empty.","Remove any proxy/mock/fixture layer that strips the field, or update test fixtures to include a timestamp.","If the upstream truly has no timestamp, display a placeholder instead of calling this validation path."],"exampleFix":"// before\nlet response: Sub2apiBillingResponse = serde_json::from_str(&body)?;\nfetch_sub2api_billing_info(...)?;\n// after\n// ensure server returns observed_at, or guard before calling:\nif body.contains(\"observed_at\") {\n    fetch_sub2api_billing_info(...)?;\n} else {\n    // handle legacy server: show multiplier without timestamp\n}","handlingStrategy":"validation","validationCode":"let resp: serde_json::Value = serde_json::from_str(&body)?;\nlet observed_at = resp.get(\"observed_at\").and_then(|v| v.as_str()).unwrap_or(\"\");\nif observed_at.trim().is_empty() {\n    // legacy server: skip billing display or fetch from another endpoint\n}","typeGuard":"fn has_observed_at(v: &serde_json::Value) -> bool {\n    v.get(\"observed_at\").and_then(|x| x.as_str()).map_or(false, |s| !s.trim().is_empty())\n}","tryCatchPattern":"match fetch_sub2api_billing_info(...) {\n    Ok(info) => render(info),\n    Err(e) if e.to_string().contains(\"观测时间\") => render_stale_banner(),\n    Err(e) => return Err(e),\n}","preventionTips":["Pin sub2api server to a version whose billing endpoint includes observed_at.","Add observed_at to any mock/test fixtures of the billing response.","Integration-test the billing endpoint after every server upgrade."],"tags":["api","validation","missing-field","billing"],"backgroundTag":"empty-required-field","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}