Skip to content

En/error fields #1615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
99540a6
add the ResponseMarshaller interface and add tests
Umang01-hash Mar 26, 2025
834bbfa
update documentation
Umang01-hash Mar 27, 2025
bc7dbcf
resolve linters
Umang01-hash Mar 27, 2025
46c7860
Merge branch 'development' of github.com:gofr-dev/gofr into en/error_…
Umang01-hash Apr 1, 2025
8f1c3b8
Merge branch 'development' into en/error_fields
Umang01-hash Apr 1, 2025
41a8da8
changes in responder to address review comments
Umang01-hash Apr 3, 2025
77fea38
Merge branch 'development' into en/error_fields
Umang01-hash Apr 4, 2025
dfc6702
Merge branch 'en/error_fields' of github.com:gofr-dev/gofr into en/er…
Umang01-hash Apr 4, 2025
5f21d41
Merge branch 'development' of github.com:gofr-dev/gofr into en/error_…
Umang01-hash Apr 9, 2025
bc835f5
Merge branch 'development' into en/error_fields
Umang01-hash Apr 9, 2025
7fb1bf7
resolve review comments
Umang01-hash Apr 9, 2025
bb56875
Merge branch 'en/error_fields' of github.com:gofr-dev/gofr into en/er…
Umang01-hash Apr 9, 2025
c69007f
update documentation
Umang01-hash Apr 9, 2025
05b2400
resolve review comments
Umang01-hash Apr 10, 2025
1eaf8f8
resolve review comments
Umang01-hash Apr 10, 2025
c9a4d17
Merge branch 'development' into en/error_fields
Umang01-hash Apr 14, 2025
341909a
Merge branch 'development' of github.com:gofr-dev/gofr into en/error_…
Umang01-hash Apr 14, 2025
7bf1833
resolve review comments in responder's test methods
Umang01-hash Apr 14, 2025
75878f2
Merge branch 'en/error_fields' of github.com:gofr-dev/gofr into en/er…
Umang01-hash Apr 14, 2025
6a41e3f
Merge branch 'development' into en/error_fields
Umang01-hash Apr 14, 2025
4c383e4
Merge branch 'development' of github.com:gofr-dev/gofr into en/error_…
Umang01-hash Apr 14, 2025
8322040
resolve linters
Umang01-hash Apr 14, 2025
1ccb7e8
Merge branch 'en/error_fields' of github.com:gofr-dev/gofr into en/er…
Umang01-hash Apr 14, 2025
1fa3a35
Merge branch 'development' into en/error_fields
aryanmehrotra Apr 14, 2025
662c4c5
Merge branch 'development' of github.com:gofr-dev/gofr into en/error_…
Umang01-hash Apr 15, 2025
6cca220
Merge remote-tracking branch 'origin/en/error_fields' into en/error_f…
Umang01-hash Apr 15, 2025
09c4915
resolve review comments
Umang01-hash Apr 15, 2025
da351df
Merge branch 'development' into en/error_fields
Umang01-hash Apr 15, 2025
a2b3eb1
fix small typo in documentation
Umang01-hash Apr 15, 2025
112d8f3
Merge branch 'en/error_fields' of github.com:gofr-dev/gofr into en/er…
Umang01-hash Apr 15, 2025
d1c33ba
refactor the tone of NOTE in documentation
Umang01-hash Apr 15, 2025
eb009d2
fix tests and statusCode 0 condition
Umang01-hash Apr 15, 2025
056888d
resolve typo
Umang01-hash Apr 15, 2025
47be613
Merge branch 'development' into en/error_fields
Umang01-hash Apr 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/advanced-guide/gofr-errors/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,35 @@ func (c customError) LogLevel() logging.Level {
return logging.WARN
}
```

## Extended Error Responses

For RFC 7807-style error responses with additional fields, implement the ResponseMarshaller interface:

```go
type ResponseMarshaler interface {
Response() map[string]any
}
```

#### Usage:
```go
type ValidationError struct {
Field string
Message string
Code int
}

func (e ValidationError) Error() string { return e.Message }
func (e ValidationError) StatusCode() int { return e.Code }

func (e ValidationError) Response() map[string]any {
return map[string]any{
"field": e.Field,
"type": "validation_error",
"details": "Invalid input format",
}
}
```

> NOTE: The `message` field is reserved and always populated from `Error()` method. Avoid using message as a key in the response map
22 changes: 20 additions & 2 deletions pkg/gofr/http/responder.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,28 @@ func handleSuccess(method string, data any) (statusCode int, err any) {
}
}

// ResponseMarshaller defines an interface for errors that can provide custom fields.
// This enables errors to extend the error response with additional fields.
type ResponseMarshaller interface {
Response() map[string]any
}

// createErrorResponse returns an error response that always contains a "message" field,
// and if the error implements ResponseMarshaller, it merges custom fields into the response.
func createErrorResponse(err error) map[string]any {
return map[string]any{
"message": err.Error(),
resp := map[string]any{"message": err.Error()}

if rm, ok := err.(ResponseMarshaller); ok {
for k, v := range rm.Response() {
if k == "message" {
continue // Skip to avoid overriding the Error() message
}

resp[k] = v
}
}

return resp
}

// response represents an HTTP response.
Expand Down
75 changes: 75 additions & 0 deletions pkg/gofr/http/responder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package http

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
Expand Down Expand Up @@ -207,6 +208,80 @@ func TestResponder_TemplateResponse(t *testing.T) {
assert.Equal(t, expectedBody, responseBody)
}

func TestResponder_CustomErrorWithResponse(t *testing.T) {
w := httptest.NewRecorder()
responder := NewResponder(w, http.MethodGet)

customErr := &CustomError{
Code: http.StatusNotFound,
Message: "resource not found",
Title: "Custom Error",
}

responder.Respond(nil, customErr)

resp := w.Result()
defer resp.Body.Close()

assert.Equal(t, http.StatusNotFound, resp.StatusCode)

var body map[string]any
err := json.NewDecoder(resp.Body).Decode(&body)
require.NoError(t, err)

errorObj := body["error"].(map[string]any)

assert.Equal(t, "resource not found", errorObj["message"])
assert.Equal(t, int(http.StatusNotFound), int(errorObj["code"].(float64)))
assert.Equal(t, "Custom Error", errorObj["title"])
}

type CustomError struct {
Code int
Message string
Title string
}

func (e *CustomError) Error() string { return e.Message }
func (e *CustomError) StatusCode() int { return e.Code }
func (e *CustomError) Response() map[string]any {
return map[string]any{"title": e.Title, "code": e.Code}
}

func TestResponder_ReservedMessageField(t *testing.T) {
w := httptest.NewRecorder()
responder := NewResponder(w, http.MethodGet)

msgErr := &MessageOverrideError{
Msg: "original message",
}

responder.Respond(nil, msgErr)

resp := w.Result()
defer resp.Body.Close()

var body map[string]any
err := json.NewDecoder(resp.Body).Decode(&body)
require.NoError(t, err)

errorObj := body["error"].(map[string]any)
assert.Equal(t, "original message", errorObj["message"])
assert.Equal(t, "additional info", errorObj["info"])
}

type MessageOverrideError struct {
Msg string
}

func (e *MessageOverrideError) Error() string { return e.Msg }
func (*MessageOverrideError) Response() map[string]any {
return map[string]any{
"message": "trying to override",
"info": "additional info",
}
}

func createTemplateFile(t *testing.T, path, content string) {
t.Helper()

Expand Down
Loading