Repository: goadesign/goa
Pull request #3249 (2023-02-24 11:51:36)

Use errors.Join() instead of go-multierror
[errors.Join](https://pkg.go.dev/errors#Join) was added in Go 1.20.
This pull request replaces [go-multierror](https://pkg.go.dev/github.com/hashicorp/go-multierror) with it.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0155ea70..2531b8e1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,10 +11,10 @@ jobs:
       uses: runforesight/foresight-workflow-kit-action@v1
       if: success() || failure()
 
-    - name: Set up Go 1.19
+    - name: Set up Go 1.20
       uses: actions/setup-go@v3.5.0
       with:
-        go-version: 1.19
+        go-version: '1.20'
       id: go
 
     - name: Check out code into the Go module directory
@@ -55,10 +55,10 @@ jobs:
       uses: runforesight/foresight-workflow-kit-action@v1
       if: success() || failure()
 
-    - name: Set up Go 1.19
+    - name: Set up Go 1.20
       uses: actions/setup-go@v3.5.0
       with:
-        go-version: 1.19
+        go-version: '1.20'
       id: go
 
     - name: Check out code into the Go module directory
@@ -90,4 +90,4 @@ jobs:
         test_framework: GOLANG
         test_path: test-report.json
         coverage_format: GOLANG
-        coverage_path: .\coverage
\ No newline at end of file
+        coverage_path: .\coverage
diff --git a/Makefile b/Makefile
index bdff24f9..5a94607b 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@
 #
 MAJOR=3
 MINOR=11
-BUILD=0
+BUILD=1
 
 GOOS=$(shell go env GOOS)
 GO_FILES=$(shell find . -type f -name '*.go')
diff --git a/README.md b/README.md
index 5cb7fbea..4b44e413 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 ## Overview
 
-[![GoDev](https://img.shields.io/badge/go-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/goa.design/goa/v3@v3.11.0/dsl?tab=doc)
+[![GoDev](https://img.shields.io/badge/go-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/goa.design/goa/v3@v3.11.1/dsl?tab=doc)
 [![Foresight](https://api-public.service.runforesight.com/api/v1/badge/test?repoId=67defc2e-b3f8-4043-bac7-a4e215518ed3)](http://goa.app.runforesight.com/)
 [![Go Report Card](https://goreportcard.com/badge/goadesign/goa)](https://goreportcard.com/report/goadesign/goa)
 [![Slack](https://img.shields.io/badge/Slack-gray.svg?longCache=true&logo=slack&colorB=red)](https://gophers.slack.com/messages/goa)
@@ -86,7 +86,7 @@ invoking the client code.
 go install goa.design/goa/v3/cmd/goa@v3
 ```
 
-Current Release: `v3.11.0`
+Current Release: `v3.11.1`
 
 ## Teaser
 
@@ -352,7 +352,7 @@ In particular the page
 explains how to leverage the generated code to implement an HTTP or gRPC
 service.
 
-The [![DSL GoDoc](https://img.shields.io/badge/godoc-DSL-blue)](https://pkg.go.dev/goa.design/goa/v3@v3.11.0/dsl?tab=doc)
+The [![DSL GoDoc](https://img.shields.io/badge/godoc-DSL-blue)](https://pkg.go.dev/goa.design/goa/v3@v3.11.1/dsl?tab=doc)
 contains a fully documented reference of all the DSL functions.
 
 ### Instrumentation and System Example
diff --git a/go.mod b/go.mod
index 31025d91..f9788ca0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module goa.design/goa/v3
 
-go 1.19
+go 1.20
 
 require (
 	github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598
@@ -8,7 +8,6 @@ require (
 	github.com/getkin/kin-openapi v0.112.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/websocket v1.5.0
-	github.com/hashicorp/go-multierror v1.1.1
 	github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d
 	github.com/pkg/errors v0.9.1
 	github.com/sergi/go-diff v1.3.1
@@ -25,7 +24,6 @@ require (
 	github.com/go-openapi/swag v0.19.13 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4 // indirect
-	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/invopop/yaml v0.1.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/mailru/easyjson v0.7.6 // indirect
diff --git a/go.sum b/go.sum
index 7374a4e7..17937da4 100644
--- a/go.sum
+++ b/go.sum
@@ -27,11 +27,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
 github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
diff --git a/middleware/sampler.go b/middleware/sampler.go
index 4af61b1b..5a346aa1 100644
--- a/middleware/sampler.go
+++ b/middleware/sampler.go
@@ -32,6 +32,8 @@ const (
 	adaptiveUpperBoundFloat = float64(adaptiveUpperBoundInt)
 )
 
+var rnd = rand.New(rand.NewSource(1))
+
 // NewAdaptiveSampler computes the interval for sampling for tracing middleware.
 // it can also be used by non-web go routines to trace internal API calls.
 //
@@ -88,11 +90,11 @@ func (s *adaptiveSampler) Sample() bool {
 	}
 
 	// currentRate is never zero.
-	return currentRate == adaptiveUpperBoundInt || rand.Intn(adaptiveUpperBoundInt) < currentRate
+	return currentRate == adaptiveUpperBoundInt || rnd.Intn(adaptiveUpperBoundInt) < currentRate
 }
 
 // Sample implementation for fixed percentage
 func (s fixedSampler) Sample() bool {
 	samplingPercent := int(s)
-	return samplingPercent > 0 && (samplingPercent == 100 || rand.Intn(100) < samplingPercent)
+	return samplingPercent > 0 && (samplingPercent == 100 || rnd.Intn(100) < samplingPercent)
 }
diff --git a/pkg/error.go b/pkg/error.go
index 10f53966..e994241a 100644
--- a/pkg/error.go
+++ b/pkg/error.go
@@ -3,11 +3,10 @@ package goa
 import (
 	"crypto/rand"
 	"encoding/base64"
+	"errors"
 	"fmt"
 	"io"
 	"strings"
-
-	"github.com/hashicorp/go-multierror"
 )
 
 type (
@@ -228,7 +227,7 @@ func MergeErrors(err, other error) error {
 	//
 	// Do this before we modify ourselves, as History() may include us!
 	e.history = append(e.History(), o.History()...)
-	e.err = multierror.Append(e.err, o.err)
+	e.err = errors.Join(e.err, o.err)
 
 	e.Message = e.Message + "; " + o.Message
 	e.Timeout = e.Timeout && o.Timeout
diff --git a/pkg/version.go b/pkg/version.go
index 3f08c93a..b862cb0d 100644
--- a/pkg/version.go
+++ b/pkg/version.go
@@ -12,7 +12,7 @@ const (
 	// Minor version number
 	Minor = 11
 	// Build number
-	Build = 0
+	Build = 1
 	// Suffix - set to empty string in release tag commits.
 	Suffix = ""
 )


Test patch:
diff --git a/middleware/sampler_test.go b/middleware/sampler_test.go
index fa68b404..2cc32e2a 100644
--- a/middleware/sampler_test.go
+++ b/middleware/sampler_test.go
@@ -1,7 +1,6 @@
 package middleware
 
 import (
-	"math/rand"
 	"testing"
 	"time"
 )
@@ -24,7 +23,7 @@ func TestFixedSampler(t *testing.T) {
 	}
 
 	// 50 %
-	rand.Seed(123) // set seed for reproducibility.
+	rnd.Seed(123) // set seed for reproducibility.
 	trueCount := 0
 	subject = NewFixedSampler(33)
 	for i := 0; i < 100; i++ {
@@ -60,7 +59,7 @@ func TestAdaptiveSampler(t *testing.T) {
 
 	// change start time to 1s ago for a more predictable result.
 	trueCount := 0
-	rand.Seed(123) // set seed for reproducibility.
+	rnd.Seed(123) // set seed for reproducibility.
 	now := time.Now()
 	subject.(*adaptiveSampler).start = now.Add(-time.Second)
 	for i := 99; i < 199; i++ {
diff --git a/pkg/error_test.go b/pkg/error_test.go
new file mode 100644
index 00000000..e8a46b60
--- /dev/null
+++ b/pkg/error_test.go
@@ -0,0 +1,44 @@
+package goa
+
+import (
+	"errors"
+	"testing"
+)
+
+func TestServiceErrorUnwrap(t *testing.T) {
+	var (
+		errFoo          = errors.New("foo")
+		errBar          = errors.New("bar")
+		serviceErrorFoo = NewServiceError(errFoo, "foo", false, false, false)
+		serviceErrorBar = NewServiceError(errBar, "bar", false, false, false)
+	)
+	cases := map[string]struct {
+		err  error
+		want error
+	}{
+		"service error": {
+			err:  serviceErrorFoo,
+			want: errFoo,
+		},
+		"merged service error": {
+			err:  MergeErrors(serviceErrorFoo, serviceErrorBar),
+			want: errors.Join(errFoo, errBar),
+		},
+	}
+	for k, tc := range cases {
+		t.Run(k, func(t *testing.T) {
+			got := errors.Unwrap(tc.err)
+			if errs, ok := tc.want.(interface{ Unwrap() []error }); ok {
+				for _, e := range errs.Unwrap() {
+					if !errors.Is(got, e) {
+						t.Errorf("got %#v, want %#v", got, tc.want)
+					}
+				}
+			} else {
+				if !errors.Is(got, tc.want) {
+					t.Errorf("got %#v, want %#v", got, tc.want)
+				}
+			}
+		})
+	}
+}