Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ require (
go.opentelemetry.io/otel v1.45.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.28.0
goa.design/goa/v3 v3.29.2
goa.design/goa/v3 v3.30.0
golang.org/x/crypto v0.55.0
golang.org/x/term v0.45.0
golang.org/x/text v0.41.0
Expand Down Expand Up @@ -186,7 +186,7 @@ require (
google.golang.org/api v0.277.0 // indirect
google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,8 @@ go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
goa.design/goa/v3 v3.29.2 h1:nabAmgYTUsE+NfG4KkMyRl6ddHN2uTAflOHC/FuaTfI=
goa.design/goa/v3 v3.29.2/go.mod h1:CTCdFy0UYeiYuoXmFkUJXji1xGPG7K6XFsO2NqU/QaQ=
goa.design/goa/v3 v3.30.0 h1:KXfAR5qlUCJLVPlHTZ6pHdC5b013BB78PLbDf2fFhfY=
goa.design/goa/v3 v3.30.0/go.mod h1:Is2byRdJddS20jhGT3Ql80N5hQyAc4HgAl7H/A0ZmJ4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
Expand Down Expand Up @@ -625,8 +625,8 @@ google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d h1:N1Ec54vZnIPd7Mn
google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:c2hJ1grtnH0xUiEKGDGkjGNTJ1Hy2LrblyKOHF0sqRM=
google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 h1:4namukbyF7JY83aWHQwi9J5ugNTnDReLJ9ZcpqOpRB4=
google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772/go.mod h1:1brfde68Npq6+WA75c1EHWPijZEG1kMus61ygPZfn4A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754 h1:k5CJw9e5ONCcA/u0webKt092npXuY+KeGh3Q8NAVf0g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
Expand Down
42 changes: 38 additions & 4 deletions vendor/goa.design/goa/v3/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"sort"
"strings"
"syscall"
)

type (
Expand Down Expand Up @@ -59,6 +60,11 @@ func (c ClientError) Unwrap() error {
return c.Err
}

// Retryable reports whether repeating an idempotent request may succeed.
func (c ClientError) Retryable() bool {
return c.Temporary
}

// NewDebugDoer wraps the given doer and captures the request and response so
// they can be printed.
func NewDebugDoer(d Doer) DebugDoer {
Expand Down Expand Up @@ -178,7 +184,14 @@ func ErrInvalidURL(svc, m, u string, err error) error {
// response body.
func ErrDecodingError(svc, m string, err error) error {
msg := fmt.Sprintf("failed to decode response body: %s", err)
return &ClientError{Name: "decoding_error", Message: msg, Service: svc, Method: m, Err: err}
return &ClientError{
Name: "decoding_error",
Message: msg,
Service: svc,
Method: m,
Temporary: retryableRequestError(err),
Err: err,
}
}

// ErrValidationError is the error returned when the response body is properly
Expand All @@ -197,9 +210,12 @@ func ErrInvalidResponse(svc, m string, code int, body string) error {
}
msg := fmt.Sprintf("invalid response code %#v"+b+"%s", code, body)

temporary := code == http.StatusServiceUnavailable ||
temporary := code == http.StatusRequestTimeout ||
code == http.StatusConflict ||
code == http.StatusTooEarly ||
code == http.StatusTooManyRequests ||
code == http.StatusBadGateway ||
code == http.StatusServiceUnavailable ||
code == http.StatusGatewayTimeout

timeout := code == http.StatusRequestTimeout ||
Expand All @@ -215,12 +231,30 @@ func ErrInvalidResponse(svc, m string, code int, body string) error {

// ErrRequestError is the error returned when the request fails to be sent.
func ErrRequestError(svc, m string, err error) error {
temporary := false
timeout := false
var nerr net.Error
if errors.As(err, &nerr) {
timeout = nerr.Timeout()
}
return &ClientError{Name: "request_error", Message: err.Error(), Service: svc, Method: m,
Temporary: temporary, Timeout: timeout, Err: err}
Temporary: retryableRequestError(err), Timeout: timeout, Err: err}
}

// retryableRequestError recognizes connection and response-body failures that
// can succeed when a replay-safe request uses a new connection.
func retryableRequestError(err error) bool {
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
return true
}
if errors.Is(err, syscall.ECONNRESET) ||
errors.Is(err, syscall.ECONNREFUSED) ||
errors.Is(err, syscall.EPIPE) {
return true
}
var dnserr *net.DNSError
if errors.As(err, &dnserr) && dnserr.IsTemporary {
return true
}
var nerr net.Error
return errors.As(err, &nerr) && nerr.Timeout()
}
70 changes: 70 additions & 0 deletions vendor/goa.design/goa/v3/pkg/retry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// This file implements the retry behavior used by generated clients. The
// transport packages preserve whether a failed call can be attempted again;
// this package owns when a typed service invocation is replayed.
package goa

import (
"context"
"errors"
"math/rand/v2"
"time"
)

const retryBackoff = 100 * time.Millisecond

// RetryEndpoint retries endpoint once when the returned error describes a
// temporary failure. Generated clients use temporaryErrorNames for designed
// errors whose retry trait is known from the service design. The original
// context and request value are reused, so callers retain their deadline and
// cancellation behavior.
func RetryEndpoint(endpoint Endpoint, temporaryErrorNames ...string) Endpoint {
return func(ctx context.Context, request any) (any, error) {
response, err := endpoint(ctx, request)
if err == nil || ctx.Err() != nil || !isRetryableError(err, temporaryErrorNames) {
return response, err
}

timer := time.NewTimer(retryDelay())
defer timer.Stop()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-timer.C:
return endpoint(ctx, request)
}
}
}

// isRetryableError recognizes transport errors, Goa's default service error,
// and generated error values named as temporary by the method design.
func isRetryableError(err error, temporaryErrorNames []string) bool {
var serviceError *ServiceError
if errors.As(err, &serviceError) && serviceError.Temporary {
return true
}

var retryable interface {
Retryable() bool
}
if errors.As(err, &retryable) && retryable.Retryable() {
return true
}

var namer GoaErrorNamer
if !errors.As(err, &namer) {
return false
}
name := namer.GoaErrorName()
for _, temporaryName := range temporaryErrorNames {
if name == temporaryName {
return true
}
}
return false
}

// retryDelay spreads concurrent retries over the interval from half to one
// and a half times the base delay.
func retryDelay() time.Duration {
return retryBackoff/2 + time.Duration(rand.Int64N(int64(retryBackoff)))
}
4 changes: 2 additions & 2 deletions vendor/goa.design/goa/v3/pkg/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ const (
// Major version number
Major = 3
// Minor version number
Minor = 29
Minor = 30
// Build number
Build = 2
Build = 0
// Suffix - set to empty string in release tag commits.
Suffix = ""
)
Expand Down
4 changes: 2 additions & 2 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,7 @@ go.yaml.in/yaml/v2
# go.yaml.in/yaml/v3 v3.0.5
## explicit; go 1.16
go.yaml.in/yaml/v3
# goa.design/goa/v3 v3.29.2
# goa.design/goa/v3 v3.30.0
## explicit; go 1.25.0
goa.design/goa/v3/http
goa.design/goa/v3/pkg
Expand Down Expand Up @@ -953,7 +953,7 @@ google.golang.org/genproto/googleapis/api
google.golang.org/genproto/googleapis/api/annotations
google.golang.org/genproto/googleapis/api/expr/v1alpha1
google.golang.org/genproto/googleapis/api/httpbody
# google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754
# google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688
## explicit; go 1.25.0
google.golang.org/genproto/googleapis/rpc/errdetails
google.golang.org/genproto/googleapis/rpc/status
Expand Down
Loading