Skip to content

Commit

Permalink
feat: Http grpc mapping (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
CAJan93 authored Mar 14, 2023
1 parent 8893ebb commit bea618e
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 7 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@ func (req *Request) Validate() error {
}
```

You can also use HTTP and GRPC codes to create errors

```golang
// http
httpCode := operation()
err := eris.New("http operation went badly").WithCodeHttp(httpCode)
// err is nil, if httpCode is 200
if err != nil {
// log err
}

// grpc
grpcCode := operation()
err := eris.New("grpc operation went badly").WithCodeGrpc(grpcCode)
// err is nil, if grpcCode is OK
if err != nil {
// log err
}
```

### Wrapping errors

[`eris.Wrap`](https://pkg.go.dev/github.com/risingwavelabs/eris#Wrap) adds context to an error while preserving the original error. The default assigned error code will be `internal`. Like above you can change the code via `WithCode` and set additional properties using `WithProperty`.
Expand Down Expand Up @@ -123,6 +143,11 @@ if err.HasKVs() {
additional_context := caller2.KVs()
// log additional context
}

httpResultCode := 200
if err != nil {
httpResultCode = err.Code().ToHttp()
}
```

You can also use `GetCode(err error)`. This will default to `unknown` if you pass in an standard lib error.
Expand Down
63 changes: 57 additions & 6 deletions codes.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package eris

import (
"net/http"

grpc "google.golang.org/grpc/codes"
)

Expand Down Expand Up @@ -78,8 +80,11 @@ const (
DEFAULT_UNKNOWN_CODE = CodeUnknown
)

// FromGrpc converts a grpc code to an eris code.
func FromGrpc(c grpc.Code) Code {
// fromGrpc converts a grpc code to an eris code. Returns false if mapping failed
func fromGrpc(c grpc.Code) (Code, bool) {
if c == grpc.OK {
return DEFAULT_UNKNOWN_CODE, false
}
if resultCode, ok := map[grpc.Code]Code{
grpc.Aborted: CodeAborted,
grpc.AlreadyExists: CodeAlreadyExists,
Expand All @@ -98,13 +103,13 @@ func FromGrpc(c grpc.Code) Code {
grpc.Unknown: CodeUnknown,
grpc.Unimplemented: CodeUnimplemented,
}[c]; ok {
return resultCode
return resultCode, true
}
return DEFAULT_UNKNOWN_CODE
return DEFAULT_UNKNOWN_CODE, true
}

// ToGRPC converts an eris code to a grpc code.
func (c Code) ToGRPC() grpc.Code {
// ToGrpc converts an eris code to a grpc code.
func (c Code) ToGrpc() grpc.Code {
if grpcCode, ok := map[Code]grpc.Code{
CodeAborted: grpc.Aborted,
CodeAlreadyExists: grpc.AlreadyExists,
Expand All @@ -127,3 +132,49 @@ func (c Code) ToGRPC() grpc.Code {
}
return grpc.Unknown
}

// We do not provide a FromHttp method, since many http codes, would correlate to a grpc code OK
// TODO Write a test that asserts that

type HTTPStatus int

// fromHttp converts a http code to an eris code. Returns false if mapping failed
func fromHttp(code HTTPStatus) (Code, bool) {
// mapping according to https://github.com/lobocv/simplerr/blob/master/ecosystem/http/translate_error_code.go
if code == 200 {
return DEFAULT_UNKNOWN_CODE, false
}
if c, ok := map[HTTPStatus]Code{
http.StatusInternalServerError: CodeUnknown,
http.StatusNotFound: CodeNotFound,
http.StatusRequestTimeout: CodeDeadlineExceeded,
http.StatusForbidden: CodePermissionDenied,
http.StatusUnauthorized: CodeUnauthenticated,
http.StatusNotImplemented: CodeUnimplemented,
http.StatusBadRequest: CodeInvalidArgument,
http.StatusTooManyRequests: CodeResourceExhausted,
}[code]; ok {
return c, true
}
return CodeUnknown, true
}

// ToHttp converts an eris code to a http code.
func (code Code) ToHttp() HTTPStatus {
// mapping according to https://github.com/lobocv/simplerr/blob/master/ecosystem/http/translate_error_code.go
if httpCode, ok := map[Code]HTTPStatus{
CodeUnknown: http.StatusInternalServerError,
CodeNotFound: http.StatusNotFound,
CodeDeadlineExceeded: http.StatusRequestTimeout,
CodePermissionDenied: http.StatusForbidden,
CodeUnauthenticated: http.StatusUnauthorized,
CodeUnimplemented: http.StatusNotImplemented,
CodeInvalidArgument: http.StatusBadRequest,
CodeResourceExhausted: http.StatusTooManyRequests,
}[code]; ok {
return httpCode
}

// Default according to https://chromium.googlesource.com/external/github.com/grpc/grpc/+/refs/tags/v1.21.4-pre1/doc/statuscodes.md
return http.StatusInternalServerError
}
34 changes: 34 additions & 0 deletions codes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package eris

import (
"net/http"
"testing"

grpc "google.golang.org/grpc/codes"
)

func TestDefaultCodes(t *testing.T) {
invalidHttp := Code(-1)
resultCodeHttp := invalidHttp.ToHttp()
if resultCodeHttp != http.StatusInternalServerError {
t.Errorf("invalid code should be mapped to 500, but was %v", resultCodeHttp)
}

invalidGrpc := Code(-1)
resultCodeGrpc := invalidGrpc.ToGrpc()
if resultCodeGrpc != grpc.Unknown {
t.Errorf("invalid code should be mapped to grpc.Unknown, but was %v", resultCodeGrpc)
}
}

func TestInvalidConversions(t *testing.T) {
code, validConversion := fromGrpc(grpc.OK)
if validConversion {
t.Errorf("grpc.Ok should not get converted to our error codes, but was converted to %v", code)
}

code, validConversion = fromHttp(200)
if validConversion {
t.Errorf("http 200 should not get converted to our error codes, but was converted to %v", code)
}
}
44 changes: 44 additions & 0 deletions eris.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ package eris
import (
"fmt"
"io"
"net/http"
"reflect"

grpc "google.golang.org/grpc/codes"
)

type statusError interface {
error
WithCode(Code) statusError
WithCodeGrpc(grpc.Code) statusError
WithCodeHttp(HTTPStatus) statusError
WithProperty(string, any) statusError
Code() Code
HasKVs() bool
Expand Down Expand Up @@ -289,6 +294,27 @@ func (e *rootError) WithCode(code Code) statusError {
return e
}

// TODO: also do this for other errors
// add this function to interface

// WithCodeGrpc sets the error code, based on an GRPC error code.
func (e *rootError) WithCodeGrpc(code grpc.Code) statusError {
if e == nil || code == grpc.OK {
return nil
}
e.code, _ = fromGrpc(code)
return e
}

// WithCodeHttp sets the error code, based on an HTTP status code.
func (e *rootError) WithCodeHttp(code HTTPStatus) statusError {
if e == nil || code == http.StatusOK {
return nil
}
e.code, _ = fromHttp(code)
return e
}

// WithProperty adds a key-value pair to the error.
func (e *rootError) WithProperty(key string, value any) statusError {
if e == nil {
Expand Down Expand Up @@ -381,6 +407,24 @@ func (e *wrapError) WithCode(code Code) statusError {
return e
}

// WithCodeGrpc sets the error code, based on an GRPC error code.
func (e *wrapError) WithCodeGrpc(code grpc.Code) statusError {
if e == nil || code == grpc.OK {
return nil
}
e.code, _ = fromGrpc(code)
return e
}

// WithCodeHttp sets the error code, based on an HTTP status code.
func (e *wrapError) WithCodeHttp(code HTTPStatus) statusError {
if e == nil || code == http.StatusOK {
return nil
}
e.code, _ = fromHttp(code)
return e
}

// WithProperty adds a key-value pair to the error.
func (e *wrapError) WithProperty(key string, value any) statusError {
if e == nil {
Expand Down
14 changes: 14 additions & 0 deletions eris_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package eris_test
import (
"errors"
"fmt"
"net/http"
"reflect"
"runtime"
"strings"
"testing"

"github.com/risingwavelabs/eris"
grpc "google.golang.org/grpc/codes"
)

var (
Expand Down Expand Up @@ -764,3 +766,15 @@ func TestStackFrames(t *testing.T) {
})
}
}

func TestOkCode(t *testing.T) {
err := eris.New("everything went fine").WithCodeGrpc(grpc.OK)
if err != nil {
t.Errorf("expected nil error if grpc status is OK, but error was %v", err)
}

err = eris.New("everything went fine again").WithCodeHttp(http.StatusOK)
if err != nil {
t.Errorf("expected nil error if grpc status is OK, but error was %v", err)
}
}
2 changes: 1 addition & 1 deletion stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

const (
file = "StatusError/stack_test.go"
file = "eris/stack_test.go"
readFunc = "eris_test.ReadFile"
parseFunc = "eris_test.ParseFile"
processFunc = "eris_test.ProcessFile"
Expand Down

0 comments on commit bea618e

Please sign in to comment.