{"record":{"id":"5603dc3abacb2466","repo":"Crosstalk-Solutions/project-nomad","slug":"msg-5603dc","errorCode":null,"errorMessage":"${msg}","messagePattern":"\\$\\{msg\\}","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"admin/app/controllers/drug_reference_controller.ts","lineNumber":115,"sourceCode":"   * Returns a slim collapsed result list (brand+generic pairs).\n   */\n  async search({ request, response }: HttpContext) {\n    try {\n      const params = await request.validateUsing(searchDrugValidator)\n      const results = await this.service.search(params.q, {\n        productType: params.product_type,\n        route: params.route,\n        sort: params.sort,\n        limit: params.limit,\n        offset: params.offset,\n        scope: params.scope,\n      })\n      return { results }\n    } catch (err) {\n      const msg = err instanceof Error ? err.message : String(err)\n      logger.warn(`[DrugReferenceController] search failed: ${msg}`)\n      return response.badRequest({ error: msg })\n    }\n  }\n\n  /**\n   * GET /api/drug-reference/status\n   * Returns the live ingest status DTO.\n   */\n  async status({ response }: HttpContext) {\n    try {\n      const status = await this.service.getIngestStatus()\n      return status\n    } catch (err) {\n      const msg = err instanceof Error ? err.message : String(err)\n      logger.error(`[DrugReferenceController] status failed: ${msg}`)\n      return response.internalServerError({ error: 'Could not read ingest status' })\n    }\n  }\n\n  /**","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/controllers/drug_reference_controller.ts#L97-L133","documentation":"DrugReferenceController.search wraps its search pipeline (query parsing + upstream drug reference search) in a try/catch and converts any exception into a 400 Bad Request carrying the raw error message. Because the catch is generic, the 400 does not necessarily mean a client mistake — infrastructure failures are also reported as 400. The real cause is always in the preceding [DrugReferenceController] search failed warn log.","triggerScenarios":"GET /api/drug-reference/search with a malformed or empty q, an unsupported page/pageSize value, or when the underlying reference service throws (empty index, ingest never ran, upstream provider error). Called by the results endpoint, so navigating to search results with a bad or stale query triggers it.","commonSituations":"Searching before the drug-reference ingest has completed (empty index); passing non-numeric pagination params; reference provider API key missing; stale frontend sending a query format from an older API version.","solutions":["Check server logs for the [DrugReferenceController] search failed warn with the actual message","If the error mentions index/ingest, hit GET /api/drug-reference/status and run the ingest/download flow before searching","Sanitize the q parameter and pagination values on the client","Verify reference provider credentials/env config"],"exampleFix":"// before\nfetch('/api/drug-reference/search?q=')\n// after\nif (!query.trim()) throw new Error('empty query')\nfetch(`/api/drug-reference/search?q=${encodeURIComponent(query.trim())}&page=1&pageSize=20`)","handlingStrategy":"validation","validationCode":"const q = query.trim()\nif (q.length < 2) return showHint('Type at least 2 characters')\nconst page = Number.isInteger(pageNum) && pageNum > 0 ? pageNum : 1\nconst res = await fetch(`/api/drug-reference/search?q=${encodeURIComponent(q)}&page=${page}`)\nif (!res.ok) return handleSearchError(await res.json())","typeGuard":"const isSearchErrorResponse = (b: unknown): b is { error: string } =>\n  typeof b === 'object' && b !== null && typeof (b as any).error === 'string'\n\nasync function safeSearch(q: string) {\n  const res = await fetch(`/api/drug-reference/search?q=${encodeURIComponent(q)}`)\n  const body = await res.json().catch(() => null)\n  if (!res.ok) return { results: [], error: isSearchErrorResponse(body) ? body.error : `HTTP ${res.status}` }\n  return body as { results: unknown[] }\n}","tryCatchPattern":"try {\n  return await drugRef.search(q, page)\n} catch (err) {\n  // check ingest status before showing a hard error\n  const status = await drugRef.status()\n  if (!status.ingested) return { results: [], hint: 'Reference data not ingested yet' }\n  throw err\n}","preventionTips":["Debounce search and require a minimum query length before calling","Check /api/drug-reference/status after deploy and run ingest before enabling search UI","Validate pagination params client-side (positive integers, bounded pageSize)"],"tags":["search","validation","upstream","bad-request"],"backgroundTag":"search-query-validation-failed","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}