feat(platform-api): per-op upstream routing via upstreamDefinitions#2108
feat(platform-api): per-op upstream routing via upstreamDefinitions#2108mehara-rothila wants to merge 2 commits into
Conversation
Add control-plane support for resource-level (per-operation) upstreams. An operation references a named entry in an API-level upstreamDefinitions pool (ref-only); the pool and per-op refs are persisted in the API configuration and emitted in the deployment YAML the gateway resolves. - openapi: ReusableUpstream/UpstreamTimeout pool and ref-only OperationUpstream - model/dto/utils: carry the pool and per-op upstream through create, import, merge and deployment-YAML emission - service: validate refs resolve to unique definitions and reject the same shapes the gateway rejects (empty or absent per-op ref, empty operation upstream wrapper, invalid HTTP method, definition name that breaks the name contract or exceeds 100 characters, definition url that is not host-only http/https, weight outside 0..100, non-positive timeout) - handler: map invalid upstream definitions and operations to HTTP 400 - tests: unit and real-SQLite integration coverage for each rule
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR introduces support for named, reusable upstream definitions and per-operation upstream overrides to the API platform. APIs can now define a pool of reusable upstream targets and reference them by name at both the API level and within individual operations. The changes add upstream definition and per-operation override fields across request/response models, implement validation to ensure references resolve and conform to naming and URL constraints, extend mapping logic to preserve upstreams through YAML and database round-trips, and include comprehensive testing to verify both preservation and validation behavior. Sequence DiagramsequenceDiagram
participant Client
participant CreateAPI Handler
participant APIService
participant Validator
participant APIUtil
participant Repository
Client->>CreateAPI Handler: POST create request with UpstreamDefinitions + OperationUpstream
CreateAPI Handler->>APIService: CreateAPI(request)
APIService->>Validator: validateOperationMethods(operations)
APIService->>Validator: validateUpstreamRefs(definitions, operations)
Validator-->>APIService: ✓ validation passed
APIService->>APIUtil: ModelToRESTAPI(modelAPI)
APIUtil-->>APIService: restAPI with UpstreamDefinitions
APIService->>Repository: Create(restAPI)
Repository-->>APIService: created API
APIService-->>CreateAPI Handler: success
CreateAPI Handler-->>Client: HTTP 200 + created API
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
…tion with the gateway Restrict an upstreamDefinition connect timeout to ms, s, m, or h units. time.ParseDuration alone also accepts ns/us units and compound values like 1h30m that the gateway rejects at deploy, so without this an API saves at the control plane and then fails to deploy. This matches the connect pattern already published in the OpenAPI spec. Set the upstream weight schema minimum to 0 to match the validator and the gateway, which both accept a 0..100 range.
cc3b23a to
ba21576
Compare
Summary
Adds control-plane (platform-api) support for a per-operation
upstreamoverride on REST API operations: an operation can route itsmainand/orsandboxtraffic to a different backend than the API-level upstream. Per-op targets are ref-only: each references a named entry inupstreamDefinitionsrather than carrying an inline URL. The platform-api accepts, validates, persists, and emits this configuration in the deployment YAML the gateway resolves. Operations without a per-op upstream fall back to the API-level upstream, exactly as before.Backends are declared once in an API-level
upstreamDefinitionspool and referenced by name from both API-level and operation-level upstreams. The per-op upstream and the pool live inside the existing API configuration blob, so there is no database migration.Why
Real-world APIs often need individual operations to reach different backend services. Today that needs path-based workarounds or splitting one API into several. This lets the API definition declare per-operation routing directly, while backend URLs stay defined once in
upstreamDefinitionsand are referenced by name. Because the platform-api is the publisher-facing front door, it validates the configuration up front and fails fast with HTTP 400, so a config the control plane accepts is one the gateway will accept on deploy instead of surfacing as a later deployment failure.Design decisions
Ref-only at the operation level.
operations[].upstream.main/.sandboxcarry only arefto a namedupstreamDefinition, with no inlineurland no per-target timeout. Backends are declared once inupstreamDefinitions; the connect timeout lives on the definition. The wrapper and the leaf are both locked withadditionalProperties: false, and at least one ofmain/sandboxis required. API-levelupstreamstill accepts eitherurlorref, unchanged.Backends declared once in a named pool.
upstreamDefinitionsis an array of named entries (name, optionalbasePath, optional connecttimeout, and one or more weightedupstreams). Both API-level and operation-levelrefs resolve against this pool, so a backend URL is defined in exactly one place and reused by name.Validate at the front door, aligned with the gateway. The platform-api now rejects the same shapes the gateway rejects, so the two layers cannot disagree: a
refmust resolve to a unique, well-formed definition; and an empty or absent per-opref, an emptyupstream: {}wrapper, anupstreamDefinitionname that breaks the name contract (^[a-zA-Z0-9\-_]+$) or exceeds 100 characters, anupstreamDefinitionurl that is not host-only http/https, a weight outside 0..100, a connect timeout that is non-positive or not expressed in ms, s, m, or h units, and an invalid HTTP method are all rejected. Invalid upstream-definition and operation errors map to HTTP 400, and the same checks run on both the create and the update path.No schema migration. The per-operation upstream and the
upstreamDefinitionspool are stored inside the existing API configuration JSON, so persistence is purely additive with no database migration.What changed (platform-api / control-plane layer)
resources/openapi.yaml): aReusableUpstream+UpstreamTimeoutpool on the API, and a ref-onlyOperationUpstream/OperationUpstreamTargeton the operation. The operation wrapper and target are locked withadditionalProperties: false; the wrapper requires at least one ofmain/sandbox; therefis constrained to^[a-zA-Z0-9\-_]+$, max 100 characters. Go types regenerated (api/generated.go).BuildAPIDeploymentYAMLemitsspec.upstreamDefinitionsandoperations[].upstream.main/.sandbox.refin the gateway's shape, with the operation upstream emitted ref-only.internal/service/api.go): ref resolution plus all the gateway-aligned validation rules above, applied on both the create and the update path.internal/handler/api.go): map invalid upstream-definition and operation errors to HTTP 400 on both create and update.Test coverage
internal/service,internal/utils, andinternal/repository: ref resolution; every validation rule (name pattern and length, host-only url with scheme and host checks, weight 0..100, connect timeout positive and restricted to ms, s, m, or h units, required and well-formed per-op ref, non-empty operation upstream wrapper, HTTP method enum); and DTO/model conversion across create, import, merge, and deployment-YAML emission (operation upstream emitted ref-only).upstreamDefinitionspool and the per-operation upstream, and a deployment-YAML contract test asserting the emittedoperations[].upstream.main.refandspec.upstreamDefinitionsmatch the gateway shape (the per-op ref is attached to the right operation and does not leak to others).Example
API configuration sent to the platform-api. The per-operation
upstreamis ref-only; backends are declared once inupstreamDefinitionsand referenced by name:Related PRs
refat deploy time and builds the Envoy clusters and routes.