diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 4dcd8a8032..cd14da305d 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -1480,7 +1480,7 @@ "examples": [["redirect"]] }, "handlers": { - "additionalProperties": false, + "additionalProperties": true, "title": "Individual Error Handler Configuration", "type": "object", "properties": { diff --git a/.schemas/config.schema.json b/.schemas/config.schema.json index 216788c8cd..388d1e8031 100644 --- a/.schemas/config.schema.json +++ b/.schemas/config.schema.json @@ -1158,7 +1158,7 @@ "examples": [["redirect"]] }, "handlers": { - "additionalProperties": false, + "additionalProperties": true, "title": "Individual Error Handler Configuration", "type": "object", "properties": { diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 4293d97092..2e0a8acb66 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -83,6 +83,7 @@ type Provider interface { } type ProviderErrorHandlers interface { + ErrorHandlers() []string ErrorHandlerConfig(id string, override json.RawMessage, dest interface{}) error ErrorHandlerIsEnabled(id string) bool ErrorHandlerFallbackSpecificity() []string diff --git a/driver/configuration/provider_koanf.go b/driver/configuration/provider_koanf.go index 35795f03d6..1f69efd604 100644 --- a/driver/configuration/provider_koanf.go +++ b/driver/configuration/provider_koanf.go @@ -338,8 +338,9 @@ func (v *KoanfProvider) PipelineConfig(prefix, id string, override json.RawMessa } func (v *KoanfProvider) validatePipelineConfig(prefix, id string, marshalled []byte) error { + id_parts := strings.SplitN(id, "#", 2) rawComponentSchema, err := schema.FS.ReadFile(fmt.Sprintf( - "pipeline/%s.%s.schema.json", strings.Split(prefix, ".")[0], id)) + "pipeline/%s.%s.schema.json", strings.Split(prefix, ".")[0], id_parts[0])) if err != nil { return errors.WithStack(err) } @@ -368,6 +369,10 @@ func (v *KoanfProvider) validatePipelineConfig(prefix, id string, marshalled []b return nil } +func (v *KoanfProvider) ErrorHandlers() []string { + return v.source.MapKeys(ErrorsHandlers) +} + func (v *KoanfProvider) ErrorHandlerConfig(id string, override json.RawMessage, dest interface{}) error { return v.PipelineConfig(ErrorsHandlers, id, override, dest) } diff --git a/driver/registry_memory.go b/driver/registry_memory.go index 9bcf90ec4d..53920bae92 100644 --- a/driver/registry_memory.go +++ b/driver/registry_memory.go @@ -6,6 +6,7 @@ package driver import ( "context" "sync" + "strings" "go.opentelemetry.io/otel/trace" @@ -250,10 +251,20 @@ func (r *RegistryMemory) prepareErrors() { defer r.Unlock() if r.errors == nil { - interim := []pe.Handler{ - pe.NewErrorJSON(r.c, r), - pe.NewErrorRedirect(r.c, r), - pe.NewErrorWWWAuthenticate(r.c, r), + var interim []pe.Handler + interim = []pe.Handler{ + pe.NewErrorJSON("json", r.c, r), + pe.NewErrorRedirect("redirect", r.c, r), + pe.NewErrorWWWAuthenticate("www_authenticate", r.c, r), + } + for _, key := range r.c.ErrorHandlers() { + if strings.HasPrefix(key, "json#") { + interim = append(interim, pe.NewErrorJSON(key, r.c, r)) + } else if strings.HasPrefix(key, "redirect#") { + interim = append(interim, pe.NewErrorRedirect(key, r.c, r)) + } else if strings.HasPrefix(key, "www_authenticate#") { + interim = append(interim, pe.NewErrorWWWAuthenticate(key, r.c, r)) + } } r.errors = map[string]pe.Handler{} diff --git a/pipeline/errors/error_json.go b/pipeline/errors/error_json.go index 1b9d8e5aee..7efc5e97f9 100644 --- a/pipeline/errors/error_json.go +++ b/pipeline/errors/error_json.go @@ -22,6 +22,7 @@ type ( Verbose bool `json:"verbose"` } ErrorJSON struct { + n string c configuration.Provider d errorJSONDependencies } @@ -31,10 +32,11 @@ type ( ) func NewErrorJSON( + n string, c configuration.Provider, d errorJSONDependencies, ) *ErrorJSON { - return &ErrorJSON{c: c, d: d} + return &ErrorJSON{n:n, c: c, d: d} } func (a *ErrorJSON) Handle(w http.ResponseWriter, r *http.Request, config json.RawMessage, _ pipeline.Rule, handleError error) error { @@ -89,5 +91,5 @@ func (a *ErrorJSON) Config(config json.RawMessage) (*ErrorJSONConfig, error) { } func (a *ErrorJSON) GetID() string { - return "json" + return a.n } diff --git a/pipeline/errors/error_redirect.go b/pipeline/errors/error_redirect.go index f1c00064ee..1a5c87916d 100644 --- a/pipeline/errors/error_redirect.go +++ b/pipeline/errors/error_redirect.go @@ -28,6 +28,7 @@ type ( ReturnToQueryParam string `json:"return_to_query_param"` } ErrorRedirect struct { + n string c configuration.Provider d ErrorRedirectDependencies } @@ -37,10 +38,11 @@ type ( ) func NewErrorRedirect( + n string, c configuration.Provider, d ErrorRedirectDependencies, ) *ErrorRedirect { - return &ErrorRedirect{c: c, d: d} + return &ErrorRedirect{n: n, c: c, d: d} } func (a *ErrorRedirect) Handle(w http.ResponseWriter, r *http.Request, config json.RawMessage, _ pipeline.Rule, _ error) error { @@ -79,7 +81,7 @@ func (a *ErrorRedirect) Config(config json.RawMessage) (*ErrorRedirectConfig, er } func (a *ErrorRedirect) GetID() string { - return "redirect" + return a.n } func (a *ErrorRedirect) RedirectURL(uri *url.URL, c *ErrorRedirectConfig) string { diff --git a/pipeline/errors/error_redirect_test.go b/pipeline/errors/error_redirect_test.go index 5f2e5d9fae..a8219698da 100644 --- a/pipeline/errors/error_redirect_test.go +++ b/pipeline/errors/error_redirect_test.go @@ -259,3 +259,49 @@ func TestErrorReturnToRedirectURLHeaderUsage(t *testing.T) { }) } } + +func TestErrorRedirectInstance(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + conf.SetForTest(t, "errors.handlers.redirect#forbidden.enabled", true) + reg := internal.NewRegistry(conf) + + a, err := reg.PipelineErrorHandler("redirect#forbidden") + require.NoError(t, err) + + t.Run("method=handle", func(t *testing.T) { + for k, tc := range []struct { + d string + header http.Header + config string + expectError error + givenError error + assert func(t *testing.T, recorder *httptest.ResponseRecorder) + }{ + { + d: "should redirect with 302 - absolute (HTTP)", + givenError: &herodot.ErrNotFound, + config: `{"to":"http://test/test"}`, + assert: func(t *testing.T, rw *httptest.ResponseRecorder) { + assert.Equal(t, 302, rw.Code) + assert.Equal(t, "http://test/test", rw.Header().Get("Location")) + }, + }, + } { + t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/test", nil) + err := a.Handle(w, r, json.RawMessage(tc.config), nil, tc.givenError) + + if tc.expectError != nil { + require.EqualError(t, err, tc.expectError.Error(), "%+v", err) + return + } + + require.NoError(t, err) + if tc.assert != nil { + tc.assert(t, w) + } + }) + } + }) +} diff --git a/pipeline/errors/error_www_authenticate.go b/pipeline/errors/error_www_authenticate.go index c2c959597e..f7dd66724e 100644 --- a/pipeline/errors/error_www_authenticate.go +++ b/pipeline/errors/error_www_authenticate.go @@ -20,6 +20,7 @@ type ( Realm string `json:"realm"` } ErrorWWWAuthenticate struct { + n string c configuration.Provider d ErrorWWWAuthenticateDependencies } @@ -29,10 +30,11 @@ type ( ) func NewErrorWWWAuthenticate( + n string, c configuration.Provider, d ErrorWWWAuthenticateDependencies, ) *ErrorWWWAuthenticate { - return &ErrorWWWAuthenticate{c: c, d: d} + return &ErrorWWWAuthenticate{n:n, c: c, d: d} } func (a *ErrorWWWAuthenticate) Handle(w http.ResponseWriter, r *http.Request, config json.RawMessage, _ pipeline.Rule, _ error) error { @@ -68,5 +70,5 @@ func (a *ErrorWWWAuthenticate) Config(config json.RawMessage) (*ErrorWWWAuthenti } func (a *ErrorWWWAuthenticate) GetID() string { - return "www_authenticate" + return a.n } diff --git a/spec/config.schema.json b/spec/config.schema.json index c036e98bb6..fea6ca5509 100644 --- a/spec/config.schema.json +++ b/spec/config.schema.json @@ -1486,7 +1486,7 @@ "examples": [["redirect"]] }, "handlers": { - "additionalProperties": false, + "additionalProperties": true, "title": "Individual Error Handler Configuration", "type": "object", "properties": {