From 104c178f814a73e8e1a0ae3a71bef0c4fd9a8e6f Mon Sep 17 00:00:00 2001 From: Malintha Amarasinghe Date: Wed, 12 Aug 2026 17:05:11 +1000 Subject: [PATCH 1/2] Report every unmatched route override, not just the first --- platform-api/internal/server/overrides.go | 12 ++++--- .../internal/server/overrides_test.go | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/platform-api/internal/server/overrides.go b/platform-api/internal/server/overrides.go index 62cc2f7bc3..5f3806b4aa 100644 --- a/platform-api/internal/server/overrides.go +++ b/platform-api/internal/server/overrides.go @@ -22,6 +22,7 @@ import ( "log/slog" "net/http" "sort" + "strings" "github.com/wso2/api-platform/platform-api/internal/router" ) @@ -56,10 +57,13 @@ func installCoreRoutes( } if len(unknown) > 0 { sort.Strings(unknown) - p := unknown[0] - return fmt.Errorf("plugin %q declared a route override for %q, which is not a core route "+ - "(patterns are matched exactly, including method and path; %d override(s) unmatched)", - overrides[p].plugin, p, len(unknown)) + claims := make([]string, 0, len(unknown)) + for _, pattern := range unknown { + claims = append(claims, fmt.Sprintf("plugin %q -> %q", overrides[pattern].plugin, pattern)) + } + return fmt.Errorf("%d route override(s) name a pattern that is not a core route "+ + "(patterns are matched exactly, including method and path): %s", + len(unknown), strings.Join(claims, "; ")) } // A duplicate pattern can now only come from a plugin route registered on diff --git a/platform-api/internal/server/overrides_test.go b/platform-api/internal/server/overrides_test.go index f3900fafb0..a7094b073b 100644 --- a/platform-api/internal/server/overrides_test.go +++ b/platform-api/internal/server/overrides_test.go @@ -301,6 +301,42 @@ func TestInstallCoreRoutes_UnknownPatternAbortsStartup(t *testing.T) { } } +// One unmatched pattern per error would mean fixing a typo, restarting, and +// finding the next one — so the error names every unmatched override at once. +func TestInstallCoreRoutes_UnknownPatternErrorNamesEveryOverride(t *testing.T) { + patterns := []string{ + "GET /api/v1/gateways/{gatewayId}", + "GET /api/v0.9/gateways/{id}", + "POST /api/v0.9/gateways", + } + cloud := &overridePlugin{ + fakePlugin: &fakePlugin{name: "cloud", spec: specWithScopes}, + overrides: []pdk.RouteOverride{ + {Pattern: patterns[0], Wrap: passthrough}, + {Pattern: patterns[1], Wrap: passthrough}, + }, + } + audit := &overridePlugin{ + fakePlugin: &fakePlugin{name: "audit", spec: specWithScopes}, + overrides: []pdk.RouteOverride{{Pattern: patterns[2], Wrap: passthrough}}, + } + + _, err := startup(t, coreRecorder(map[string]int{}), cloud, audit) + if err == nil { + t.Fatal("expected startup to abort, got nil error") + } + for _, pattern := range patterns { + if !strings.Contains(err.Error(), pattern) { + t.Fatalf("error should name every unmatched pattern, %q missing from: %v", pattern, err) + } + } + for _, plugin := range []string{"cloud", "audit"} { + if !strings.Contains(err.Error(), plugin) { + t.Fatalf("error should name every claiming plugin, %q missing from: %v", plugin, err) + } + } +} + // Two decorators on one route would have to be ordered by something no plugin // author can see, so this is a startup error naming both claimants. func TestInitPlugins_TwoPluginsClaimingOnePatternAbortStartup(t *testing.T) { From 4b83005d712e3608f24e62d8e2b2b7cfd4a936f6 Mon Sep 17 00:00:00 2001 From: Malintha Amarasinghe Date: Sat, 15 Aug 2026 00:57:34 +1000 Subject: [PATCH 2/2] Pin the unmatched-override error message exactly in tests --- platform-api/internal/server/overrides.go | 3 +- .../internal/server/overrides_test.go | 32 ++++++++----------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/platform-api/internal/server/overrides.go b/platform-api/internal/server/overrides.go index 5f3806b4aa..3d85e06d84 100644 --- a/platform-api/internal/server/overrides.go +++ b/platform-api/internal/server/overrides.go @@ -61,8 +61,7 @@ func installCoreRoutes( for _, pattern := range unknown { claims = append(claims, fmt.Sprintf("plugin %q -> %q", overrides[pattern].plugin, pattern)) } - return fmt.Errorf("%d route override(s) name a pattern that is not a core route "+ - "(patterns are matched exactly, including method and path): %s", + return fmt.Errorf("%d route override(s) name a pattern that is not a core route: %s", len(unknown), strings.Join(claims, "; ")) } diff --git a/platform-api/internal/server/overrides_test.go b/platform-api/internal/server/overrides_test.go index a7094b073b..171145f66d 100644 --- a/platform-api/internal/server/overrides_test.go +++ b/platform-api/internal/server/overrides_test.go @@ -302,38 +302,34 @@ func TestInstallCoreRoutes_UnknownPatternAbortsStartup(t *testing.T) { } // One unmatched pattern per error would mean fixing a typo, restarting, and -// finding the next one — so the error names every unmatched override at once. +// finding the next one — so the error names every unmatched override at once, +// each paired with the plugin that claimed it and ordered by pattern. The whole +// message is compared: a claim listing the wrong plugin, or claims in map order, +// is exactly what the operator reading it cannot afford. func TestInstallCoreRoutes_UnknownPatternErrorNamesEveryOverride(t *testing.T) { - patterns := []string{ - "GET /api/v1/gateways/{gatewayId}", - "GET /api/v0.9/gateways/{id}", - "POST /api/v0.9/gateways", - } cloud := &overridePlugin{ fakePlugin: &fakePlugin{name: "cloud", spec: specWithScopes}, overrides: []pdk.RouteOverride{ - {Pattern: patterns[0], Wrap: passthrough}, - {Pattern: patterns[1], Wrap: passthrough}, + {Pattern: "GET /api/v1/gateways/{gatewayId}", Wrap: passthrough}, + {Pattern: "GET /api/v0.9/gateways/{id}", Wrap: passthrough}, }, } audit := &overridePlugin{ fakePlugin: &fakePlugin{name: "audit", spec: specWithScopes}, - overrides: []pdk.RouteOverride{{Pattern: patterns[2], Wrap: passthrough}}, + overrides: []pdk.RouteOverride{{Pattern: "POST /api/v0.9/gateways", Wrap: passthrough}}, } _, err := startup(t, coreRecorder(map[string]int{}), cloud, audit) if err == nil { t.Fatal("expected startup to abort, got nil error") } - for _, pattern := range patterns { - if !strings.Contains(err.Error(), pattern) { - t.Fatalf("error should name every unmatched pattern, %q missing from: %v", pattern, err) - } - } - for _, plugin := range []string{"cloud", "audit"} { - if !strings.Contains(err.Error(), plugin) { - t.Fatalf("error should name every claiming plugin, %q missing from: %v", plugin, err) - } + + want := `3 route override(s) name a pattern that is not a core route: ` + + `plugin "cloud" -> "GET /api/v0.9/gateways/{id}"; ` + + `plugin "cloud" -> "GET /api/v1/gateways/{gatewayId}"; ` + + `plugin "audit" -> "POST /api/v0.9/gateways"` + if err.Error() != want { + t.Fatalf("error message mismatch\n got: %s\nwant: %s", err.Error(), want) } }