Skip to content

Add plugin route overrides, and drop the platform pdk re-exports - #2961

Open
malinthaprasan wants to merge 7 commits into
wso2:mainfrom
malinthaprasan:platform-modze
Open

Add plugin route overrides, and drop the platform pdk re-exports#2961
malinthaprasan wants to merge 7 commits into
wso2:mainfrom
malinthaprasan:platform-modze

Conversation

@malinthaprasan

@malinthaprasan malinthaprasan commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Purpose

A wrapper embedding platform-api as a library can add routes and chain middleware, but cannot change what an existing core endpoint returns. The only options are forking the handler or shadowing the route, and ServeMux panics on a duplicate pattern.

#2831

Goals

Let a plugin decorate one existing core route: its Wrap receives the original core handler as next.

Approach

Six commits, each buildable on its own:

  1. internal/router — a Router interface (*http.ServeMux satisfies it) and a Recorder that records registrations instead of serving them. Core handlers' RegisterRoutes now take router.Router; the pdk.Plugin / plugin.Plugin contracts keep *http.ServeMux, so no plugin or wrapper changes.
  2. pdk/override.goRouteOverride, RouteOverrideProvider, and the capture helpers Invoke / WriteCaptured.
  3. Server wiring — core routes are recorded, plugins declare their claims, then installCoreRoutes validates them and installs each recorded route on the mux, wrapped where claimed.
  4. Façade removalplatform re-exports nothing from pdk. See Breaking changes in the PDK below.
  5. RouteDecorator — a defined type of func(http.Handler) http.Handler to provide the handler funtion to override the default handler.

Constraints:

  • An override cannot change the route's scopes-
  • A plugin route colliding with a core pattern gives a startup error

Breaking changes in the PDK

platform no longer re-exports anything from pdk. It exports only New, App, Run, and the With* options; every contract type has one name, in pdk.

Seven symbols are removed, all previously released on main:

Removed Replace with
platform.Plugin pdk.Plugin
platform.Deps pdk.Deps
platform.Middleware pdk.Middleware
platform.ChainPosition pdk.ChainPosition
platform.PositionedMiddleware pdk.PositionedMiddleware
platform.BeforePlatformChain pdk.BeforePlatformChain
platform.AfterPlatformChain pdk.AfterPlatformChain

User stories

  • As a wrapper author, I add a field to an existing endpoint's response without forking its handler or duplicating its route.
  • As a reviewer, every decorated route is declared explicitly and logged at startup, and a stale pattern stops the server instead of quietly doing nothing.

Documentation

N/A — extension surface only, no user-facing API change. The contract is documented in the doc comments on pdk.RouteOverride, Invoke, and WriteCaptured.

Automation tests

  • Unit tests — 32 new. internal/router 100.0% statement coverage, pdk 96.8%, installCoreRoutes 100.0%. Cover ordering and deferred errors in the recorder, capture/write semantics, and every startup-failure path (unknown pattern, duplicate claims, nil Wrap, empty pattern, plugin/core collision). Two assert the header-replacement fix on Header().Values() rather than Get()Get returns only the first value, so it cannot see a duplicate. Two more pin RouteDecorator as a type distinct from Middleware, while confirming a plain func literal still assigns to Wrap.
  • Integration tests — none new. Manually verified against a running server with a wrapper declaring two overrides: enriched response on the overridden route, list route unchanged, core's 404 passed through byte for byte, 405 preserved, and a bad pattern refusing startup.

Security checks

  • Followed secure coding standards? yes
  • Ran FindSecurityBugs plugin? N/A — Go module, no Java source; go vet clean.
  • Confirmed no keys/passwords/tokens/secrets committed? yes

An override cannot widen access. Required scopes are keyed by OpenAPI path/method and are untouched, so a decorated route keeps the requirement it had. A decorator cannot re-route a request either — the handler for the pattern is already selected before Wrap runs, so rewriting the path changes nothing about what executes. The contract documents that a decorator must read the organization from request context, never from request input. Every malformed or unmatched override aborts startup rather than being skipped.

Samples

import "github.com/wso2/api-platform/platform-api/pdk"

// Declare the claim. Pattern is matched as an exact string against what core
// registers — a wrong version or wildcard name fails startup.
func (p *MyPlugin) RouteOverrides() []pdk.RouteOverride {
    return []pdk.RouteOverride{
        {Pattern: "GET /api/v0.9/gateways/{gatewayId}", Wrap: p.enrichGateway()},
    }
}

// next is the original core handler, registered under the same pattern — so the
// mux has already resolved r.PathValue("gatewayId") by the time this runs.
func (p *MyPlugin) enrichGateway() pdk.RouteDecorator {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            res := pdk.Invoke(next, r) // run core, capture its response

            if res.Status != http.StatusOK {
                pdk.WriteCaptured(w, res) // pass errors through untouched
                return
            }

            var base platformapi.GatewayResponse
            if err := json.Unmarshal(res.Body, &base); err != nil {
                trackingID := util.NewToken()
                p.deps.Logger.Error("gateway decode failed",
                    "trackingId", trackingID, "error", err)
                httputil.WriteJSON(w, http.StatusInternalServerError, map[string]any{
                    "error": "internal_error", "tracking_id": trackingID,
                })
                return
            }

            httputil.WriteJSON(w, http.StatusOK,
                toCloudGateway(base, p.environmentFor(base.Id)))
        })
    }
}

