From 68720dcaa8d857624daf9d51f4db25d02904080e Mon Sep 17 00:00:00 2001 From: Fabio Bonelli Date: Thu, 12 Nov 2026 06:46:07 +0000 Subject: [PATCH] fix: return 500 for unhandled handler errors --- internal/common/errors.go | 8 ++++++- main_test.go | 35 +++++++++++++++++++++++++++++++ publiccode-validator-api.oas.yaml | 20 ++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/internal/common/errors.go b/internal/common/errors.go index 0bc9e73..1cb8b14 100644 --- a/internal/common/errors.go +++ b/internal/common/errors.go @@ -2,6 +2,7 @@ package common import ( "errors" + "log" "github.com/gofiber/fiber/v3" ) @@ -34,7 +35,12 @@ func CustomErrorHandler(ctx fiber.Ctx, err error) error { case ProblemJSONError: problemJSON = &e default: - problemJSON = &ProblemJSONError{Status: fiber.StatusNotFound, Title: fiber.ErrNotFound.Message, Detail: e.Error()} + log.Printf("Unhandled request error: %v", err) + + problemJSON = &ProblemJSONError{ + Status: fiber.StatusInternalServerError, + Title: fiber.ErrInternalServerError.Message, + } } } diff --git a/main_test.go b/main_test.go index 714ee23..576e92b 100644 --- a/main_test.go +++ b/main_test.go @@ -1,11 +1,14 @@ package main import ( + "bytes" "encoding/json" + "errors" "fmt" "io" "log" "net/http" + "net/http/httptest" "net/url" "os" "strings" @@ -129,6 +132,38 @@ func TestApi(t *testing.T) { runTestCases(t, tests) } +func TestUnhandledErrors(t *testing.T) { + for _, panics := range []bool{false, true} { + t.Run(fmt.Sprintf("panic=%t", panics), func(t *testing.T) { + var logs bytes.Buffer + previousOutput := log.Writer() + log.SetOutput(&logs) + t.Cleanup(func() { log.SetOutput(previousOutput) }) + + app := Setup() + app.Get("/error", func(ctx fiber.Ctx) error { + if panics { + panic("secret internal state") + } + return errors.New("secret internal state") + }) + + res, err := app.Test(httptest.NewRequest(http.MethodGet, "/error", nil)) + if !assert.NoError(t, err) { + return + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + assert.NoError(t, err) + assert.Equal(t, http.StatusInternalServerError, res.StatusCode) + assert.Equal(t, "application/problem+json", res.Header.Get("Content-Type")) + assert.JSONEq(t, `{"title":"Internal Server Error","status":500}`, string(body)) + assert.Contains(t, logs.String(), "secret internal state") + }) + } +} + func TestValidateEndpoint(t *testing.T) { validYml := loadTestdata(t, "valid.publiccode.yml") invalidYml := loadTestdata(t, "invalid.publiccode.yml") diff --git a/publiccode-validator-api.oas.yaml b/publiccode-validator-api.oas.yaml index 10b1515..3cf7bce 100644 --- a/publiccode-validator-api.oas.yaml +++ b/publiccode-validator-api.oas.yaml @@ -41,6 +41,16 @@ paths: v: "1.0.0" commit: "abc1234" + "500": + description: Unexpected server error + content: + application/problem+json: + schema: + $ref: "#/components/schemas/ProblemJSON" + example: + title: Internal Server Error + status: 500 + /validate: post: operationId: validatePubliccode @@ -137,6 +147,16 @@ paths: title: Not Found status: 404 + "500": + description: Unexpected server error + content: + application/problem+json: + schema: + $ref: "#/components/schemas/ProblemJSON" + example: + title: Internal Server Error + status: 500 + components: schemas: Status: