Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
thalesfsp committed Feb 18, 2022
1 parent 9779123 commit 42cfebc
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 108 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.15
go-version: 1.17

- name: Setup golangci-lint
uses: golangci/golangci-lint-action@v2.5.2
with:
version: v1.41.1
version: v1.43.0

- name: Lint
run: /home/runner/golangci-lint-1.41.1-linux-amd64/golangci-lint run -v -c .golangci.yml
run: /home/runner/golangci-lint-1.43.0-linux-amd64/golangci-lint run -v -c .golangci.yml

- name: Test
run: make test coverage
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ linters:
- cyclop
- tagliatelle
- goerr113
- varnamelen
fast: false

# Settings for specific linters
Expand Down
13 changes: 9 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Roadmap

## [0.0.2] - 2021-09-27
## [1.0.0] - 2022-02-17
### Added
- Functional `Option`s.

### Changed
- `New` now implements the functional optional pattern.

### Removed
- Removed `SetStatusCode`.

## [0.0.2] - 2021-09-27
### Changed
- `Wrap` now accepts a list of errors.

## [0.0.1] - 2021-09-24

### Checklist

- [x] CI Pipeline:
- [x] Lint
- [x] Tests
Expand All @@ -39,7 +45,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [x] Example's test file

### Added

- [x] Ability to create custom errors.
- [x] Ability to create custom errors with code.
- [x] Ability to create custom errors with status code.
Expand Down
16 changes: 8 additions & 8 deletions builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,31 @@ import (
// action failed, e.g: "Failed to create host". Default status code is `500`.
//
// Note: Status code can be redefined, call `SetStatusCode`.
func NewFailedToError(message string, code string, err error) error {
return New(fmt.Sprintf("failed to %s", message), code, http.StatusInternalServerError, err)
func NewFailedToError(message string, opts ...Option) error {
return New(fmt.Sprintf("failed to %s", message), append(opts, WithStatusCode(http.StatusInternalServerError))...)
}

// NewInvalidError is the building block for errors usually thrown when
// something fail validation, e.g: "Invalid port". Default status code is `400`.
//
// Note: Status code can be redefined, call `SetStatusCode`.
func NewInvalidError(message string, code string, err error) error {
return New(fmt.Sprintf("invalid %s", message), code, http.StatusBadRequest, err)
func NewInvalidError(message string, opts ...Option) error {
return New(fmt.Sprintf("invalid %s", message), append(opts, WithStatusCode(http.StatusBadRequest))...)
}

// NewMissingError is the building block for errors usually thrown when required
// information is missing, e.g: "Missing host". Default status code is `400`.
//
// Note: Status code can be redefined, call `SetStatusCode`.
func NewMissingError(message string, code string, err error) error {
return New(fmt.Sprintf("missing %s", message), code, http.StatusBadRequest, err)
func NewMissingError(message string, opts ...Option) error {
return New(fmt.Sprintf("missing %s", message), append(opts, WithStatusCode(http.StatusBadRequest))...)
}

// NewRequiredError is the building block for errors usually thrown when
// required information is missing, e.g: "Port is required". Default status code
// is `400`.
//
// Note: Status code can be redefined, call `SetStatusCode`.
func NewRequiredError(message string, code string, err error) error {
return New(fmt.Sprintf("%s required", message), code, http.StatusBadRequest, err)
func NewRequiredError(message string, opts ...Option) error {
return New(fmt.Sprintf("%s required", message), append(opts, WithStatusCode(http.StatusBadRequest))...)
}
31 changes: 14 additions & 17 deletions customerror.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,6 @@ type CustomError struct {
// Error interface implementation.
//////

// SetStatusCode sets the status code.
//
// Note: Calling this on a static error is dangerous as it will change the
// status code of all its references!
func (cE *CustomError) SetStatusCode(code int) *CustomError {
cE.StatusCode = code

return cE
}

// Error interface implementation returns the properly formatted error message.
func (cE *CustomError) Error() string {
errMsg := cE.Message
Expand All @@ -68,6 +58,13 @@ func (cE *CustomError) Unwrap() error {
return cE.Err
}

// Is interface implementation ensures chain continuity. Treats `CustomError` as
// equivalent to `err`.
//nolint:errorlint
func (cE CustomError) Is(err error) bool {
return cE.Err == err
}

// Wrap `customError` around `errors`.
func Wrap(customError error, errors ...error) error {
errMsgs := []string{}
Expand All @@ -85,14 +82,14 @@ func Wrap(customError error, errors ...error) error {
// Factory.
//////

// New creates custom errors. `message` is required. Failing to satisfy that
// will throw a fatal error.
func New(message, code string, statusCode int, err error) *CustomError {
// New is the custom error factory.
func New(message string, opts ...Option) error {
cE := &CustomError{
Code: code,
Message: message,
Err: err,
StatusCode: statusCode,
Message: message,
}

for _, opt := range opts {
opt(cE)
}

if err := validator.New().Struct(cE); err != nil {
Expand Down
91 changes: 20 additions & 71 deletions customerror_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ var (
)

//nolint:lll
func TestNew(t *testing.T) {
func TestNewLowLevel(t *testing.T) {
type args struct {
message string
code string
statusCode int
err error
message string
opts []Option
}
tests := []struct {
name string
Expand All @@ -46,79 +44,75 @@ func TestNew(t *testing.T) {
name: "should work - with message, and code",
args: args{
message: failedCreateSomethingMsg,
code: code,
opts: []Option{WithCode(code)},
},
want: "E1010: Failed to create something",
},
{
name: "should work - with message, and error",
args: args{
message: failedCreateSomethingMsg,
err: ErrFailedToReachServer,
opts: []Option{WithError(ErrFailedToReachServer)},
},
want: "Failed to create something. Original Error: Failed to reach servers",
},
{
name: "should work - with message, and deep error",
args: args{
message: failedCreateSomethingMsg,
err: ErrFailedToReachServerDeep,
opts: []Option{WithError(ErrFailedToReachServerDeep)},
},
want: "Failed to create something. Original Error: Failed to reach servers. Servers are broken",
},
{
name: "should work - with message, and status code",
args: args{
message: failedCreateSomethingMsg,
statusCode: statusCode,
message: failedCreateSomethingMsg,
opts: []Option{WithStatusCode(statusCode)},
},
want: "Failed to create something (404 - Not Found)",
},
{
name: "should work - with message, code, and error",
args: args{
message: failedCreateSomethingMsg,
code: code,
err: ErrFailedToReachServer,
opts: []Option{WithCode(code), WithError(ErrFailedToReachServer)},
},
want: "E1010: Failed to create something. Original Error: Failed to reach servers",
},
{
name: "should work - with message, code, error, and deep error",
args: args{
message: failedCreateSomethingMsg,
code: code,
err: ErrFailedToReachServerDeep,
opts: []Option{WithCode(code), WithError(ErrFailedToReachServerDeep)},
},
want: "E1010: Failed to create something. Original Error: Failed to reach servers. Servers are broken",
},
{
name: "should work - with message, code, error, deep error, and status code",
args: args{
message: failedCreateSomethingMsg,
code: code,
statusCode: statusCode,
err: ErrFailedToReachServerDeep,
message: failedCreateSomethingMsg,
opts: []Option{WithCode(code), WithError(ErrFailedToReachServerDeep), WithStatusCode(statusCode)},
},
want: "E1010: Failed to create something (404 - Not Found). Original Error: Failed to reach servers. Servers are broken",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := New(tt.args.message, tt.args.code, tt.args.statusCode, tt.args.err)
got := New(tt.args.message, tt.args.opts...)

if !strings.EqualFold(got.Error(), tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
t.Errorf("NewLowLevel() = %v, want %v", got, tt.want)
}
})
}
}

func TestBuiltin(t *testing.T) {
ErrFailedToCreateFile := NewFailedToError("create file", "", nil)
ErrInvalidPath := NewInvalidError("path", "", nil)
ErrMissingPath := NewMissingError("path", "", nil)
ErrRequiredPath := NewRequiredError("path is", "", nil)
ErrFailedToCreateFile := NewFailedToError("create file")
ErrInvalidPath := NewInvalidError("path")
ErrMissingPath := NewMissingError("path")
ErrRequiredPath := NewRequiredError("path is")

testFunc := func(e error) error { return e }

Expand Down Expand Up @@ -246,7 +240,7 @@ func TestNew_deepNestedErrors(t *testing.T) {

layer3 := fmt.Errorf("layer 3. %w", layer2)

ErrLayered := New("custom message", "", 0, layer3)
ErrLayered := New("custom message", WithError(layer3))
if ErrLayered.Error() != expectedErrMsg {
t.Errorf("CustomError deep nested errors got %s, want %s", ErrLayered, expectedErrMsg)
}
Expand Down Expand Up @@ -284,7 +278,7 @@ func TestWrap(t *testing.T) {

layer3 := fmt.Errorf("layer 3. %w", layer2)

ErrLayered := New("custom message", "", 0, layer3)
ErrLayered := New("custom message", WithError(layer3))
if ErrLayered.Error() != expectedErrMsg {
t.Errorf("Wrap got %s, want %s", ErrLayered, expectedErrMsg)
}
Expand All @@ -303,48 +297,3 @@ func TestWrap(t *testing.T) {
t.Errorf("Wrap Is got %s, want %s", errSome, ErrLayered)
}
}

func TestCustomError_SetStatusCode(t *testing.T) {
type fields struct {
Code string
Err error
Message string
StatusCode int
}
type args struct {
code int
}
tests := []struct {
name string
fields fields
args args
want *CustomError
}{
{
name: "Should work",
fields: fields{
Code: "",
Err: nil,
Message: "test",
StatusCode: 0,
},
args: args{
code: 400,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cE := New(
tt.fields.Message,
tt.fields.Code,
tt.fields.StatusCode,
tt.fields.Err,
).SetStatusCode(tt.args.code)

if cE.StatusCode != tt.args.code {
t.Errorf("CustomError.SetStatusCode() = %v, want %v", cE.StatusCode, tt.args.code)
}
})
}
}
14 changes: 12 additions & 2 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ package customerror
import (
"errors"
"fmt"
"net/http"
)

// Demonstrates how to create static, and dynamic custom errors, also how to
// check, and instrospect custom errors.
func ExampleNew() {
// Custom static error definition.
ErrMissingID := NewMissingError("id", "E1010", nil)
ErrMissingID := NewMissingError("id", WithCode("E1010"))

// Some function, for demo purpose.
SomeFunc := func(id string) error {
Expand All @@ -23,7 +24,7 @@ func ExampleNew() {
}

// Dynamic custom error.
return NewFailedToError("write to disk", "E1523", nil)
return NewFailedToError("write to disk", WithCode("E1523"))
}

// Case: Without `id`, returns `ErrMissingID`.
Expand Down Expand Up @@ -55,3 +56,12 @@ func ExampleNew() {
// 500
// E1523: failed to write to disk (500 - Internal Server Error)
}

// Demonstrates how to create static, and dynamic custom errors, also how to
// check, and instrospect custom errors.
func ExampleNew_options() {
fmt.Println(NewMissingError("id", WithCode("E1010"), WithStatusCode(http.StatusOK), WithError(errors.New("some error"))))

// output:
// E1010: missing id (400 - Bad Request). Original Error: some error
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/saucelabs/customerror

go 1.17

require github.com/go-playground/validator/v10 v10.9.0
require github.com/go-playground/validator/v10 v10.10.0

require (
github.com/go-playground/locales v0.14.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
Expand Down
Loading

0 comments on commit 42cfebc

Please sign in to comment.