Related PRs

None.

Test environment

Go 1.26.5, macOS. go build ./..., go build ./cmd/main.go, go vet, and the test suite all clean; each of the first three commits was checked out and built independently. The reference wrapper (apip-cloud-platform-api) was also smoke-built against this branch through its .local symlinks — go build, go vet, go test clean — since nothing in this repo compiles against pdk from outside the module.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds deferred core route registration through a shared router interface. It adds plugin route overrides, response capture helpers, startup validation, external-plugin forwarding, and updates handler registration signatures.

Changes

Core routing and plugin route overrides

Layer / File(s) Summary
Router recorder and handler registration
platform-api/internal/router/*, platform-api/internal/handler/*
Core handlers register routes through router.Router. Recorder preserves routes, detects invalid registrations, and defers errors.
Route override and response contracts
platform-api/pdk/override.go, platform-api/pdk/override_test.go, platform-api/internal/plugin/plugin.go, platform-api/platform/platform.go
The PDK defines route override and captured-response contracts. The platform documentation and exported aliases are updated.
Plugin override collection and validation
platform-api/internal/server/plugins.go, platform-api/internal/server/external_plugin.go
Plugin initialization collects route override claims, validates them, rejects duplicates, and forwards external-plugin overrides.
Deferred installation and override execution
platform-api/internal/server/server.go, platform-api/internal/server/overrides.go, platform-api/internal/server/overrides_test.go
Server startup records core routes, applies validated decorators, handles registration failures, and tests route behavior and startup validation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: krishanx92, anugayan, renuka-fernando

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes both primary changes: plugin route overrides and removal of platform PDK re-exports.
Description check ✅ Passed The description covers the required purpose, goals, approach, user stories, documentation, tests, security checks, samples, related PRs, and test environment.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@malinthaprasan malinthaprasan changed the title WIP Allow plugins to decorate an existing core route (route overrides) Jul 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@platform-api/pdk/override.go`:
- Around line 139-160: Update WriteCaptured to clear each destination header key
before adding its captured values, ensuring captured headers replace existing
upstream values rather than append duplicates. Preserve the existing
Content-Length exclusion and status/body handling.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 047bb071-e076-4a83-819e-fe588316c3f2

📥 Commits

Reviewing files that changed from the base of the PR and between f4de02b and 7f1fda0.

📒 Files selected for processing (31)
  • platform-api/internal/handler/api.go
  • platform-api/internal/handler/api_deployment.go
  • platform-api/internal/handler/api_key.go
  • platform-api/internal/handler/apikey_user.go
  • platform-api/internal/handler/application.go
  • platform-api/internal/handler/auth_login.go
  • platform-api/internal/handler/gateway.go
  • platform-api/internal/handler/gateway_internal.go
  • platform-api/internal/handler/llm.go
  • platform-api/internal/handler/llm_apikey.go
  • platform-api/internal/handler/llm_deployment.go
  • platform-api/internal/handler/llm_proxy_apikey.go
  • platform-api/internal/handler/mcp.go
  • platform-api/internal/handler/mcp_deployment.go
  • platform-api/internal/handler/organization.go
  • platform-api/internal/handler/project.go
  • platform-api/internal/handler/secret.go
  • platform-api/internal/handler/subscription_handler.go
  • platform-api/internal/handler/subscription_plan_handler.go
  • platform-api/internal/handler/websocket.go
  • platform-api/internal/plugin/plugin.go
  • platform-api/internal/router/router.go
  • platform-api/internal/router/router_test.go
  • platform-api/internal/server/external_plugin.go
  • platform-api/internal/server/overrides.go
  • platform-api/internal/server/overrides_test.go
  • platform-api/internal/server/plugins.go
  • platform-api/internal/server/server.go
  • platform-api/pdk/override.go
  • platform-api/pdk/override_test.go
  • platform-api/platform/override.go

Comment thread platform-api/pdk/override.go
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 7, 2026
WriteCaptured merged the captured headers into the real ResponseWriter
with Header.Add, which appends to any value already present under the
same key. A decorator that set a header before passing core's response
through therefore emitted both values -- two Content-Type lines for a
field RFC 9110 defines as a singleton. The existing test could not see
it, because Header.Get returns only the first value.

Delete each captured key before adding its values, so a captured header
replaces what the decorator set instead of stacking on top of it.
Set-Cookie is excepted: multiple cookies are legitimate, and a blanket
delete would discard the decorator's own.

Headers under keys core never touched are untouched, since Del on an
absent key is a no-op.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 7, 2026
@malinthaprasan malinthaprasan changed the title Allow plugins to decorate an existing core route (route overrides) Add plugin route overrides, and drop the platform façade's pdk re-exports Aug 7, 2026
@malinthaprasan malinthaprasan changed the title Add plugin route overrides, and drop the platform façade's pdk re-exports Add plugin route overrides, and drop the platform pdk re-exports Aug 7, 2026
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.

1 participant