{"record":{"id":"f1db5d875065a592","repo":"a-b-street/abstreet","slug":"http-error","errorCode":null,"errorMessage":"HTTP error {}: {}","messagePattern":"HTTP error (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"abstio/src/http.rs","lineNumber":18,"sourceCode":"use anyhow::{Context, Result};\n\n/// Performs an HTTP POST request and returns the response.\npub async fn http_post<U: AsRef<str>, B: Into<reqwest::Body>>(url: U, body: B) -> Result<String> {\n    let url = url.as_ref();\n    info!(\"HTTP POST to {}\", url);\n    let resp = reqwest::Client::new()\n        .post(url)\n        .body(body)\n        .send()\n        .await\n        .with_context(|| url.to_string())?;\n    let status = resp.status();\n    let text = resp.text().await.with_context(|| url.to_string())?;\n    // With error_for_status{_ref}, it's unclear how to propagate errors and also get the error\n    // message from the body, so do this\n    if status.is_client_error() || status.is_server_error() {\n        bail!(\"HTTP error {}: {}\", status, text);\n    }\n    Ok(text)\n}\n\n/// Performs an HTTP GET request and returns the raw response. Unlike the variations in\n/// download.rs, no progress -- but it works on native and web.\npub async fn http_get<I: AsRef<str>>(url: I) -> Result<Vec<u8>> {\n    let url = url.as_ref();\n    info!(\"HTTP GET {}\", url);\n    let resp = reqwest::get(url).await?.error_for_status()?.bytes().await?;\n    Ok(resp.to_vec())\n}\n","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/abstio/src/http.rs#L1-L31","documentation":"abstio::http_post performs an HTTP POST and returns the body as text. If the response status is a client error (4xx) or server error (5xx), it bails with \"HTTP error {status}: {body}\" so the caller can see both the status code and the server's error message.","triggerScenarios":"Any POST whose server responds with 4xx/5xx: bad endpoint URL, missing/invalid authentication, malformed request payload, server-side failure, rate limiting.","commonSituations":"Hitting an API whose auth token expired; posting to a URL that only accepts GET; network proxies returning 502/503; server validation rejecting the posted JSON.","solutions":["Inspect the status code and body in the error to identify whether the problem is auth, payload, or server-side.","Verify the URL, headers (Content-Type, auth), and request body format match what the endpoint expects.","Add retry logic with backoff for transient 5xx (e.g. 502/503), but not for 4xx errors."],"exampleFix":"// before\nlet text = abstio::http_post(url, body).await?;\n// after\nmatch abstio::http_post(url, body).await {\n    Ok(text) => Ok(text),\n    Err(err) => { log::error!(\"POST to {} failed: {}\", url, err); Err(err) }\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight checks before POST:\nassert!(!auth_token.is_empty(), \"missing auth token\");\nassert!(url.starts_with(\"https://\"), \"unexpected URL\");","typeGuard":null,"tryCatchPattern":"match abstio::http_post(url, body).await {\n    Ok(text) => Ok(text),\n    Err(e) if e.to_string().contains(\"HTTP error 5\") => retry_with_backoff(url, body).await,\n    Err(e) => Err(e),\n}","preventionTips":["Log the status and body from the error message before deciding to retry.","Retry only 5xx responses; treat 4xx as a caller bug.","Validate the payload serializes to what the endpoint expects."],"tags":["http","network","api"],"backgroundTag":"http-error-response","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}