astrid-runtime/astrid · error

empty page deserializes

Error message

empty page deserializes

What it means

This panic comes from `.expect("empty page deserializes")` in the test `parse_search_response_null_next_cursor_is_last_page`. The library helper `parse_search_response` returns an Option and yielded None for a JSON body of `{"results": [], "next_cursor": null}`, meaning the page failed to deserialize into the search-response shape. The test exists precisely to guarantee that an empty last page with a null cursor is accepted.

Source

Thrown at crates/astrid-gateway/src/routes/sessions_tests.rs:697

    let parsed = parse_search_response(body).expect("frozen search shape deserializes");
    assert_eq!(parsed.results.len(), 2);
    assert_eq!(parsed.next_cursor.as_deref(), Some("page-2"));
    let first = &parsed.results[0];
    assert_eq!(first.session_id, "s1");
    assert_eq!(first.title.as_deref(), Some("Trip planning"));
    assert_eq!(first.match_count, 2);
    assert_eq!(first.updated_at, Some(1_719_000_000));
    let second = &parsed.results[1];
    assert!(second.title.is_none());
    assert!(second.snippet.is_none());
    assert!(second.updated_at.is_none());
    assert_eq!(second.match_count, 1);
}

#[test]
fn parse_search_response_null_next_cursor_is_last_page() {
    let body = serde_json::json!({ "results": [], "next_cursor": null });
    let parsed = parse_search_response(body).expect("empty page deserializes");
    assert!(parsed.results.is_empty());
    assert!(parsed.next_cursor.is_none());
    // Absent next_cursor is also fine (defaults to None).
    let body = serde_json::json!({ "results": [] });
    assert!(parse_search_response(body).unwrap().next_cursor.is_none());
}

#[test]
fn parse_search_response_rejects_garbage() {
    let body = serde_json::json!({ "results": "not-an-array" });
    assert!(matches!(
        parse_search_response(body).unwrap_err(),
        GatewayError::Kernel(_)
    ));
}

/// Live round-trip over a real `EventBus` for the `update` verb: the
/// stand-in capsule receives the principal-stamped, present-keys-only

View on GitHub (pinned to affd8760f4)

Solutions

  1. Inspect the body passed to parse_search_response and confirm it contains a `results` array (possibly empty) and an optional `next_cursor`.
  2. Update parse_search_response so a null `next_cursor` maps to None instead of failing deserialization.
  3. Add a serde-level unit test on the response struct with `#[serde(default)]`/Option fields to lock the empty-page contract.
  4. Regenerate or refresh any client types from the current OpenAPI/schema if the gateway contract changed.

Example fix

// before
let parsed = parse_search_response(body).expect("empty page deserializes");
// after
let Some(parsed) = parse_search_response(body) else {
    eprintln!("body did not match search-response schema: {body}");
    return;
};
Defensive patterns

Strategy: validation

Validate before calling

if body.get("results").and_then(|r| r.as_array()).is_none() {
    return Err("search response missing results array");
}

Type guard

fn is_valid_search_body(v: &serde_json::Value) -> bool {
    v.get("results").map_or(false, |r| r.is_array())
}

Try / catch

let Some(parsed) = parse_search_response(body) else {
    eprintln!("unparseable search response: {body}");
    return Err(ParseError::Schema);
};

Prevention

When it happens

Trigger: Calling `parse_search_response` with a body whose shape does not match the expected struct — e.g. results missing, wrong JSON type for `results`, or `next_cursor` of an unexpected type — so the deserialization path returns None instead of Some(page).

Common situations: The session-search API changed its response envelope (field renamed from `results`, cursor renamed); a serializer emits `next_cursor: null` on the last page and an older parser rejects it; hand-rolled JSON in tests drifts from the real payload schema.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/a9a06863eeec45e7. Report an issue: GitHub.