{"record":{"id":"78822a08fe643476","repo":"tonhowtf/omniget","slug":"tabela-de-precos-invalida","errorCode":null,"errorMessage":"tabela de precos invalida","messagePattern":"tabela de precos invalida","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/tools/pricing.rs","lineNumber":133,"sourceCode":"        .and_then(|p| std::fs::metadata(p).ok())\n        .and_then(|m| m.modified().ok())\n        .map(|t| chrono::DateTime::<chrono::Utc>::from(t).to_rfc3339());\n    Ok(PricingInfo {\n        models: v\n            .as_object()\n            .map(|o| o.len().saturating_sub(1))\n            .unwrap_or(0),\n        updated_at: updated,\n        path: path.map(|p| p.to_string_lossy().to_string()),\n    })\n}\n\n/// Busca por substring nas chaves; todas as palavras precisam bater.\npub async fn search(query: &str, mode: &str, limit: usize) -> anyhow::Result<Vec<ModelPrice>> {\n    let v = load(false).await?;\n    let obj = v\n        .as_object()\n        .ok_or_else(|| anyhow!(\"tabela de precos invalida\"))?;\n    let tokens: Vec<String> = query\n        .to_lowercase()\n        .split_whitespace()\n        .map(|s| s.to_string())\n        .collect();\n    let mut out: Vec<ModelPrice> = obj\n        .iter()\n        .filter(|(k, _)| *k != \"sample_spec\")\n        .filter(|(k, val)| {\n            let hay = format!(\n                \"{} {}\",\n                k.to_lowercase(),\n                val[\"litellm_provider\"]\n                    .as_str()\n                    .unwrap_or(\"\")\n                    .to_lowercase()\n            );\n            tokens.iter().all(|t| hay.contains(t))","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/tools/pricing.rs#L115-L151","documentation":"search() loads the price table (from cache or network) and requires the top-level JSON to be an object keyed by model name. If the parsed value is not a JSON object (array, string, null, etc.), it throws \"tabela de precos invalida\". This is a schema guard protecting the token-matching logic that iterates over object keys.","triggerScenarios":"The cached pricing.json contains valid JSON of the wrong shape (e.g. truncated into an array, an HTML error page saved as the cache, or an upstream format change), and load() successfully parsed it without shape-checking.","commonSituations":"Cache file corrupted or half-written by a crash, upstream changed the response schema, a proxy returned an error page that was cached as JSON text (if it happened to parse).","solutions":["Delete the cached pricing file at cache_path so load() re-fetches a fresh table","Validate the JSON structure of the cache file (top-level must be an object of model keys)","Update the app if the upstream price-table format changed","Add a shape check in load() before caching responses"],"exampleFix":"// before: trusting any parseable JSON\nif let Ok(text) = tokio::fs::read_to_string(&path).await {\n    if let Ok(v) = serde_json::from_str(&text) { return Ok(v); }\n}\n// after: validate shape before returning\nif let Ok(text) = tokio::fs::read_to_string(&path).await {\n    if let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) {\n        if v.as_object().map(|o| !o.is_empty()).unwrap_or(false) { return Ok(v); }\n    }\n}","handlingStrategy":"type-guard","validationCode":"// inspect the cache before use\nlet v: serde_json::Value = serde_json::from_str(\n    &std::fs::read_to_string(&cache_path)?\n)?;\nif !v.is_object() {\n    std::fs::remove_file(&cache_path)?; // força re-fetch\n}","typeGuard":"fn is_price_table(v: &serde_json::Value) -> bool {\n    v.as_object().map(|o| !o.is_empty()).unwrap_or(false)\n}","tryCatchPattern":"match pricing::search(q, \"\", 10).await {\n    Err(e) if e.to_string().contains(\"invalida\") => {\n        // limpar cache e tentar uma vez\n        std::fs::remove_file(cache_path()).ok();\n        pricing::search(q, \"\", 10).await\n    }\n    other => other,\n}","preventionTips":["Validate the shape of fetched data before writing it to the cache","Write the cache atomically (temp file + rename) to avoid truncated files","Delete stale caches after app upgrades that change the schema"],"tags":["json","schema","pricing","cache","rust"],"backgroundTag":"schema-validation-failed","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}