{"record":{"id":"714903e229b46dfd","repo":"sigoden/aichat","slug":"invalid-request-body-err","errorCode":null,"errorMessage":"Invalid request body, {err}","messagePattern":"Invalid request body, (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/serve.rs","lineNumber":243,"sourceCode":"        Ok(res)\n    }\n\n    fn list_rags(&self) -> Result<AppResponse> {\n        let data = json!({ \"data\": self.rags });\n        let res = Response::builder()\n            .header(\"Content-Type\", \"application/json; charset=utf-8\")\n            .body(Full::new(Bytes::from(data.to_string())).boxed())?;\n        Ok(res)\n    }\n\n    async fn search_rag(&self, req: hyper::Request<Incoming>) -> Result<AppResponse> {\n        let req_body = req.collect().await?.to_bytes();\n        let req_body: Value = serde_json::from_slice(&req_body)\n            .map_err(|err| anyhow!(\"Invalid request json, {err}\"))?;\n\n        debug!(\"search rag request: {req_body}\");\n        let SearchRagReqBody { name, input } = serde_json::from_value(req_body)\n            .map_err(|err| anyhow!(\"Invalid request body, {err}\"))?;\n\n        let config = Arc::new(RwLock::new(self.config.clone()));\n\n        let abort_signal = create_abort_signal();\n\n        let rag_path = config.read().rag_file(&name);\n        let rag = Rag::load(&config, &name, &rag_path)?;\n\n        let rag_result = Config::search_rag(&config, &rag, &input, abort_signal).await?;\n\n        let data = json!({ \"data\": rag_result });\n        let res = Response::builder()\n            .header(\"Content-Type\", \"application/json; charset=utf-8\")\n            .body(Full::new(Bytes::from(data.to_string())).boxed())?;\n        Ok(res)\n    }\n\n    async fn chat_completions(&self, req: hyper::Request<Incoming>) -> Result<AppResponse> {","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/serve.rs#L225-L261","documentation":"In `search_rag` (src/serve.rs:243), the handler parses the raw request body into serde_json::Value successfully, then deserializes that Value into the `SearchRagReqBody { name, input }` struct. This error wraps serde's field/type error when the JSON has the correct syntax but does not match the struct: missing `name` or `input`, wrong types (e.g. input is a number instead of a string), or unexpected extra fields if deny_unknown_fields is set.","triggerScenarios":"POST to the /search_rag endpoint with a JSON body that is valid JSON but not a valid SearchRagReqBody: `{}` (missing fields), `{\"name\": \"myrag\"}` (missing input), `{\"name\": 123, \"input\": \"q\"}` (wrong type), or `{\"name\":\"r\",\"input\":\"q\",\"extra\":1}` when unknown fields are rejected.","commonSituations":"Clients sending OpenAI-style or homemade payloads instead of the endpoint's schema; forgetting that `name` refers to the rag file name registered in config; sending form-encoded or multipart data that accidentally parses as JSON but lacks fields; API version drift where the client was written against an older body schema.","solutions":["Include both required fields with correct types: `{\"name\": \"<rag-file-name>\", \"input\": \"<query string>\"}`","Send `Content-Type: application/json` and a raw JSON body, not form data","Check the exact field names and types of SearchRagReqBody in src/serve.rs and match your client payload to it","Log the request body (the handler already debug!-logs it) and compare against the struct definition"],"exampleFix":"// before\ncurl -X POST /search_rag -d '{\"rag_name\": \"docs\", \"query\": \"hello\"}'\n// after\ncurl -X POST /search_rag -H 'Content-Type: application/json' -d '{\"name\": \"docs\", \"input\": \"hello\"}'","handlingStrategy":"validation","validationCode":"function validateSearchRagBody(body) {\n  if (typeof body !== 'object' || body === null) return 'body must be an object';\n  if (typeof body.name !== 'string' || !body.name) return 'name (string) is required';\n  if (typeof body.input !== 'string' || !body.input) return 'input (string) is required';\n  return null;\n}","typeGuard":"function isSearchRagBody(v) {\n  return typeof v === 'object' && v !== null\n    && typeof v.name === 'string'\n    && typeof v.input === 'string';\n}","tryCatchPattern":"try {\n  const res = await fetch('/search_rag', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({name, input})});\n  if (!res.ok) {\n    const text = await res.text();\n    if (text.includes('Invalid request body')) throw new Error(`Schema mismatch: ${text}`);\n    throw new Error(`HTTP ${res.status}: ${text}`);\n  }\n} catch (e) { /* log and surface schema error to caller */ }","preventionTips":["Generate the client payload from the SearchRagReqBody schema rather than hand-writing JSON","Always JSON.stringify the body and set Content-Type: application/json","Log the exact body sent and diff against the server's struct definition","Add a client-side JSON-schema validator for endpoint payloads"],"tags":["rust","json","serde","http-server","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}