seanmonstar/reqwest · error · reqwest::Error
error decoding response body
Error message
error decoding response body
What it means
The `Kind::Decode` error from `error::decode(...)` (error.rs:352-354). It wraps failures while interpreting the response bytes — overwhelmingly `serde_json::from_slice` failing inside `.json::<T>()` (response.rs:272), or a hyper body decode error while reading the stream (client.rs:3129, response.rs:435).
Source
Thrown at src/error.rs:353
#[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
Status(StatusCode),
Body,
Decode,
Upgrade,
}
// constructors
pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Builder, Some(e))
}
pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Body, Some(e))
}
pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Decode, Some(e))
}
pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Request, Some(e))
}
pub(crate) fn dns<E: Into<BoxError>>(e: E) -> BoxError {
Box::new(DnsError { inner: e.into() })
}
pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
Error::new(Kind::Redirect, Some(e)).with_url(url)
}
pub(crate) fn status_code(
url: Url,
status: StatusCode,
#[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] reason: Option<hyper::ext::ReasonPhrase>,View on GitHub (pinned to 17e9bcb51c)
Solutions
- Read `.text().await?` first and log it when decode fails, then parse manually to see the real payload.
- Deserialize into a permissive enum/`serde_json::Value` to handle both success and error shapes.
- Check `response.status()` and `Content-Type` before calling `.json()`.
- Inspect `e.source()` for the exact `serde_json::Error` (line/column) or hyper decode error.
Example fix
// before
let user: User = resp.json().await?; // decode error on HTML error page
// after
let status = resp.status();
let text = resp.text().await?;
if !status.is_success() {
anyhow::bail!("api error {status}: {text}");
}
let user: User = serde_json::from_str(&text)
.map_err(|e| anyhow::anyhow!("decode failed: {e}; body was: {text}"))? Defensive patterns
Strategy: try-catch
Validate before calling
// Read body as text and inspect before deserializing.
let status = resp.status();
let ct = resp.headers().get("content-type").cloned();
let text = resp.text().await?;
if !status.is_success() { return Err(anyhow!("{status}: {text}")); }
if ct.map(|v| !v.to_string().contains("json")).unwrap_or(true) {
return Err(anyhow!("expected json, got: {text}"));
}
Type guard
fn is_decode_error(e: &reqwest::Error) -> bool { e.is_decode() }
Try / catch
let value = match resp.json::<T>().await {
Ok(v) => v,
Err(e) if e.is_decode() => {
// e.url() / e.source() carry the body snippet and serde location
return Err(anyhow!("decode failed: {}", e.source().map(|s| s.to_string()).unwrap_or_default()));
}
Err(e) => return Err(e.into()),
}; Prevention
- Check status and Content-Type before .json().
- Deserialize into a wide type (serde_json::Value or an enum) when the API returns multiple shapes.
- Log the raw body on decode failure once, then tighten the type.
When it happens
Trigger: Calling `.json::<MyType>()` on a response whose body isn't valid JSON or doesn't match the type; the server returning HTML/text error pages with status 200; truncated body causing `serde_json` EOF; hyper failing to assemble/decode chunked frames.
Common situations: API returns `{"error": "..."}` while you deserialize into a success struct; gateway injects an HTML 502 page; `Content-Type` lies; partial response due to dropped connection so JSON is incomplete.
Related errors
AI-assisted analysis of seanmonstar/reqwest@17e9bcb51c (2026-08-06).
Data as JSON: /data/errors/440204f6a8272cb6.json.
Report an issue: GitHub.