Skip to content

Commit

Permalink
feat: add package to handle end-user error messages. (#13)
Browse files Browse the repository at this point in the history
Because

- Connector errors produce an unfriendly output in VDP.
- In order to trigger a pipeline, several repositories are involved in
  the execution.

This commit

- Implements a way to add and extract end-user messages to errors.
  • Loading branch information
jvallesm authored Dec 19, 2023
1 parent 3236165 commit 6230a89
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:

- uses: actions/setup-go@v1
with:
go-version: 1.17
go-version: 1.21

- uses: actions/checkout@v3

Expand Down
50 changes: 50 additions & 0 deletions errmsg/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# errmsg

Add end-user messages to errors.

`err.Error()` doesn't usually provide a human-friendly output. `errmsg` allows
errors to carry an (extendable) end-user message that can be used in e.g.
handlers.

Here is an example on how it can be used:

```go
package connector

import (
// ...
"github.com/instill-ai/x/errmsg"
)

func (c *Client) sendReq(reqURL, method, contentType string, data io.Reader) ([]byte, error) {
// ...

res, err := c.HTTPClient.Do(req)
if err != nil {
err := fmt.Errorf("failed to call connector vendor: %w", err)
return nil, errmsg.AddMessage(err, "Failed to call Vendor API.")
}

if res.StatusCode < 200 || res.StatusCode >= 300 {
err := fmt.Errorf("vendor responded with status code %d", res.StatusCode)
msg := fmt.Sprintf("Vendor responded with a %d status code.", res.StatusCode)
return nil, errmsg.AddMessage(err, msg)
}

// ...
}
```

```go
package handler

func (h *PublicHandler) DoAction(ctx context.Context, req *pb.DoActionRequest) (*pb.DoActionResponse, error) {
resp, err := h.triggerActionSteps(ctx, req)
if err != nil {
resp.Outputs, resp.Metadata, err = h.triggerNamespacePipeline(ctx, req)
return nil, status.Error(asGRPCStatus(err), errmsg.MessageOrErr(err))
}

return resp, nil
}
```
67 changes: 67 additions & 0 deletions errmsg/errmsg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package errmsg

import (
"errors"
"fmt"
)

// endUserError is an error that holds an end-user message.
type endUserError struct {
message string
cause error
}

// Error implements the error interface by returning the internal error message.
func (e *endUserError) Error() string { return e.cause.Error() }

// Unwrap implements the Unwrap interface.
func (e *endUserError) Unwrap() error { return e.cause }

// As implements the required function to ensure errors.As can properly match
// endUserEror targets.
func (e *endUserError) As(target any) bool {
if tgt, ok := target.(**endUserError); ok {
*tgt = e
return true
}

return false
}

// AddMessage adds an end-user message to an error, prepending it to any
// potential existing message.
func AddMessage(err error, msg string) error {
if msgInCause := Message(err); msgInCause != "" {
msg = fmt.Sprintf("%s %s", msg, msgInCause)
}

return &endUserError{
cause: err,
message: msg,
}
}

// Message extracts an end-user message from the error.
func Message(err error) string {
for err != nil {
eu := new(endUserError)
if errors.As(err, &eu) && eu.message != "" {
return eu.message
}

err = errors.Unwrap(err)
}

return ""
}

// MessageOrErr extracts an end-user message from the error. If no message is
// found, err.Error() is returned.
func MessageOrErr(err error) string {
msg := Message(err)
if msg == "" {
return err.Error()
}

return msg
}
72 changes: 72 additions & 0 deletions errmsg/errmsg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package errmsg

import (
"errors"
"fmt"
"testing"

qt "github.com/frankban/quicktest"
)

func TestAddAndExtractMessage(t *testing.T) {
c := qt.New(t)

testcases := []struct {
name string
wantMsg string
wantErr string
err error
}{
{
name: "no message",
wantMsg: "boom",
wantErr: "boom",
err: errors.New("boom"),
},
{
name: "message on top of stack",
wantMsg: "Something went wrong.",
wantErr: "boom",
err: AddMessage(errors.New("boom"), "Something went wrong."),
},
{
name: "message in wrapped error (fmt)",
wantMsg: "Something went wrong.",
wantErr: "bang: boom",
err: fmt.Errorf(
"bang: %w",
AddMessage(errors.New("boom"), "Something went wrong."),
),
},
{
name: "message in joint error",
wantMsg: "Something went wrong.",
wantErr: "bang\nboom",
err: errors.Join(
errors.New("bang"),
AddMessage(errors.New("boom"), "Something went wrong."),
),
},
{
name: "multi-message error",
wantMsg: "An error happened. Something went wrong.",
wantErr: "bang: boom",
err: AddMessage(
// handle error coming from downstream
fmt.Errorf("bang: %w",
// downstream error also contains message
AddMessage(errors.New("boom"), "Something went wrong."),
),
// add message to downstream error
"An error happened.",
),
},
}

for _, tc := range testcases {
c.Run(tc.name, func(c *qt.C) {
c.Check(MessageOrErr(tc.err), qt.Equals, tc.wantMsg)
c.Check(tc.err, qt.ErrorMatches, tc.wantErr)
})
}
}
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
module github.com/instill-ai/x

go 1.19
go 1.21

require (
github.com/frankban/quicktest v1.14.6
github.com/google/uuid v1.3.0
github.com/iancoleman/strcase v0.2.0
github.com/pkg/errors v0.8.1
github.com/stretchr/testify v1.7.0
go.temporal.io/sdk v1.13.1
go.uber.org/zap v1.21.0
Expand All @@ -21,10 +23,14 @@ require (
github.com/gogo/status v1.1.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/stretchr/objx v0.3.0 // indirect
go.temporal.io/api v1.6.1-0.20211110205628-60c98e9cbfe2 // indirect
go.uber.org/atomic v1.9.0 // indirect
Expand Down
11 changes: 9 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
Expand Down Expand Up @@ -65,8 +67,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
Expand All @@ -81,6 +84,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand All @@ -90,6 +95,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -98,6 +104,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down Expand Up @@ -201,7 +209,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
Expand Down

0 comments on commit 6230a89

Please sign in to comment.