sigoden/aichat · error · anyhow::Error

Invalid request json

Error message

Invalid request json, {err}

What it means

The /search-rag handler reads the request body and deserializes it as JSON; malformed JSON fails with this error (a second, distinct error covers valid JSON with wrong fields). The raw serde error is interpolated for diagnosis.

Solutions

  1. Send a valid JSON body with Content-Type: application/json
  2. Validate the payload with a JSON linter before sending
  3. Ensure the body isn't empty or truncated in transit

Example fix

// before
curl -X POST localhost:8000/search-rag -d name=docs
// after
curl -X POST localhost:8000/search-rag -H 'Content-Type: application/json' -d '{"name":"docs","input":"query"}'
Defensive patterns

Strategy: validation

Validate before calling

fn valid_search_body(s: &str) -> bool {
    serde_json::from_str::<serde_json::Value>(s)
        .ok()
        .map(|v| v.get("name").is_some() && v.get("input").is_some())
        .unwrap_or(false)
}

Type guard

fn as_search_body(b: &[u8]) -> Option<(String, String)> {
    let v: serde_json::Value = serde_json::from_slice(b).ok()?;
    Some((v.get("name")?.as_str()?.into(), v.get("input")?.as_str()?.into()))
}

Try / catch

let res = client.post(url).json(&json!({"name": rag, "input": q})).send().await?;
if res.status().is_client_error() {
    let msg = res.text().await?;
    eprintln!("Bad request: {msg}");
}

Prevention

When it happens

Trigger: POSTing to search_rag with a body that is not valid JSON: empty body, HTML/plain text, truncated JSON, or wrong Content-Type payload.

Common situations: Forgetting to send a body; curl without proper quoting; clients sending form-encoded data; proxies truncating large bodies.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09). Data as JSON: /api/errors/c443bbf0e662eb5a. Report an issue: GitHub.

Appendix: source

Thrown at src/serve.rs:239

        let data = json!({ "data": self.roles });
        let res = Response::builder()
            .header("Content-Type", "application/json; charset=utf-8")
            .body(Full::new(Bytes::from(data.to_string())).boxed())?;
        Ok(res)
    }

    fn list_rags(&self) -> Result<AppResponse> {
        let data = json!({ "data": self.rags });
        let res = Response::builder()
            .header("Content-Type", "application/json; charset=utf-8")
            .body(Full::new(Bytes::from(data.to_string())).boxed())?;
        Ok(res)
    }

    async fn search_rag(&self, req: hyper::Request<Incoming>) -> Result<AppResponse> {
        let req_body = req.collect().await?.to_bytes();
        let req_body: Value = serde_json::from_slice(&req_body)
            .map_err(|err| anyhow!("Invalid request json, {err}"))?;

        debug!("search rag request: {req_body}");
        let SearchRagReqBody { name, input } = serde_json::from_value(req_body)
            .map_err(|err| anyhow!("Invalid request body, {err}"))?;

        let config = Arc::new(RwLock::new(self.config.clone()));

        let abort_signal = create_abort_signal();

        let rag_path = config.read().rag_file(&name);
        let rag = Rag::load(&config, &name, &rag_path)?;

        let rag_result = Config::search_rag(&config, &rag, &input, abort_signal).await?;

        let data = json!({ "data": rag_result });
        let res = Response::builder()
            .header("Content-Type", "application/json; charset=utf-8")
            .body(Full::new(Bytes::from(data.to_string())).boxed())?;

View on GitHub (pinned to 82976d349a)