-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
206 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,206 @@ | ||
# errors | ||
<div align="center"> | ||
<img alt="errdetail logo" src="assets/go.png" height="150" /> | ||
|
||
<h3>Errdetail</h3> | ||
<p>Enrich Go errors by context information.</p> | ||
</div> | ||
|
||
--- | ||
|
||
[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/) | ||
[![License](https://img.shields.io/github/license/dnozdrin/errdetail)](/LICENSE) | ||
[![Codecov](https://codecov.io/gh/dnozdrin/errdetail/branch/main/graph/badge.svg)](https://codecov.io/gh/dnozdrin/errdetail) | ||
[![Coreportcard](https://goreportcard.com/badge/github.com/dnozdrin/errdetail)](https://goreportcard.com/report/github.com/dnozdrin/errdetail) | ||
[![Go Reference](https://pkg.go.dev/badge/github.com/dnozdrin/errdetail.svg?style=flat-square)](https://pkg.go.dev/github.com/dnozdrin/errdetail) | ||
[![Release](https://img.shields.io/github/release/dnozdrin/errdetail.svg)](https://github.com/dnozdrin/errdetail/releases/latest) | ||
|
||
## Description | ||
|
||
**Errdetail** allows to add one or multiple details to an `error`. Each detail may contain the next optional fields: | ||
|
||
| Field | Possible usage | Example | | ||
|-------------|----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------| | ||
| domain | a particular interest, activity, or type of knowledge | `user.management` | | ||
| code | a unique code for a particular type of error | `validation_email_invalid_character` | | ||
| description | a short, human-readable summary of the problem that should change from occurrence to occurrence of the problem | `email address must follow the format described in the RFC 5322` | | ||
| field | a field where the error occurred, common for validation issues | `user.email` | | ||
| reason | a human-readable explanation specific to this occurrence of the problem | `an invalid character has been detected in the provided sequence` | | ||
|
||
With such approach it's possible to aggregate information about several errors into one type that implements the `error` interface. | ||
|
||
## Usage | ||
|
||
### Create a detailed error | ||
|
||
```go | ||
err := errdetail.New( | ||
"bad request", | ||
errdetail.NewDetail( | ||
errdetail.WithDomain("user.auth"), | ||
errdetail.WithCode("invalid_email"), | ||
errdetail.WithDescription("email validation failed"), | ||
errdetail.WithField("user.email"), | ||
errdetail.WithReason("invalid character detected: \"#\""), | ||
) | ||
) | ||
``` | ||
|
||
### Wrap existing error | ||
|
||
```go | ||
err := errdetail.Wrap( | ||
errdetail.ErrInvalidArgument, | ||
"bad request", | ||
errdetail.NewDetail( | ||
errdetail.WithDomain("user.auth"), | ||
errdetail.WithCode("invalid_email"), | ||
errdetail.WithDescription("email validation failed"), | ||
errdetail.WithField("user.email"), | ||
errdetail.WithReason("invalid character detected: \"#\""), | ||
), | ||
errdetail.NewDetail( | ||
errdetail.WithDomain("user.auth"), | ||
errdetail.WithCode("invalid_password"), | ||
errdetail.WithDescription("password validation failed"), | ||
errdetail.WithField("user.password"), | ||
errdetail.WithReason("password is empty"), | ||
), | ||
) | ||
``` | ||
|
||
### Use provided error constructors | ||
|
||
```go | ||
err := NewNotFound("discount not found", errdetail.NewDetail(errdetail.WithCode("order_discount_not_supported"))) | ||
``` | ||
|
||
### Use predefined errors | ||
|
||
```go | ||
func (a *Adapter) Get(ctx context.Context, id uuid.UUID) (Item, error) { | ||
if item, ok := a.storage.Get(ctx, id); !ok { | ||
return Item{}, errdetail.ErrNotFound | ||
} | ||
|
||
// ... | ||
} | ||
``` | ||
|
||
### Transform errors details to a suitable presentation | ||
|
||
```go | ||
|
||
type ErrorResponse struct { | ||
Error *Error `json:"error,omitempty"` | ||
} | ||
|
||
type Error struct { | ||
Status int `json:"status"` | ||
Title string `json:"title"` | ||
Code ResponseCode `json:"code"` | ||
Details []ErrorDetail `json:"details,omitempty"` | ||
} | ||
|
||
type ErrorDetail struct { | ||
Domain string `json:"domain,omitempty"` | ||
Reason string `json:"reason,omitempty"` | ||
Field string `json:"field,omitempty"` | ||
Description string `json:"description,omitempty"` | ||
Code string `json:"code,omitempty"` | ||
} | ||
|
||
func NewErrorResponse(err error) ErrorResponse { | ||
if err == nil { | ||
return ErrorResponse{} | ||
} | ||
|
||
var ( | ||
status int | ||
title string | ||
code ResponseCode | ||
) | ||
|
||
switch { | ||
case errors.Is(err, errdetail.ErrInvalidArgument): | ||
status = http.StatusBadRequest | ||
title = errdetail.ErrInvalidArgument.Error() | ||
code = "INVALID_ARGUMENT" | ||
|
||
case errors.Is(err, errdetail.ErrNotFound): | ||
status = http.StatusNotFound | ||
title = errdetail.ErrNotFound.Error() | ||
code = "NOT_FOUND" | ||
|
||
// ... | ||
|
||
default: | ||
status = http.StatusInternalServerError | ||
title = "unknown" | ||
code = "UNKNOWN" | ||
} | ||
|
||
return ErrorResponse{ | ||
Error: &Error{ | ||
Code: code, | ||
Title: title, | ||
Status: status, | ||
Details: extractDetails(err), | ||
}, | ||
} | ||
} | ||
|
||
func extractDetails(err error) []ErrorDetail { | ||
extracted := errdetail.ExtractDetails(err) | ||
if len(extracted) == 0 { | ||
return nil | ||
} | ||
|
||
details := make([]ErrorDetail, len(extracted)) | ||
for i := range extracted { | ||
details[i] = ErrorDetail{ | ||
Domain: extracted[i].Domain(), | ||
Reason: extracted[i].Reason(), | ||
Field: extracted[i].Field(), | ||
Description: extracted[i].Description(), | ||
Code: extracted[i].Code(), | ||
} | ||
} | ||
|
||
return details | ||
} | ||
|
||
``` | ||
|
||
`ErrorResponse` encoded to JSON: | ||
|
||
```json | ||
{ | ||
"error": { | ||
"status": 400, | ||
"title": "invalid argument", | ||
"code": "INVALID_ARGUMENT", | ||
"details": [ | ||
{ | ||
"domain": "user.auth", | ||
"code": "invalid_email", | ||
"description": "email validation failed", | ||
"field": "user.email", | ||
"reason": "invalid character detected: \"#\"" | ||
}, | ||
{ | ||
"domain": "user.auth", | ||
"code": "invalid_password", | ||
"description": "password validation failed", | ||
"field": "user.password", | ||
"reason": "password is empty" | ||
} | ||
] | ||
} | ||
} | ||
``` | ||
|
||
For further details see [examples](https://github.com/dnozdrin/errdetail/tree/main/examples) and [reference](https://pkg.go.dev/badge/github.com/dnozdrin). | ||
|
||
## Contributing | ||
|
||
For contribution guidelines check [CONTRIBUTING.md](https://github.com/dnozdrin/errdetail/blob/main/CONTRIBUTING.md) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.