diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index a551dc3..9068864 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -14,7 +14,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
- go-version: '1.19.3'
+ go-version: '1.20.1'
id: go
- name: Check out code into the Go module directory
diff --git a/README.md b/README.md
index 119d015..f2698f8 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
[![Coverage Status](https://coveralls.io/repos/github/hedhyw/semerr/badge.svg?branch=main)](https://coveralls.io/github/hedhyw/semerr?branch=main)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/hedhyw/semerr)](https://pkg.go.dev/github.com/hedhyw/semerr?tab=doc)
-Package `semerr` helps to work with errors in Golang.
+Package `semerr` helps to work with errors in Golang. It supports go 1.20 [errors.Join](https://pkg.go.dev/errors#Join).
@@ -88,10 +88,13 @@ func (s *Server) handleCreateOrder(w http.ResponseWriter, r *http.Request) {
```go
errOriginal := errors.New("some error")
errWrapped := semerr.NewBadRequestError(errOriginal) // The text will be the same.
+errJoined := errors.Join(errOriginal, errWrapped) // It supports joined errors.
fmt.Println(errWrapped) // "some error"
fmt.Println(httperr.Code(errWrapped)) // http.StatusBadRequest
+fmt.Println(httperr.Code(errJoined)) // http.StatusBadRequest
fmt.Println(grpcerr.Code(errWrapped)) // codes.InvalidArgument
+fmt.Println(grpcerr.Code(errJoined)) // codes.InvalidArgument
fmt.Println(errors.Is(err, errOriginal)) // true
fmt.Println(semerr.NewBadRequestError(nil)) // nil
fmt.Println(httperr.Wrap(errOriginal, http.StatusBadRequest)) // = semerr.NewBadRequestError(errOriginal)
diff --git a/README.md.tmpl b/README.md.tmpl
index fe2e686..4dd5e9e 100644
--- a/README.md.tmpl
+++ b/README.md.tmpl
@@ -8,7 +8,7 @@
[![Coverage Status](https://coveralls.io/repos/github/hedhyw/semerr/badge.svg?branch=main)](https://coveralls.io/github/hedhyw/semerr?branch=main)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/hedhyw/semerr)](https://pkg.go.dev/github.com/hedhyw/semerr?tab=doc)
-Package `semerr` helps to work with errors in Golang.
+Package `semerr` helps to work with errors in Golang. It supports go 1.20 [errors.Join](https://pkg.go.dev/errors#Join).
@@ -88,10 +88,13 @@ func (s *Server) handleCreateOrder(w http.ResponseWriter, r *http.Request) {
```go
errOriginal := errors.New("some error")
errWrapped := semerr.NewBadRequestError(errOriginal) // The text will be the same.
+errJoined := errors.Join(errOriginal, errWrapped) // It supports joined errors.
fmt.Println(errWrapped) // "some error"
fmt.Println(httperr.Code(errWrapped)) // http.StatusBadRequest
+fmt.Println(httperr.Code(errJoined)) // http.StatusBadRequest
fmt.Println(grpcerr.Code(errWrapped)) // codes.InvalidArgument
+fmt.Println(grpcerr.Code(errJoined)) // codes.InvalidArgument
fmt.Println(errors.Is(err, errOriginal)) // true
fmt.Println(semerr.NewBadRequestError(nil)) // nil
fmt.Println(httperr.Wrap(errOriginal, http.StatusBadRequest)) // = semerr.NewBadRequestError(errOriginal)
diff --git a/go.mod b/go.mod
index 1698785..d89b49e 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
- google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514 // indirect
+ google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
diff --git a/go.sum b/go.sum
index d660c0b..94f59af 100644
--- a/go.sum
+++ b/go.sum
@@ -48,7 +48,7 @@ cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9j
cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ=
cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
-cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M=
+cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE=
cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk=
cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc=
cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8=
@@ -64,12 +64,12 @@ cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodC
cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84=
cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=
cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
-cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY=
+cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k=
cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=
cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=
cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0=
cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc=
-cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI=
+cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ=
cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=
cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=
cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=
@@ -105,7 +105,7 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7
cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=
cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw=
cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc=
-cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E=
+cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac=
cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI=
@@ -124,7 +124,7 @@ cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL
cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE=
cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U=
cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA=
-cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M=
+cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg=
cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM=
cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk=
cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA=
@@ -277,7 +277,7 @@ cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Q
cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=
cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
-cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=
+cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w=
cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=
@@ -421,7 +421,7 @@ cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZ
cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0=
cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU=
cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s=
-cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA=
+cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc=
cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=
cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=
cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4=
@@ -472,10 +472,10 @@ cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1r
cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA=
cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs=
cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=
-cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0=
+cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=
cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk=
cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw=
-cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg=
+cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk=
cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=
cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M=
@@ -1285,8 +1285,8 @@ google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ
google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA=
google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
-google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514 h1:rtNKfB++wz5mtDY2t5C8TXlU5y52ojSu7tZo0z7u8eQ=
-google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
diff --git a/internal/cmd/generator/generator.go b/internal/cmd/generator/generator.go
index e91f099..f0750b4 100644
--- a/internal/cmd/generator/generator.go
+++ b/internal/cmd/generator/generator.go
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"context"
+ "errors"
"fmt"
"go/format"
"io/fs"
@@ -18,7 +19,6 @@ import (
_ "embed"
- "github.com/hedhyw/semerr/pkg/v1/semerr"
"google.golang.org/grpc/codes"
"gopkg.in/yaml.v2"
)
@@ -106,7 +106,7 @@ func walkFn(errDefs []errorDefinition) fs.WalkDirFunc {
return fmt.Errorf("opening out file: %w", err)
}
- defer func() { err = semerr.NewMultiError(err, f.Close()) }()
+ defer func() { err = errors.Join(err, f.Close()) }()
var buf bytes.Buffer
err = tmpl.Execute(&buf, errDefs)
diff --git a/makefile b/makefile
index fae4e70..71f3974 100644
--- a/makefile
+++ b/makefile
@@ -1,5 +1,5 @@
FILES_DIR?=$(PWD)/pkg/v1
-GOLANGCI_LINT_VER:=v1.50.1
+GOLANGCI_LINT_VER:=v1.51.2
all: generate lint test
.PHONY: all
diff --git a/pkg/v1/grpcerr/grpcerr_generated.go b/pkg/v1/grpcerr/grpcerr_generated.go
index 05b35a2..9eb34f2 100755
--- a/pkg/v1/grpcerr/grpcerr_generated.go
+++ b/pkg/v1/grpcerr/grpcerr_generated.go
@@ -11,49 +11,63 @@ import (
"google.golang.org/grpc/status"
)
-// Code returns grpc status code for err.
+// Code returns grpc status code for err. In case of joined errors
+// it returns the first code found in the chain.
func Code(err error) codes.Code {
- switch err.(type) {
- case nil:
+ switch {
+ case err == nil:
return codes.OK
- case semerr.StatusRequestTimeoutError:
+ case errors.As(err, &semerr.StatusRequestTimeoutError{}):
return 1
- case semerr.InternalServerError:
+ case errors.As(err, &semerr.InternalServerError{}):
return 2
- case semerr.BadRequestError:
+ case errors.As(err, &semerr.BadRequestError{}):
return 3
- case semerr.UnsupportedMediaTypeError:
+ case errors.As(err, &semerr.UnsupportedMediaTypeError{}):
return 3
- case semerr.StatusGatewayTimeoutError:
+ case errors.As(err, &semerr.StatusGatewayTimeoutError{}):
return 4
- case semerr.NotFoundError:
+ case errors.As(err, &semerr.NotFoundError{}):
return 5
- case semerr.ConflictError:
+ case errors.As(err, &semerr.ConflictError{}):
return 6
- case semerr.ForbiddenError:
+ case errors.As(err, &semerr.ForbiddenError{}):
return 7
- case semerr.TooManyRequestsError:
+ case errors.As(err, &semerr.TooManyRequestsError{}):
return 8
- case semerr.RequestEntityTooLargeError:
+ case errors.As(err, &semerr.RequestEntityTooLargeError{}):
return 11
- case semerr.UnimplementedError:
+ case errors.As(err, &semerr.UnimplementedError{}):
return 12
- case semerr.ServiceUnavailableError:
+ case errors.As(err, &semerr.ServiceUnavailableError{}):
return 14
- case semerr.UnauthorizedError:
+ case errors.As(err, &semerr.UnauthorizedError{}):
return 16
+ default:
+ return getGRPCErrorCode(err)
}
+}
- code := status.Code(err)
- if code != codes.OK && code != codes.Unknown {
- return code
+func getGRPCErrorCode(err error) codes.Code {
+ var errGRPC interface {
+ GRPCStatus() *status.Status
+ error
}
- if err = errors.Unwrap(err); err == nil {
- return codes.Unknown
+ if errors.As(err, &errGRPC) {
+ status := errGRPC.GRPCStatus()
+
+ if status == nil {
+ return codes.Unknown
+ }
+
+ code := status.Code()
+ if code != codes.OK && code != codes.Unknown {
+ return code
+ }
}
- return Code(err)
+ return codes.Unknown
}
// Wrap wraps the `err` with an error corresponding to the `code`.
diff --git a/pkg/v1/grpcerr/grpcerr_generated.go.tmpl b/pkg/v1/grpcerr/grpcerr_generated.go.tmpl
index 197be84..afa03b8 100644
--- a/pkg/v1/grpcerr/grpcerr_generated.go.tmpl
+++ b/pkg/v1/grpcerr/grpcerr_generated.go.tmpl
@@ -11,27 +11,41 @@ import (
"google.golang.org/grpc/status"
)
-// Code returns grpc status code for err.
+// Code returns grpc status code for err. In case of joined errors
+// it returns the first code found in the chain.
func Code(err error) codes.Code {
- switch err.(type) {
- case nil:
+ switch {
+ case err == nil:
return codes.OK
{{- range $errorDef := . }}
- case semerr.{{ $errorDef.Name }}:
+ case errors.As(err, &semerr.{{ $errorDef.Name }}{}):
return {{ $errorDef.GRPCStatus }}
{{- end }}
+ default:
+ return getGRPCErrorCode(err)
}
+}
- code := status.Code(err)
- if code != codes.OK && code != codes.Unknown {
- return code
+func getGRPCErrorCode(err error) codes.Code {
+ var errGRPC interface {
+ GRPCStatus() *status.Status
+ error
}
- if err = errors.Unwrap(err); err == nil {
- return codes.Unknown
+ if errors.As(err, &errGRPC) {
+ status := errGRPC.GRPCStatus()
+
+ if status == nil {
+ return codes.Unknown
+ }
+
+ code := status.Code()
+ if code != codes.OK && code != codes.Unknown {
+ return code
+ }
}
- return Code(err)
+ return codes.Unknown
}
// Wrap wraps the `err` with an error corresponding to the `code`.
diff --git a/pkg/v1/grpcerr/grpcerr_generated_test.go b/pkg/v1/grpcerr/grpcerr_generated_test.go
index 04ab2d8..e854b10 100755
--- a/pkg/v1/grpcerr/grpcerr_generated_test.go
+++ b/pkg/v1/grpcerr/grpcerr_generated_test.go
@@ -214,3 +214,19 @@ func TestWrap(t *testing.T) {
})
}
}
+
+func TestJoin(t *testing.T) {
+ t.Parallel()
+
+ const err = semerr.Error("some error")
+
+ gotCode := grpcerr.Code(errors.Join(
+ fmt.Errorf("regular: %w", err),
+ fmt.Errorf("bad request: %w", semerr.NewBadRequestError(err)),
+ semerr.NewNotFoundError(fmt.Errorf("not found: %w", err)),
+ ))
+
+ if gotCode != codes.InvalidArgument {
+ t.Fatal("exp", codes.InvalidArgument, "got", gotCode)
+ }
+}
diff --git a/pkg/v1/grpcerr/grpcerr_generated_test.go.tmpl b/pkg/v1/grpcerr/grpcerr_generated_test.go.tmpl
index 6ae186e..3d582df 100644
--- a/pkg/v1/grpcerr/grpcerr_generated_test.go.tmpl
+++ b/pkg/v1/grpcerr/grpcerr_generated_test.go.tmpl
@@ -106,3 +106,19 @@ func TestWrap(t *testing.T) {
})
}
}
+
+func TestJoin(t *testing.T) {
+ t.Parallel()
+
+ const err = semerr.Error("some error")
+
+ gotCode := grpcerr.Code(errors.Join(
+ fmt.Errorf("regular: %w", err),
+ fmt.Errorf("bad request: %w", semerr.NewBadRequestError(err)),
+ semerr.NewNotFoundError(fmt.Errorf("not found: %w", err)),
+ ))
+
+ if gotCode != codes.InvalidArgument {
+ t.Fatal("exp", codes.InvalidArgument, "got", gotCode)
+ }
+}
diff --git a/pkg/v1/httperr/httperr_generated.go b/pkg/v1/httperr/httperr_generated.go
index cbbb1c0..5481b30 100755
--- a/pkg/v1/httperr/httperr_generated.go
+++ b/pkg/v1/httperr/httperr_generated.go
@@ -9,44 +9,41 @@ import (
"github.com/hedhyw/semerr/pkg/v1/semerr"
)
-// Code returns http status code for err.
+// Code returns http status code for err. In case of joined errors
+// it returns the first code found in the chain.
func Code(err error) int {
- switch err.(type) {
- case nil:
+ switch {
+ case err == nil:
return http.StatusOK
- case semerr.StatusRequestTimeoutError:
+ case errors.As(err, &semerr.StatusRequestTimeoutError{}):
return 408
- case semerr.InternalServerError:
+ case errors.As(err, &semerr.InternalServerError{}):
return 500
- case semerr.BadRequestError:
+ case errors.As(err, &semerr.BadRequestError{}):
return 400
- case semerr.UnsupportedMediaTypeError:
+ case errors.As(err, &semerr.UnsupportedMediaTypeError{}):
return 415
- case semerr.StatusGatewayTimeoutError:
+ case errors.As(err, &semerr.StatusGatewayTimeoutError{}):
return 504
- case semerr.NotFoundError:
+ case errors.As(err, &semerr.NotFoundError{}):
return 404
- case semerr.ConflictError:
+ case errors.As(err, &semerr.ConflictError{}):
return 409
- case semerr.ForbiddenError:
+ case errors.As(err, &semerr.ForbiddenError{}):
return 403
- case semerr.TooManyRequestsError:
+ case errors.As(err, &semerr.TooManyRequestsError{}):
return 429
- case semerr.RequestEntityTooLargeError:
+ case errors.As(err, &semerr.RequestEntityTooLargeError{}):
return 413
- case semerr.UnimplementedError:
+ case errors.As(err, &semerr.UnimplementedError{}):
return 501
- case semerr.ServiceUnavailableError:
+ case errors.As(err, &semerr.ServiceUnavailableError{}):
return 503
- case semerr.UnauthorizedError:
+ case errors.As(err, &semerr.UnauthorizedError{}):
return 401
- }
-
- if err = errors.Unwrap(err); err == nil {
+ default:
return http.StatusInternalServerError
}
-
- return Code(err)
}
// Wrap wraps the `err` with an error corresponding to the `code`.
diff --git a/pkg/v1/httperr/httperr_generated.go.tmpl b/pkg/v1/httperr/httperr_generated.go.tmpl
index 0cea779..c1570b2 100644
--- a/pkg/v1/httperr/httperr_generated.go.tmpl
+++ b/pkg/v1/httperr/httperr_generated.go.tmpl
@@ -9,26 +9,21 @@ import (
"github.com/hedhyw/semerr/pkg/v1/semerr"
)
-// Code returns http status code for err.
+// Code returns http status code for err. In case of joined errors
+// it returns the first code found in the chain.
func Code(err error) int {
- switch err.(type) {
- case nil:
+ switch {
+ case err == nil:
return http.StatusOK
{{- range $errorDef := . }}
- case semerr.{{ $errorDef.Name }}:
+ case errors.As(err, &semerr.{{ $errorDef.Name }}{}):
return {{ $errorDef.HTTPStatus }}
{{- end }}
- }
-
- if err = errors.Unwrap(err); err == nil {
+ default:
return http.StatusInternalServerError
}
-
- return Code(err)
}
-
-
// Wrap wraps the `err` with an error corresponding to the `code`.
// If there is no `err` for this code then the `err` will be returned
// without wrapping.
diff --git a/pkg/v1/httperr/httperr_generated_test.go b/pkg/v1/httperr/httperr_generated_test.go
index 7f5726c..107b3b9 100755
--- a/pkg/v1/httperr/httperr_generated_test.go
+++ b/pkg/v1/httperr/httperr_generated_test.go
@@ -208,3 +208,19 @@ func TestWrap(t *testing.T) {
})
}
}
+
+func TestJoin(t *testing.T) {
+ t.Parallel()
+
+ const err = semerr.Error("some error")
+
+ gotCode := httperr.Code(errors.Join(
+ fmt.Errorf("regular: %w", err),
+ fmt.Errorf("bad request: %w", semerr.NewBadRequestError(err)),
+ semerr.NewNotFoundError(fmt.Errorf("not found: %w", err)),
+ ))
+
+ if gotCode != http.StatusBadRequest {
+ t.Fatal("exp", http.StatusBadRequest, "got", gotCode)
+ }
+}
diff --git a/pkg/v1/httperr/httperr_generated_test.go.tmpl b/pkg/v1/httperr/httperr_generated_test.go.tmpl
index 16be805..69c8185 100644
--- a/pkg/v1/httperr/httperr_generated_test.go.tmpl
+++ b/pkg/v1/httperr/httperr_generated_test.go.tmpl
@@ -99,4 +99,20 @@ func TestWrap(t *testing.T) {
}
})
}
-}
\ No newline at end of file
+}
+
+func TestJoin(t *testing.T) {
+ t.Parallel()
+
+ const err = semerr.Error("some error")
+
+ gotCode := httperr.Code(errors.Join(
+ fmt.Errorf("regular: %w", err),
+ fmt.Errorf("bad request: %w", semerr.NewBadRequestError(err)),
+ semerr.NewNotFoundError(fmt.Errorf("not found: %w", err)),
+ ))
+
+ if gotCode != http.StatusBadRequest {
+ t.Fatal("exp", http.StatusBadRequest, "got", gotCode)
+ }
+}
diff --git a/pkg/v1/semerr/multi.go b/pkg/v1/semerr/multi.go
index 7a00141..0a42dfb 100644
--- a/pkg/v1/semerr/multi.go
+++ b/pkg/v1/semerr/multi.go
@@ -25,6 +25,8 @@ func (m MultiErr) Error() string {
// NewMultiError creates a error that can hold multiple errors.
// It skips or nil values. If count of errors is 1, it returns the
// original value. The main error is the first.
+//
+// Deprecated: use errors.Join.
func NewMultiError(errs ...error) error {
if len(errs) == 0 {
return nil
diff --git a/vendor/modules.txt b/vendor/modules.txt
index b336cbb..b092eb9 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -9,7 +9,7 @@ github.com/golang/protobuf/ptypes/timestamp
## explicit; go 1.12
# github.com/rogpeppe/go-internal v1.9.0
## explicit; go 1.17
-# google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514
+# google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4
## explicit; go 1.19
google.golang.org/genproto/googleapis/rpc/status
# google.golang.org/grpc v1.53.0