diff --git a/platform-api/internal/server/overrides.go b/platform-api/internal/server/overrides.go index 62cc2f7bc3..3d85e06d84 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,12 @@ 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: %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..171145f66d 100644 --- a/platform-api/internal/server/overrides_test.go +++ b/platform-api/internal/server/overrides_test.go @@ -301,6 +301,38 @@ 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, +// 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) { + cloud := &overridePlugin{ + fakePlugin: &fakePlugin{name: "cloud", spec: specWithScopes}, + overrides: []pdk.RouteOverride{ + {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: "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") + } + + 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) + } +} + // 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) {