Crosstalk-Solutions/project-nomad · error

${msg}

Error message

${msg}

What it means

This is a catch-all error handler in ConditionsController.drugsApi that surfaces any thrown error from the external drugs API call as a 400 Bad Request with the raw error message. It is not a specific library error; whatever upstream failure occurred (network, timeout, invalid API key, malformed query) gets flattened into this response. The `message="${msg}"` form indicates the log line captures the interpolated upstream message.

Source

Thrown at admin/app/controllers/conditions_controller.ts:111

      if (params.slug) {
        const result = await this.service.drugsForSlug(params.slug, params.limit, filterOpts)
        if (!result) {
          return response.notFound({ error: 'Condition not found' })
        }
        return remediesOn ? result : { ...result, remedies: [] }
      }

      if (params.q) {
        const result = await this.service.drugsForFreeText(params.q, params.limit, filterOpts)
        return remediesOn ? result : { ...result, remedies: [] }
      }

      return response.badRequest({ error: 'Provide a slug or q query parameter' })
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err)
      logger.warn(`[ConditionsController] drugsApi failed: ${msg}`)
      return response.badRequest({ error: msg })
    }
  }
}

View on GitHub (pinned to 0bd1c6f4f9)

Solutions

  1. Check the server logs for the preceding [ConditionsController] drugsApi failed line to see the real upstream message
  2. Verify the request includes exactly one of slug or q query parameter
  3. Confirm the drugs API credentials/base URL env vars are set and valid
  4. If upstream is down/rate-limited, retry later or add caching/fallback for drug lookups

Example fix

// before
const res = await fetch(`/api/conditions/drugs`)
// after
const res = await fetch(`/api/conditions/drugs?q=${encodeURIComponent(query)}`)
if (!res.ok) console.error(await res.json())
Defensive patterns

Strategy: validation

Validate before calling

const params = new URLSearchParams()
if (slug) params.set('slug', slug)
else if (q?.trim()) params.set('q', q.trim())
if (![...params.keys()].length) throw new Error('slug or q required')
const res = await fetch(`/api/conditions/drugs?${params}`)
if (!res.ok) { /* surface res.error to UI, fall back to cached list */ }

Type guard

const isApiError = (b: unknown): b is { error: string } =>
  typeof b === 'object' && b !== null && typeof (b as any).error === 'string''

async function parseError(res: Response): Promise<string> {
  const body = await res.json().catch(() => null)
  return isApiError(body) ? body.error : `HTTP ${res.status}`
}

Try / catch

try {
  return await conditionsApi.drugs({ q })
} catch (err) {
  // HTTP 400 with { error }; show message, fall back to cached results
  return cachedDrugs
}

Prevention

When it happens

Trigger: Calling GET /api/conditions/drugs without a slug or q parameter returns a deterministic 400; alternatively the upstream drugs API request fails (DNS failure, 401/403 from missing API key, timeout, or non-JSON response) and the caught error message is relayed here.

Common situations: Missing or expired DRUGS_API_KEY env var in the admin service; upstream drugs endpoint changed or is rate-limiting; developers testing locally without network access to the third-party API; frontend sending both slug and q or neither.

Related errors


AI-assisted analysis of Crosstalk-Solutions/project-nomad@0bd1c6f4f9 (2026-08-27). Data as JSON: /api/errors/e146859ef14fc3d1. Report an issue: GitHub.