Skip to content

fix(api): POST /v1/search names unknown providers instead of opaque 400 (#10849) - #10919

Merged
diegosouzapw merged 1 commit into
release/v3.8.50from
fix/10849-search-provider-400
Aug 21, 2026
Merged

diegosouzapw merged 1 commit into
release/v3.8.50from
fix/10849-search-provider-400

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Closes #10849

Root cause

v1SearchSchema.provider (src/shared/validation/schemas/apiV1.ts) was a hard-coded z.enum([...17 catalog ids]). Any provider id outside that literal list (e.g. grok, or short aliases like brave/serper) failed Zod validation inside validateBody() before the route's own, better-designed check (resolveSearchProvider() in open-sse/config/searchRegistry.ts) ever ran. validateBody() also always hard-codes message: "Invalid request" on failure, with the real Zod issue only in details[] — but route.ts was building the 400 response from validation.error.message alone, dropping details and hiding which field/value actually failed.

Downstream, resolveSearchProvider() already returns null for unknown ids and the route already replies Unknown search provider: <id> — but that branch was unreachable for anything outside the enum. Additionally, SEARCH_PROVIDER_ALIASES only mapped jina-ai/jina → jina-search; no short aliases existed for the other providers (brave → brave-search, etc.), so even after relaxing the enum those ids would still fail with an unnamed error.

Fix

  • src/shared/validation/schemas/apiV1.ts: v1SearchSchema.provider is now z.string().min(1).optional() (documented with a comment listing the known catalog ids) instead of a hard-coded enum, so resolveSearchProvider() is the single runtime source of truth for provider validity.
  • open-sse/config/searchRegistry.ts: extended SEARCH_PROVIDER_ALIASES with short-form aliases mirroring the existing jina/jina-ai pattern — brave, serper, perplexity, exa, tavily, google-pse, linkup, ollama, searchapi, youcom, searxng, zai, duckduckgo.
  • src/shared/validation/helpers.ts: added formatValidationMessage(), which builds a "<field>: <message>" string from the first Zod issue instead of the generic "Invalid request", for routes (like /v1/search) that reply with a single message string via errorResponse().
  • src/app/api/v1/search/route.ts: uses formatValidationMessage() when schema validation fails, so a genuinely bad field (e.g. search_type: "bogus") also gets a named message instead of an opaque one.
  • Aligned two existing tests (tests/unit/firecrawl-search.test.ts, tests/unit/search-registry.test.ts) that encoded the old (buggy) schema-level-rejection contract — they now assert the corrected contract: the schema accepts any non-empty string, and resolveSearchProvider() is the runtime gate for unknown/legacy ids.

Regression test (TDD)

tests/unit/search-provider-opaque-400-10849.test.ts — reproduces the bug against the unfixed code (confirmed RED: both provider: "grok" and provider: "brave" returned the opaque "Invalid request" message), then confirms GREEN after the fix:

  • unknown provider id → Unknown search provider: grok
  • short alias brave → resolves like the existing jina aliases (no opaque 400)
  • a genuinely invalid field (search_type: "bogus") → field-named message, not opaque

Run: node --import tsx/esm --test tests/unit/search-provider-opaque-400-10849.test.ts → 3/3 passing.

Validation

  • npm run typecheck:core — clean
  • node scripts/check/check-complexity.mjs / check-cognitive-complexity.mjs / check-file-size.mjs — all OK (no regression vs baseline)
  • node scripts/check/check-changelog-integrity.mjs — OK
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files> — clean
  • Sibling test files exercising the touched schema/route/registry: tests/unit/search-route.test.ts, tests/unit/search-registry.test.ts, tests/unit/firecrawl-search.test.ts, tests/unit/jina-complete-provider.test.ts, tests/unit/9201-search-proxy-bypass.test.ts — all passing after the fix (2 pre-existing schema-level assertions updated to the corrected contract, see above)
  • tests/unit/hard-session-lease-bypass-inventory.test.ts fails on this branch, but the failure is unrelated pre-existing drift: git diff origin/release/v3.8.50 -- tests/unit/hard-session-lease-bypass-inventory.test.ts is empty (the test file is untouched by this PR) and the src/app/api/v1/search/route.ts call-site count in the failing assertion is identical (2) between actual and expected — the mismatch is entirely in unrelated routes (classify, images/edits, segment, videos/generations, geminiWeb).

⚠️ base-red inherited: #9985

…00 (#10849)

v1SearchSchema.provider was a hard-coded z.enum that rejected any id outside
its list before the route's own resolveSearchProvider() check ever ran,
so unknown/short-alias provider ids (grok, brave, serper, ...) always
surfaced a generic "Invalid request" instead of the informative
"Unknown search provider: <id>" message. Relax the schema to a free-form
string and let resolveSearchProvider() own runtime validation (as it
already did for ids that passed the enum). Also extend
SEARCH_PROVIDER_ALIASES with short-form aliases mirroring the existing
jina/jina-ai pattern (brave, serper, perplexity, exa, tavily, google-pse,
linkup, ollama, searchapi, youcom, searxng, zai, duckduckgo), and surface
the first Zod validation issue's field name instead of the generic
message for other still-invalid fields (e.g. search_type).
@diegosouzapw
diegosouzapw merged commit f66c986 into release/v3.8.50 Aug 21, 2026
11 checks passed
@diegosouzapw
diegosouzapw deleted the fix/10849-search-provider-400 branch August 21, 2026 15:33
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…rch-provider-400

fix(api): POST /v1/search names unknown providers instead of opaque 400 (diegosouzapw#10849)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(api): unknown provider ids return opaque Invalid request instead of Unknown search provider

2 participants