rwf2/Rocket · error · rocket::serde::json::Error
UnexpectedEof
UnexpectedEof
Error message
data limit exceeded
What it means
Runtime error from the Json<T> FromData guard (core/lib/src/serde/json.rs): the request body is opened with the 'json' limit (Limits::JSON, default 1 MiB); if the stream is truncated at the limit (into_string returns an incomplete Capped), Rocket returns io::ErrorKind::UnexpectedEof 'data limit exceeded', and the FromData impl maps it to 413 PayloadTooLarge. This is Rocket's guard against unbounded JSON bodies, not a client parsing error.
Source
Thrown at core/lib/src/serde/json.rs:187
/// ```
#[inline(always)]
pub fn into_inner(self) -> T {
self.0
}
}
impl<'r, T: Deserialize<'r>> Json<T> {
fn from_str(s: &'r str) -> Result<Self, Error<'r>> {
serde_json::from_str(s).map(Json).map_err(|e| Error::Parse(s, e))
}
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Result<Self, Error<'r>> {
let limit = req.limits().get("json").unwrap_or(Limits::JSON);
let string = match data.open(limit).into_string().await {
Ok(s) if s.is_complete() => s.into_inner(),
Ok(_) => {
let eof = io::ErrorKind::UnexpectedEof;
return Err(Error::Io(io::Error::new(eof, "data limit exceeded")));
},
Err(e) => return Err(Error::Io(e)),
};
Self::from_str(local_cache!(req, string))
}
}
#[crate::async_trait]
impl<'r, T: Deserialize<'r>> FromData<'r> for Json<T> {
type Error = Error<'r>;
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
match Self::from_data(req, data).await {
Ok(value) => Outcome::Success(value),
Err(Error::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
Outcome::Error((Status::PayloadTooLarge, Error::Io(e)))
},View on GitHub (pinned to 3a54d079ae)
Solutions
- Raise the limit in Rocket.toml: [default.limits] json = "5 MiB"
- Redesign the API to stream large payloads (multipart with TempFile, or chunked upload endpoints) instead of huge JSON
- Handle the 413 in a catcher or client-side by splitting the request
- Confirm with curl --data-binary @big.json -H 'Content-Type: application/json' which side truncates
Example fix
# before
# Rocket.toml (json defaults to 1 MiB)
#[post("/report", data = "<r>")]
fn report(r: Json<Report>) { }
# after
# Rocket.toml
[default.limits]
json = "8 MiB"
#[post("/report", data = "<r>")]
fn report(r: Json<Report>) { } Defensive patterns
Strategy: try-catch
Validate before calling
// route around the guard for oversized payloads: check Content-Length first
#[post("/report", data = "<data>")]
fn report(req: &Request<'_>, data: Data<'_>) -> Status {
let limit: usize = 5 * 1024 * 1024;
if let Some(len) = req.headers().get_one("Content-Length").and_then(|v| v.parse().ok()) {
if len > limit { return Status::PayloadTooLarge; }
}
// small enough: continue (guard would also work here)
Status::Ok
} Try / catch
// client side: handle 413 by splitting or compressing the payload
match client.post("/report").json(&body).send().await {
resp if resp.status() == StatusCode::PAYLOAD_TOO_LARGE => { /* split/retry smaller */ }
resp => { /* normal path */ }
} Prevention
- Declare limits.json explicitly in Rocket.toml for every profile — never assume the 1 MiB default
- Keep JSON payloads small by design; move binaries to multipart/TempFile endpoints
- Add tests with oversized fixtures so limit regressions fail in CI
When it happens
Trigger: POST/PUT with Content-Type: application/json and a body larger than limits.json (default 1 MiB) to a route guarded by Json<T>. The guard fails before serde deserialization ever runs, yielding 413.
Common situations: APIs accepting base64-encoded images or large arrays in JSON; batch endpoints; front-ends that grew payloads over time and only now crossed 1 MiB; proxies that don't enforce their own body limit so Rocket's fires first.
Related errors
AI-assisted analysis of rwf2/Rocket@3a54d079ae (2026-08-16).
Data as JSON: /api/errors/7f65140fe4551d38.
Report an issue: GitHub.