tursodatabase/turso · error · anyhow::Error

invalid response shape

Error message

invalid response shape

What it means

Thrown by TursoServer::db_sql when results[0] is ok but the element at response.result.rows is missing or not a JSON array (bindings/rust/src/sync.rs:1208). The server answered successfully, but the response does not match the shape the harness expects for a row set.

Source

Thrown at bindings/rust/src/sync.rs:1305

                    "requests": [{
                        "type": "execute",
                        "stmt": { "sql": sql }
                    }]
                }))
                .send()
                .await?
                .error_for_status()?;

            let value: serde_json::Value = resp.json().await?;

            let result = &value["results"][0];
            if result["type"] != "ok" {
                return Err(anyhow!("remote sql execution failed: {value}"));
            }

            let rows = result["response"]["result"]["rows"]
                .as_array()
                .ok_or_else(|| anyhow!("invalid response shape"))?;

            Ok(rows
                .iter()
                .map(|row| {
                    row.as_array()
                        .unwrap()
                        .iter()
                        .map(|cell| match cell["value"].clone() {
                            serde_json::Value::Null => Value::Null,
                            serde_json::Value::Number(number) => {
                                if number.is_i64() {
                                    Value::Integer(number.as_i64().unwrap())
                                } else {
                                    Value::Real(number.as_f64().unwrap())
                                }
                            }
                            serde_json::Value::String(s) => Value::Text(s),
                            _ => panic!("unexpected json output"),

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Print the full response value before the shape check to see what the server actually returned
  2. Rebuild both the sync server binary and the bindings crate from the same commit
  3. Run the tests from a single workspace checkout so client and server versions match
  4. If the statement is non-query, use an execution helper that does not expect rows

Example fix

// before
let rows = result["response"]["result"]["rows"]
    .as_array()
    .ok_or_else(|| anyhow!("invalid response shape"))?;
// after
let Some(rows) = result["response"]["result"]["rows"].as_array() else {
    anyhow::bail!("unexpected response: {value}");
};
Defensive patterns

Strategy: type-guard

Validate before calling

let value: serde_json::Value = resp.json().await?;
anyhow::ensure!(has_row_array(&value), "unexpected sync response: {value}");

Type guard

fn has_row_array(v: &serde_json::Value) -> bool {
    v["results"][0]["response"]["result"]["rows"].is_array()
}

Prevention

When it happens

Trigger: Server version that returns a different response schema (renamed fields, rows nested elsewhere); a statement type that legitimately returns no rows object; response format changed on one side of a client/server version skew.

Common situations: Harness and server built from different commits during a bisect; the Hrana/sync response format evolved in core without updating the bindings test harness; a new statement kind added server-side.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20). Data as JSON: /api/errors/9bcbcf1d72356fd4. Report an issue: GitHub.