{"record":{"id":"68dd2f10c38a1503","repo":"abhigyanpatwari/GitNexus","slug":"params-must-be-a-plain-object-with-scalar-values","errorCode":null,"errorMessage":"\"params\" must be a plain object with scalar values (string/number/boolean/null)","messagePattern":"\"params\" must be a plain object with scalar values \\(string/number/boolean/null\\)","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"gitnexus/src/server/api.ts","lineNumber":656,"sourceCode":"      res.status(statusFromError(err)).json({ error: err.message || 'Failed to read file' });\n    }\n  }\n};\n\nexport const handleQueryRequest = async (\n  req: express.Request,\n  res: express.Response,\n  resolveRepo: (repoName?: string) => Promise<{ storagePath: string } | undefined>,\n): Promise<void> => {\n  try {\n    const cypher = req.body.cypher as string;\n    if (!cypher) {\n      res.status(400).json({ error: 'Missing \"cypher\" in request body' });\n      return;\n    }\n    const queryParams = req.body.params;\n    if (queryParams !== undefined && !isValidQueryParams(queryParams)) {\n      res.status(400).json({\n        error: '\"params\" must be a plain object with scalar values (string/number/boolean/null)',\n      });\n      return;\n    }\n\n    const entry = await resolveRepo(requestedRepo(req));\n    if (!entry) {\n      res.status(404).json({ error: 'Repository not found' });\n      return;\n    }\n    const lbugPath = path.join(entry.storagePath, 'lbug');\n    const result = await withLbugDb(lbugPath, () => executePrepared(cypher, queryParams ?? {}), {\n      readOnly: true,\n    });\n    res.json({ result });\n  } catch (err: any) {\n    if (isReadOnlyDbError(err)) {\n      res.status(403).json({ error: 'Write queries are not allowed via the HTTP API' });","sourceCodeStart":638,"sourceCodeEnd":674,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/api.ts#L638-L674","documentation":"HTTP 400 from POST /api/query when the optional params field is present but fails isValidQueryParams: it must be a plain object whose values are scalars only (string, number, boolean, or null). Because the cypher is executed via executePrepared against LadybugDB, allowing arrays, nested objects, or other types would either break prepared-statement binding or smuggle structure into the query layer, so the API validates before opening the read-only connection.","triggerScenarios":"Sending params as an array [1,2,3]; nesting objects {filter:{name:'x'}}; passing a serialized string 'name=x'; sending true/false boxed weirdly via JSON revival; passing a params value that is an array of one element expecting it to bind as a scalar.","commonSituations":"Porting a query client from a Neo4j driver that accepts array params; frontend state objects being passed wholesale as params; Date objects (they serialize to strings via JSON, but hand-built clients may pass them as objects); users trying to sneak full filter objects through the HTTP API.","solutions":["Flatten params to scalars: {\"params\": {\"name\": \"foo\", \"limit\": 10, \"flag\": true}} — one level, scalar values only","Omit params entirely when the query has none","Stringify or JSON-encode nested structures before sending if the query needs them, and decode in Cypher","Validate client-side with a type guard mirroring the server's rule before POSTing"],"exampleFix":"// before\nbody: JSON.stringify({ cypher, params: { filters: { name: 'x' } } }) // nested → 400\n\n// after\nbody: JSON.stringify({ cypher, params: { name: 'x' } }) // scalar values only","handlingStrategy":"type-guard","validationCode":"// Flatten params to scalar values before sending.\nfunction flattenParams(obj: Record<string, unknown>): Record<string, string | number | boolean | null> {\n  const out: Record<string, string | number | boolean | null> = {};\n  for (const [k, v] of Object.entries(obj)) {\n    out[k] = (v !== null && typeof v === 'object') ? JSON.stringify(v) : (v as string | number | boolean | null);\n  }\n  return out;\n}","typeGuard":"type Scalar = string | number | boolean | null;\nfunction isValidQueryParams(v: unknown): v is Record<string, Scalar> {\n  if (typeof v !== 'object' || v === null || Array.isArray(v)) return false;\n  return Object.values(v).every((x) => x === null || ['string', 'number', 'boolean'].includes(typeof x));\n}","tryCatchPattern":null,"preventionTips":["Send params as one flat object of scalars only","Stringify nested structures yourself if a query needs them","Omit params entirely when the query has none"],"tags":["http-400","api","cypher","validation","query-params"],"backgroundTag":"request-validation-failed","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}