fix(error): map customer-fixable upstream config to 400, not 500 (#367) - #500
Conversation
BridgeError::Config mapped every config error to HTTP 500, so a customer-fixable misconfig (empty provider_key secret, missing api_base, missing model_name, bad request shape) surfaced as a server fault — SDKs retried it and monitoring alerted, when the fix is on the operator side. Add a distinct BridgeError::InvalidUpstreamConfig variant → 400 / error.type "invalid_request_error", non-retryable. Convert the customer-fixable construction sites across all five provider bridges: empty secret, missing model_name, the #365 api_base guard, invalid api-key header chars, and split_system request-shape validation. Errors we cause ourselves stay BridgeError::Config → 500: body serialization, our generated request_id's header encoding, internal request construction, and runtime credential/token minting (which can fail transiently). Azure URL-shape validation also stays 500 for now. Tests: gateway status/type mapping; is_retryable; each provider's converted-error assertions now expect InvalidUpstreamConfig; plus an e2e (invalid-upstream-config-400) driving the issue's own scenario — an openrouter PK admitted without api_base returns 400, not 500.
|
Warning Review limit reached
More reviews will be available in 4 minutes and 11 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughThis PR introduces a new ChangesConfiguration error classification refactoring
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/aisix-provider-anthropic/src/bridge.rs`:
- Around line 151-153: The api_key(ctx) helper currently only checks
k.is_empty() and returns potentially-invalid non-empty secrets to
chat()/chat_stream() which set the "x-api-key" header; update api_key(ctx) to
validate provider_key.secret contents (trim whitespace, reject whitespace-only,
and reject control/invalid bytes or non-printable characters) and return
Err(BridgeError::InvalidUpstreamConfig(...)) for invalid values referencing
provider_key.secret, so chat() and chat_stream() always receive a validated &str
for the header; keep the BridgeError::InvalidUpstreamConfig variant for these
cases and ensure error messages identify the invalid provider_key.secret.
In `@crates/aisix-provider-bedrock/src/bridge.rs`:
- Around line 474-475: The Bedrock credential validation currently returns
BridgeError::Config for customer-fixable issues; update BedrockSecret::parse to
return BridgeError::InvalidUpstreamConfig (with descriptive messages) instead of
BridgeError::Config for empty/invalid provider_key.secret and similar validation
failures, and change the empty-region check in build_client to return
BridgeError::InvalidUpstreamConfig when secret.region is empty; ensure the error
strings clearly reference the invalid field (e.g. "provider_key.secret
missing/invalid" and "secret.region missing") so they align with the new 400
contract and match the existing use of BridgeError::InvalidUpstreamConfig (as
used for missing model_name).
In `@crates/aisix-provider-openai/src/bridge.rs`:
- Around line 337-339: The malformed Bearer/token validation currently done in
build_request_headers (using HeaderValue::from_str ->
BridgeError::InvalidUpstreamConfig) is only applied to chat and chat_stream;
update embed, complete, and generate_image to perform the same validation so
malformed provider_key.secret surfaces as InvalidUpstreamConfig instead of
Transport errors—either call build_request_headers from those functions (embed,
complete, generate_image) or replicate the
HeaderValue::from_str(...).map_err(|e| BridgeError::InvalidUpstreamConfig(...))?
logic before calling .header(header::AUTHORIZATION, ...) so the Authorization
header is validated consistently.
In `@crates/aisix-provider-vertex/src/bridge.rs`:
- Around line 2627-2630: The test asserts InvalidUpstreamConfig for missing
model_name but resolve_api_base() still returns BridgeError::Config for invalid
provider_key.api_base inputs, causing those errors to be treated as 500; update
resolve_api_base() to return BridgeError::InvalidUpstreamConfig (with a
descriptive message) for each branch that currently returns BridgeError::Config
on invalid api_base (the same branches referenced in Lines 187-232), so
upstream-config validation failures consistently use InvalidUpstreamConfig;
search for resolve_api_base, replace the BridgeError::Config returns for
api_base validation with BridgeError::InvalidUpstreamConfig while preserving the
original error text.
In `@tests/e2e/src/cases/invalid-upstream-config-400-e2e.test.ts`:
- Line 68: Rename the test title string used in the test invocation that
currently reads "empty provider_key secret surfaces as a 400, not a 500" to
accurately describe the scenario being validated (missing api_base with a
non-empty secret); update the test(...) call so its first argument reflects
something like "missing api_base with non-empty provider secret surfaces as a
400, not a 500" to avoid confusion during triage (locate the test by the
test(...) invocation in invalid-upstream-config-400-e2e.test.ts).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7d194f81-da6b-43a2-9854-6861f0d7a203
📒 Files selected for processing (9)
crates/aisix-gateway/src/bridge.rscrates/aisix-provider-anthropic/src/bridge.rscrates/aisix-provider-azure-openai/src/bridge.rscrates/aisix-provider-bedrock/src/bridge.rscrates/aisix-provider-openai/src/bridge.rscrates/aisix-provider-vertex/src/bridge.rscrates/aisix-proxy/src/chat.rscrates/aisix-proxy/src/routing.rstests/e2e/src/cases/invalid-upstream-config-400-e2e.test.ts
…thropic key (#367) Address CodeRabbit consistency review on the static-config class: - bedrock: empty/malformed provider_key.secret JSON and empty secret.region now map to InvalidUpstreamConfig (400), matching the empty-secret/model_name cases. - vertex: service_account_json shape validation (type, private_key PEM, client_email, token_uri) → InvalidUpstreamConfig. Runtime token-mint HTTP failures (e.g. 400 invalid_grant) and JWT signing stay Config (500) — those can fail transiently. - anthropic: a non-empty secret that isn't a valid x-api-key header value (control bytes) is now rejected up front as InvalidUpstreamConfig, mirroring the openai/azure header checks, instead of failing later as an opaque reqwest builder error. Tests updated to assert the new variant for the converted sites; the runtime token-endpoint-error test still asserts Config.
…ges (#367) Second CodeRabbit pass — make the whole customer-fixable-config class consistent so api_base errors don't split across 400/500: - vertex: api_base shape validation (scheme/userinfo/query/fragment/path) → InvalidUpstreamConfig. - azure: api_base shape validation (userinfo/query/fragment), bare resource/deployment token validation, "no api_base", and malformed AAD-credentials JSON parse → InvalidUpstreamConfig. - openai: validate the secret as an Authorization header value in api_key() so the inline Bearer-header endpoints (responses/embeddings/ etc.) get the same 400 treatment, not just build_request_headers. Runtime/transient and internal errors still stay Config (500): body serialization, our generated request_id header, AAD/STS token minting + JWT signing, and the internal "exactly one auth" invariant. Tests for the converted sites updated to assert InvalidUpstreamConfig; the e2e title now matches its actual (missing-api_base) scenario.
Closes #367.
Problem
BridgeError::Configmapped every config error to HTTP500. But many are customer-fixable upstream-config issues — emptyprovider_key.secret, missingapi_base(the #365 guard), missingmodel_name, malformed request shape. A500tells the SDK to retry and monitoring to alert on a server fault, when the root cause is on the operator's side.Fix
A distinct
BridgeError::InvalidUpstreamConfigvariant →400/error.type: "invalid_request_error", and non-retryable (a 4xx; retry/failover won't help). The customer-fixable construction sites across all five provider bridges (anthropic, openai, azure-openai, bedrock, vertex) are converted:provider_key.secretmodel_nameapi_basefamily-bridge guardsplit_systemrequest-shape validationErrors we cause stay
BridgeError::Config→500(correct): body serialization, our generatedrequest_idheader encoding, internal request construction, and runtime credential/token minting (which can fail transiently). Azure URL-shape validation also stays500for now — a follow-up can move it to the new variant if desired.Behavior change
Affected upstream-config errors now return
400instead of500. The error envelope carriestype: "invalid_request_error". No change to successful requests or to genuine upstream/transport failures.Tests
InvalidUpstreamConfig→ 400 /invalid_request_error;Configstays 500.is_retryable(InvalidUpstreamConfig)is false.InvalidUpstreamConfig(these fail on the oldConfigmapping → fail-before/pass-after).invalid-upstream-config-400: the issue's own scenario — anopenrouterPK admitted withoutapi_basereturns400, not500. Verified against the live compose stack.Summary by CodeRabbit
Bug Fixes
Tests