diff --git a/bdns/dns.go b/bdns/dns.go index ea91a5c4349..d6c050e58b0 100644 --- a/bdns/dns.go +++ b/bdns/dns.go @@ -230,8 +230,8 @@ func (c *impl) exchangeOne(ctx context.Context, hostname string, qtype uint16) ( // Check if the error is a network timeout, rather than a local context // timeout. If it is, retry instead of giving up. - var netErr net.Error - isRetryable := ctx.Err() == nil && errors.As(err, &netErr) && netErr.Timeout() + netErr, isNetError := errors.AsType[net.Error](err) + isRetryable := ctx.Err() == nil && isNetError && netErr.Timeout() hasRetriesLeft := tries < c.maxTries if isRetryable && hasRetriesLeft { continue diff --git a/bdns/problem.go b/bdns/problem.go index 8783743a568..6909f8a10ea 100644 --- a/bdns/problem.go +++ b/bdns/problem.go @@ -98,9 +98,9 @@ var extendedErrorCodeToString = map[uint16]string{ func (d Error) Error() string { var detail, additional string if d.underlying != nil { - var netErr *net.OpError - var urlErr *url.Error - if errors.As(d.underlying, &netErr) { + netErr, netOk := errors.AsType[*net.OpError](d.underlying) + urlErr, urlOk := errors.AsType[*url.Error](d.underlying) + if netOk { if netErr.Timeout() { detail = detailDNSTimeout } else { @@ -108,7 +108,7 @@ func (d Error) Error() string { } // Note: we check d.underlying here even though `Timeout()` does this because the call to `netErr.Timeout()` above only // happens for `*net.OpError` underlying types! - } else if errors.As(d.underlying, &urlErr) && urlErr.Timeout() { + } else if urlOk && urlErr.Timeout() { // For DOH queries, we can get back a `*url.Error` that wraps the unexported type // `http.httpError`. Unfortunately `http.httpError` doesn't wrap any errors (like // context.DeadlineExceeded), we can't check for that; instead we need to call Timeout(). diff --git a/ca/ca.go b/ca/ca.go index 4c80439d37a..5ce5c07d01f 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -104,8 +104,8 @@ func NewCAMetrics(stats prometheus.Registerer) *caMetrics { } func (m *caMetrics) noteSignError(err error) { - var pkcs11Error pkcs11.Error - if errors.As(err, &pkcs11Error) { + _, ok := errors.AsType[pkcs11.Error](err) + if ok { m.signErrorCount.WithLabelValues("HSM").Inc() } } diff --git a/cmd/config.go b/cmd/config.go index 364c74d1e13..853d2b61ffd 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -178,8 +178,8 @@ func (t *TLSConfig) Load(scope prometheus.Registerer) (*tls.Config, error) { []string{"serial"}) err = scope.Register(tlsNotBefore) if err != nil { - are := prometheus.AlreadyRegisteredError{} - if errors.As(err, &are) { + are, ok := errors.AsType[prometheus.AlreadyRegisteredError](err) + if ok { tlsNotBefore = are.ExistingCollector.(*prometheus.GaugeVec) } else { return nil, err @@ -194,8 +194,8 @@ func (t *TLSConfig) Load(scope prometheus.Registerer) (*tls.Config, error) { []string{"serial"}) err = scope.Register(tlsNotAfter) if err != nil { - are := prometheus.AlreadyRegisteredError{} - if errors.As(err, &are) { + are, ok := errors.AsType[prometheus.AlreadyRegisteredError](err) + if ok { tlsNotAfter = are.ExistingCollector.(*prometheus.GaugeVec) } else { return nil, err diff --git a/config/duration.go b/config/duration.go index 3e30c0283e4..65512e6f3c8 100644 --- a/config/duration.go +++ b/config/duration.go @@ -35,8 +35,8 @@ func (d *Duration) UnmarshalJSON(b []byte) error { s := "" err := json.Unmarshal(b, &s) if err != nil { - var jsonUnmarshalTypeErr *json.UnmarshalTypeError - if errors.As(err, &jsonUnmarshalTypeErr) { + _, ok := errors.AsType[*json.UnmarshalTypeError](err) + if ok { return ErrDurationMustBeString } return err diff --git a/core/util_test.go b/core/util_test.go index d485cc2f576..a64a073d73e 100644 --- a/core/util_test.go +++ b/core/util_test.go @@ -269,14 +269,13 @@ func TestValidSerial(t *testing.T) { } func TestLoadCert(t *testing.T) { - var osPathErr *os.PathError _, err := LoadCert("") test.AssertError(t, err, "Loading empty path did not error") - test.AssertErrorWraps(t, err, &osPathErr) + test.AssertErrorWraps[*os.PathError](t, err) _, err = LoadCert("totally/fake/path") test.AssertError(t, err, "Loading nonexistent path did not error") - test.AssertErrorWraps(t, err, &osPathErr) + test.AssertErrorWraps[*os.PathError](t, err) _, err = LoadCert("../test/hierarchy/README.md") test.AssertError(t, err, "Loading non-PEM file did not error") diff --git a/crl/storer/storer.go b/crl/storer/storer.go index 41c1624e25a..23e8bbbeace 100644 --- a/crl/storer/storer.go +++ b/crl/storer/storer.go @@ -166,8 +166,8 @@ func (cs *crlStorer) UploadCRL(stream grpc.ClientStreamingServer[cspb.UploadCRLR Key: &filename, }) if err != nil { - var smithyErr *smithyhttp.ResponseError - if !errors.As(err, &smithyErr) || smithyErr.HTTPStatusCode() != 404 { + smithyErr, ok := errors.AsType[*smithyhttp.ResponseError](err) + if !ok || smithyErr.HTTPStatusCode() != 404 { return fmt.Errorf("getting previous CRL for %s: %w", crlId, err) } cs.log.Infof("No previous CRL found for %s, proceeding", crlId) diff --git a/db/map.go b/db/map.go index e5f65a3a500..e90be068472 100644 --- a/db/map.go +++ b/db/map.go @@ -47,8 +47,11 @@ func (e ErrDatabaseOp) Unwrap() error { // Error 1062: Duplicate entry. This error is returned when inserting a row // would violate a unique key constraint. func IsDuplicate(err error) bool { - var dbErr *mysql.MySQLError - return errors.As(err, &dbErr) && dbErr.Number == 1062 + dbErr, ok := errors.AsType[*mysql.MySQLError](err) + if ok && dbErr.Number == 1062 { + return true + } + return false } // WrappedMap wraps a *borp.DbMap such that its major functions wrap error diff --git a/db/map_test.go b/db/map_test.go index aa1efa4f53b..bba6502257b 100644 --- a/db/map_test.go +++ b/db/map_test.go @@ -202,8 +202,8 @@ func testDbMap(t *testing.T) *WrappedMap { func TestWrappedMap(t *testing.T) { mustDbErr := func(err error) ErrDatabaseOp { t.Helper() - var dbOpErr ErrDatabaseOp - test.AssertErrorWraps(t, err, &dbOpErr) + test.AssertErrorWraps[ErrDatabaseOp](t, err) + dbOpErr, _ := errors.AsType[ErrDatabaseOp](err) return dbOpErr } diff --git a/grpc/client.go b/grpc/client.go index ff658b1ebc1..5959decbe8f 100644 --- a/grpc/client.go +++ b/grpc/client.go @@ -106,8 +106,8 @@ func newClientMetrics(stats prometheus.Registerer) (clientMetrics, error) { ) err := stats.Register(grpcMetrics) if err != nil { - are := prometheus.AlreadyRegisteredError{} - if errors.As(err, &are) { + are, ok := errors.AsType[prometheus.AlreadyRegisteredError](err) + if ok { grpcMetrics = are.ExistingCollector.(*grpc_prometheus.ClientMetrics) } else { return clientMetrics{}, err @@ -121,8 +121,8 @@ func newClientMetrics(stats prometheus.Registerer) (clientMetrics, error) { }, []string{"method", "service"}) err = stats.Register(inFlightGauge) if err != nil { - are := prometheus.AlreadyRegisteredError{} - if errors.As(err, &are) { + are, ok := errors.AsType[prometheus.AlreadyRegisteredError](err) + if ok { inFlightGauge = are.ExistingCollector.(*prometheus.GaugeVec) } else { return clientMetrics{}, err diff --git a/grpc/creds/creds_test.go b/grpc/creds/creds_test.go index 0cbf92b6152..d8bc7ce15c4 100644 --- a/grpc/creds/creds_test.go +++ b/grpc/creds/creds_test.go @@ -57,8 +57,7 @@ func TestServerTransportCredentials(t *testing.T) { err = bcreds.validateClient(tls.ConnectionState{ PeerCertificates: []*x509.Certificate{badCert}, }) - var errSANNotAccepted ErrSANNotAccepted - test.AssertErrorWraps(t, err, &errSANNotAccepted) + test.AssertErrorWraps[ErrSANNotAccepted](t, err) // A creds should accept peers that have a leaf certificate with a SAN // that is on the accepted list @@ -195,6 +194,5 @@ func TestClientReset(t *testing.T) { tc := NewClientCredentials(nil, []tls.Certificate{}, "") _, _, err := tc.ClientHandshake(context.Background(), "T:1010", &brokenConn{}) test.AssertError(t, err, "ClientHandshake succeeded with brokenConn") - var netErr net.Error - test.AssertErrorWraps(t, err, &netErr) + test.AssertErrorWraps[net.Error](t, err) } diff --git a/grpc/errors.go b/grpc/errors.go index 7f9aabbb6cf..107e5b40c18 100644 --- a/grpc/errors.go +++ b/grpc/errors.go @@ -25,8 +25,8 @@ func wrapError(ctx context.Context, appErr error) error { return nil } - var berr *berrors.BoulderError - if errors.As(appErr, &berr) { + berr, ok := errors.AsType[*berrors.BoulderError](appErr) + if ok { pairs := []string{ "errortype", strconv.Itoa(int(berr.Type)), } @@ -92,8 +92,8 @@ func unwrapError(err error, md metadata.MD) error { ) } inErr := berrors.New(berrors.ErrorType(inErrType), inErrMsg) - var outErr *berrors.BoulderError - if !errors.As(inErr, &outErr) { + outErr, ok := errors.AsType[*berrors.BoulderError](inErr) + if !ok { return fmt.Errorf( "expected type of inErr to be %T got %T: %q", outErr, diff --git a/grpc/errors_test.go b/grpc/errors_test.go index 21b2f863e54..ebb0abaa1b4 100644 --- a/grpc/errors_test.go +++ b/grpc/errors_test.go @@ -58,8 +58,7 @@ func TestErrorWrapping(t *testing.T) { _, err = client.Chill(context.Background(), &test_proto.Time{}) test.Assert(t, err != nil, fmt.Sprintf("nil error returned, expected: %s", err)) test.AssertDeepEquals(t, err, es.err) - var bErr *berrors.BoulderError - ok := errors.As(err, &bErr) + bErr, ok := errors.AsType[*berrors.BoulderError](err) test.Assert(t, ok, "asserting error as boulder error") // Ensure we got a RateLimitError test.AssertErrorIs(t, bErr, berrors.RateLimit) diff --git a/grpc/server.go b/grpc/server.go index b17ce0d366d..eaf83a9694e 100644 --- a/grpc/server.go +++ b/grpc/server.go @@ -327,8 +327,8 @@ func newServerMetrics(stats prometheus.Registerer) (serverMetrics, error) { ) err := stats.Register(grpcMetrics) if err != nil { - are := prometheus.AlreadyRegisteredError{} - if errors.As(err, &are) { + are, ok := errors.AsType[prometheus.AlreadyRegisteredError](err) + if ok { grpcMetrics = are.ExistingCollector.(*grpc_prometheus.ServerMetrics) } else { return serverMetrics{}, err @@ -345,8 +345,8 @@ func newServerMetrics(stats prometheus.Registerer) (serverMetrics, error) { }) err = stats.Register(rpcLag) if err != nil { - are := prometheus.AlreadyRegisteredError{} - if errors.As(err, &are) { + are, ok := errors.AsType[prometheus.AlreadyRegisteredError](err) + if ok { rpcLag = are.ExistingCollector.(prometheus.Histogram) } else { return serverMetrics{}, err diff --git a/policy/pa.go b/policy/pa.go index ef94864afc3..822863a7da6 100644 --- a/policy/pa.go +++ b/policy/pa.go @@ -404,8 +404,8 @@ func ValidEmail(address string) error { // subError returns an appropriately typed error based on the input error func subError(ident identifier.ACMEIdentifier, err error) berrors.SubBoulderError { - var bErr *berrors.BoulderError - if errors.As(err, &bErr) { + bErr, ok := errors.AsType[*berrors.BoulderError](err) + if ok { return berrors.SubBoulderError{ Identifier: ident, BoulderError: bErr, diff --git a/policy/pa_test.go b/policy/pa_test.go index c6b7796a992..2ee368c3741 100644 --- a/policy/pa_test.go +++ b/policy/pa_test.go @@ -1,6 +1,7 @@ package policy import ( + "errors" "fmt" "net/netip" "os" @@ -167,8 +168,8 @@ func TestWellFormedIdentifiers(t *testing.T) { test.AssertNil(t, err, fmt.Sprintf("Unexpected error for %q identifier %q, got %s", tc.ident.Type, tc.ident.Value, err)) } else { test.AssertError(t, err, fmt.Sprintf("Expected error for %q identifier %q, but got none", tc.ident.Type, tc.ident.Value)) - var berr *berrors.BoulderError - test.AssertErrorWraps(t, err, &berr) + test.AssertErrorWraps[*berrors.BoulderError](t, err) + berr, _ := errors.AsType[*berrors.BoulderError](err) test.AssertContains(t, berr.Error(), tc.err.Error()) } } @@ -263,9 +264,9 @@ func TestWillingToIssue(t *testing.T) { for _, ident := range shouldBeBlocked { err := pa.WillingToIssue(identifier.ACMEIdentifiers{ident}) test.AssertError(t, err, "identifier was not correctly forbidden") - var berr *berrors.BoulderError - test.AssertErrorWraps(t, err, &berr) - test.AssertContains(t, berr.Detail, errPolicyForbidden.Error()) + test.AssertErrorWraps[*berrors.BoulderError](t, err) + berr, _ := errors.AsType[*berrors.BoulderError](err) + test.AssertContains(t, berr.Error(), errPolicyForbidden.Error()) } // Test acceptance of good identifiers @@ -361,8 +362,8 @@ func TestWillingToIssue_Wildcards(t *testing.T) { test.AssertNil(t, err, fmt.Sprintf("Unexpected error for domain %q, got %s", tc.Domain, err)) } else { test.AssertError(t, err, fmt.Sprintf("Expected error for domain %q, but got none", tc.Domain)) - var berr *berrors.BoulderError - test.AssertErrorWraps(t, err, &berr) + test.AssertErrorWraps[*berrors.BoulderError](t, err) + berr, _ := errors.AsType[*berrors.BoulderError](err) test.AssertContains(t, berr.Error(), tc.ExpectedErr.Error()) } }) diff --git a/publisher/publisher.go b/publisher/publisher.go index 9b4ac9f4a51..de88bff92b4 100644 --- a/publisher/publisher.go +++ b/publisher/publisher.go @@ -259,8 +259,8 @@ func (pub *Impl) SubmitToSingleCTWithResult(ctx context.Context, req *pubpb.Requ return nil, err } var body string - var rspErr jsonclient.RspError - if errors.As(err, &rspErr) && rspErr.StatusCode < 500 { + rspErr, ok := errors.AsType[jsonclient.RspError](err) + if ok && rspErr.StatusCode < 500 { body = string(rspErr.Body) } pub.log.InfoObject("Failed to submit certificate to CT log", struct { @@ -302,8 +302,8 @@ func (pub *Impl) singleLogSubmit( status = "canceled" } httpStatus := "" - var rspError ctClient.RspError - if errors.As(err, &rspError) && rspError.StatusCode != 0 { + rspError, ok := errors.AsType[ctClient.RspError](err) + if ok && rspError.StatusCode != 0 { httpStatus = fmt.Sprintf("%d", rspError.StatusCode) } pub.metrics.submissionLatency.With(prometheus.Labels{ diff --git a/ra/ra.go b/ra/ra.go index ca446528759..26552496021 100644 --- a/ra/ra.go +++ b/ra/ra.go @@ -799,8 +799,8 @@ func (ra *RegistrationAuthorityImpl) recheckCAA(ctx context.Context, authzs []*c // identifier from the authorization that was checked. err := recheckResult.err if err != nil { - var bErr *berrors.BoulderError - if errors.As(err, &bErr) && bErr.Type == berrors.CAA { + bErr, ok := errors.AsType[*berrors.BoulderError](err) + if ok && bErr.Type == berrors.CAA { subErrors = append(subErrors, berrors.SubBoulderError{ Identifier: recheckResult.authz.Identifier, BoulderError: bErr}) diff --git a/ra/ra_test.go b/ra/ra_test.go index 3194ff62ed9..1e6711d8b6b 100644 --- a/ra/ra_test.go +++ b/ra/ra_test.go @@ -1257,8 +1257,8 @@ func TestRecheckCAAFail(t *testing.T) { err := ra.recheckCAA(context.Background(), authzs) test.AssertError(t, err, "expected err, got nil") - var berr *berrors.BoulderError - test.AssertErrorWraps(t, err, &berr) + test.AssertErrorWraps[*berrors.BoulderError](t, err) + berr, _ := errors.AsType[*berrors.BoulderError](err) test.AssertErrorIs(t, berr, berrors.CAA) test.AssertEquals(t, len(berr.SubErrors), 2) @@ -1291,7 +1291,9 @@ func TestRecheckCAAFail(t *testing.T) { // It should error test.AssertError(t, err, "expected err from recheckCAA") // It should be a berror - test.AssertErrorWraps(t, err, &berr) + test.AssertErrorWraps[*berrors.BoulderError](t, err) + // Unwrap this err + berr, _ = errors.AsType[*berrors.BoulderError](err) // There should be *no* suberrors because there was only one overall error test.AssertEquals(t, len(berr.SubErrors), 0) } diff --git a/ratelimits/source_redis.go b/ratelimits/source_redis.go index 05562bb8869..1aa52b8c7ff 100644 --- a/ratelimits/source_redis.go +++ b/ratelimits/source_redis.go @@ -57,8 +57,8 @@ func resultForError(err error) string { // Caller canceled the operation. return "canceled" } - var netErr net.Error - if errors.As(err, &netErr) && netErr.Timeout() { + netErr, ok := errors.AsType[net.Error](err) + if ok && netErr.Timeout() { // Dialer timed out connecting to Redis. return "timeout" } diff --git a/redis/lookup.go b/redis/lookup.go index f66ed7450a3..2621306b277 100644 --- a/redis/lookup.go +++ b/redis/lookup.go @@ -118,8 +118,8 @@ func newLookup(srvLookups []cmd.ServiceDomain, dnsAuthority string, frequency ti func (look *lookup) updateNow(ctx context.Context) (tempError, nonTempError error) { var tempErrs []error handleDNSError := func(err error, srv cmd.ServiceDomain) { - var dnsErr *net.DNSError - if errors.As(err, &dnsErr) && (dnsErr.IsTimeout || dnsErr.IsTemporary) { + dnsErr, ok := errors.AsType[*net.DNSError](err) + if ok && (dnsErr.IsTimeout || dnsErr.IsTemporary) { tempErrs = append(tempErrs, err) return } diff --git a/redis/metrics.go b/redis/metrics.go index 1a7c0487852..85cb9e64992 100644 --- a/redis/metrics.go +++ b/redis/metrics.go @@ -93,8 +93,8 @@ func MustRegisterClientMetricsCollector(client poolStatGetter, stats prometheus. } err := stats.Register(newClientMetricsCollector(client, labels)) if err != nil { - are := prometheus.AlreadyRegisteredError{} - if errors.As(err, &are) { + _, ok := errors.AsType[prometheus.AlreadyRegisteredError](err) + if ok { // The collector is already registered using the same labels. return } diff --git a/sa/model_test.go b/sa/model_test.go index 0670b5703c0..73ef55a180f 100644 --- a/sa/model_test.go +++ b/sa/model_test.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "database/sql" + "errors" "fmt" "math/big" "net/netip" @@ -220,8 +221,8 @@ func TestModelToOrderBadJSON(t *testing.T) { Error: badJSON, }) test.AssertError(t, err, "expected error from modelToOrderv2") - var badJSONErr errBadJSON - test.AssertErrorWraps(t, err, &badJSONErr) + test.AssertErrorWraps[errBadJSON](t, err) + badJSONErr, _ := errors.AsType[errBadJSON](err) test.AssertEquals(t, string(badJSONErr.json), string(badJSON)) } @@ -288,8 +289,8 @@ func TestPopulateAttemptedFieldsBadJSON(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { err := populateAttemptedFields(*tc.Model, &corepb.Challenge{}) test.AssertError(t, err, "expected error from populateAttemptedFields") - var badJSONErr errBadJSON - test.AssertErrorWraps(t, err, &badJSONErr) + test.AssertErrorWraps[errBadJSON](t, err) + badJSONErr, _ := errors.AsType[errBadJSON](err) test.AssertEquals(t, string(badJSONErr.json), string(badJSON)) }) } diff --git a/sa/sa_test.go b/sa/sa_test.go index 3460fb4fd7f..d9040e1de70 100644 --- a/sa/sa_test.go +++ b/sa/sa_test.go @@ -2980,8 +2980,8 @@ func TestSerialsForIncident(t *testing.T) { test.AssertError(t, err, "Expected error for nonexistent table name") // Assert that the error is a MySQL error so we can inspect the error code. - var mysqlErr *mysql.MySQLError - if errors.As(err, &mysqlErr) { + mysqlErr, ok := errors.AsType[*mysql.MySQLError](err) + if ok { // We expect the error code to be 1146 (ER_NO_SUCH_TABLE): // https://mariadb.com/kb/en/mariadb-error-codes/ test.AssertEquals(t, mysqlErr.Number, uint16(1146)) diff --git a/sa/type-converter_test.go b/sa/type-converter_test.go index 8ca7d35d199..904747e5adc 100644 --- a/sa/type-converter_test.go +++ b/sa/type-converter_test.go @@ -2,6 +2,7 @@ package sa import ( "encoding/json" + "errors" "testing" "time" @@ -47,8 +48,8 @@ func TestAcmeIdentifierBadJSON(t *testing.T) { scanner, _ := tc.FromDb(&out) err := scanner.Binder(&badJSON, &out) test.AssertError(t, err, "expected error from scanner.Binder") - var badJSONErr errBadJSON - test.AssertErrorWraps(t, err, &badJSONErr) + test.AssertErrorWraps[errBadJSON](t, err) + badJSONErr, _ := errors.AsType[errBadJSON](err) test.AssertEquals(t, string(badJSONErr.json), badJSON) } @@ -84,8 +85,8 @@ func TestJSONWebKeyBadJSON(t *testing.T) { scanner, _ := tc.FromDb(&out) err := scanner.Binder(&badJSON, &out) test.AssertError(t, err, "expected error from scanner.Binder") - var badJSONErr errBadJSON - test.AssertErrorWraps(t, err, &badJSONErr) + test.AssertErrorWraps[errBadJSON](t, err) + badJSONErr, _ := errors.AsType[errBadJSON](err) test.AssertEquals(t, string(badJSONErr.json), badJSON) } diff --git a/sfe/overrides.go b/sfe/overrides.go index d4c47609615..5292ea4f7d7 100644 --- a/sfe/overrides.go +++ b/sfe/overrides.go @@ -557,8 +557,8 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response err = d.Result(sfe.clk.Now()) if err != nil { - var bErr *berrors.BoulderError - if errors.As(err, &bErr) && bErr.Type == berrors.RateLimit { + bErr, ok := errors.AsType[*berrors.BoulderError](err) + if ok && bErr.Type == berrors.RateLimit { http.Error(w, bErr.Detail, http.StatusTooManyRequests) return } diff --git a/test/asserts.go b/test/asserts.go index b87490f0dcb..bf6937702cb 100644 --- a/test/asserts.go +++ b/test/asserts.go @@ -83,11 +83,11 @@ func AssertError(t *testing.T, err error, message string) { } // AssertErrorWraps checks that err can be unwrapped into the given target. -// NOTE: Has the side effect of actually performing that unwrapping. -func AssertErrorWraps(t *testing.T, err error, target any) { +func AssertErrorWraps[E error](t *testing.T, err error) { t.Helper() - if !errors.As(err, target) { - t.Fatalf("error does not wrap an error of the expected type: %q !> %+T", err.Error(), target) + target, ok := errors.AsType[E](err) + if !ok { + t.Fatalf("error does not wrap an error of the expected type: %q !> %T", err.Error(), target) } } diff --git a/test/integration/errors_test.go b/test/integration/errors_test.go index ad03f0d7be7..83eab5f71a4 100644 --- a/test/integration/errors_test.go +++ b/test/integration/errors_test.go @@ -37,8 +37,8 @@ func TestTooBigOrderError(t *testing.T) { _, err := authAndIssue(nil, nil, idents, true, "") test.AssertError(t, err, "authAndIssue failed") - var prob acme.Problem - test.AssertErrorWraps(t, err, &prob) + test.AssertErrorWraps[acme.Problem](t, err) + prob, _ := errors.AsType[acme.Problem](err) test.AssertEquals(t, prob.Type, "urn:ietf:params:acme:error:malformed") test.AssertContains(t, prob.Detail, "Order cannot contain more than 100 identifiers") } @@ -113,10 +113,10 @@ func TestAccountEmailError(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - var prob acme.Problem _, err := makeClient(tc.contacts...) if err != nil { - test.AssertErrorWraps(t, err, &prob) + test.AssertErrorWraps[acme.Problem](t, err) + prob, _ := errors.AsType[acme.Problem](err) test.AssertEquals(t, prob.Type, tc.expectedProbType) test.AssertContains(t, prob.Detail, "Error validating contact(s)") test.AssertContains(t, prob.Detail, tc.expectedProbDetail) @@ -137,8 +137,8 @@ func TestRejectedIdentifier(t *testing.T) { } _, err := authAndIssue(nil, nil, idents, true, "") test.AssertError(t, err, "issuance should fail for one malformed name") - var prob acme.Problem - test.AssertErrorWraps(t, err, &prob) + test.AssertErrorWraps[acme.Problem](t, err) + prob, _ := errors.AsType[acme.Problem](err) test.AssertEquals(t, prob.Type, "urn:ietf:params:acme:error:rejectedIdentifier") test.AssertContains(t, prob.Detail, "Domain name contains an invalid character") @@ -155,7 +155,8 @@ func TestRejectedIdentifier(t *testing.T) { } _, err = authAndIssue(nil, nil, idents, true, "") test.AssertError(t, err, "issuance should fail for multiple malformed names") - test.AssertErrorWraps(t, err, &prob) + test.AssertErrorWraps[acme.Problem](t, err) + prob, _ = errors.AsType[acme.Problem](err) test.AssertEquals(t, prob.Type, "urn:ietf:params:acme:error:rejectedIdentifier") test.AssertContains(t, prob.Detail, "Domain name contains an invalid character") test.AssertContains(t, prob.Detail, "and 4 more problems") @@ -276,8 +277,7 @@ func TestOrderFinalizeEarly(t *testing.T) { if err == nil { t.Fatal("expected finalize to fail, but got success") } - var prob acme.Problem - ok := errors.As(err, &prob) + prob, ok := errors.AsType[acme.Problem](err) if !ok { t.Fatalf("expected error to be of type acme.Problem, got: %T", err) } diff --git a/tools/release/branch/main.go b/tools/release/branch/main.go index 898214f481d..8fe45bdb1be 100644 --- a/tools/release/branch/main.go +++ b/tools/release/branch/main.go @@ -62,8 +62,8 @@ func show(output string) { func main() { err := branch(os.Args[1:]) if err != nil { - var cmdErr cmdError - if errors.As(err, &cmdErr) { + cmdErr, ok := errors.AsType[cmdError](err) + if ok { show(cmdErr.output) } fmt.Println(err.Error()) diff --git a/tools/release/tag/main.go b/tools/release/tag/main.go index d212f4b364d..39be2be25bd 100644 --- a/tools/release/tag/main.go +++ b/tools/release/tag/main.go @@ -68,8 +68,8 @@ func show(output string) { func main() { err := tag(os.Args[1:]) if err != nil { - var cmdErr cmdError - if errors.As(err, &cmdErr) { + cmdErr, ok := errors.AsType[cmdError](err) + if ok { show(cmdErr.output) } fmt.Println(err.Error()) diff --git a/va/http.go b/va/http.go index ab611331fa7..f6ec25fb565 100644 --- a/va/http.go +++ b/va/http.go @@ -423,9 +423,12 @@ func fallbackErr(err error) bool { return false } // Net OpErrors are fallback errs only if the operation was a "dial" + netOpError, ok := errors.AsType[*net.OpError](err) + if ok && netOpError.Op == "dial" { + return true + } // All other errs are not fallback errs - var netOpError *net.OpError - return errors.As(err, &netOpError) && netOpError.Op == "dial" + return false } // processHTTPValidation performs an HTTP validation for the given host, port diff --git a/va/va.go b/va/va.go index 0107524f6fc..971568d3ce8 100644 --- a/va/va.go +++ b/va/va.go @@ -390,8 +390,8 @@ func (i ipError) Error() string { // meaningful. It additionally handles `berrors.ConnectionFailure` errors by // passing through the detailed message. func detailedError(err error) *probs.ProblemDetails { - var ipErr ipError - if errors.As(err, &ipErr) { + ipErr, ok := errors.AsType[ipError](err) + if ok { detailedErr := detailedError(ipErr.err) if (ipErr.ip == netip.Addr{}) { // This should never happen. @@ -402,20 +402,20 @@ func detailedError(err error) *probs.ProblemDetails { return detailedErr } // net/http wraps net.OpError in a url.Error. Unwrap them. - var urlErr *url.Error - if errors.As(err, &urlErr) { + urlErr, ok := errors.AsType[*url.Error](err) + if ok { prob := detailedError(urlErr.Err) prob.Detail = fmt.Sprintf("Fetching %s: %s", urlErr.URL, prob.Detail) return prob } - var tlsErr tls.RecordHeaderError - if errors.As(err, &tlsErr) && bytes.Equal(tlsErr.RecordHeader[:], badTLSHeader) { + tlsErr, ok := errors.AsType[tls.RecordHeaderError](err) + if ok && bytes.Equal(tlsErr.RecordHeader[:], badTLSHeader) { return probs.Malformed("Server only speaks HTTP, not TLS") } - var netOpErr *net.OpError - if errors.As(err, &netOpErr) { + netOpErr, ok := errors.AsType[*net.OpError](err) + if ok { if fmt.Sprintf("%T", netOpErr.Err) == "tls.alert" { // All the tls.alert error strings are reasonable to hand back to a // user. Confirmed against Go 1.8. @@ -426,8 +426,8 @@ func detailedError(err error) *probs.ProblemDetails { return probs.Connection(fmt.Sprintf("Timeout during %s (your server may be slow or overloaded)", netOpErr.Op)) } } - var syscallErr *os.SyscallError - if errors.As(err, &syscallErr) { + syscallErr, ok := errors.AsType[*os.SyscallError](err) + if ok { switch syscallErr.Err { case syscall.ECONNREFUSED: return probs.Connection("Connection refused") @@ -437,8 +437,8 @@ func detailedError(err error) *probs.ProblemDetails { return probs.Connection("Connection reset by peer") } } - var netErr net.Error - if errors.As(err, &netErr) && netErr.Timeout() { + netErr, ok := errors.AsType[net.Error](err) + if ok && netErr.Timeout() { return probs.Connection("Timeout after connect (your server may be slow or overloaded)") } if errors.Is(err, berrors.ConnectionFailure) { diff --git a/web/probs.go b/web/probs.go index 236375e2e0e..926aab0ff9a 100644 --- a/web/probs.go +++ b/web/probs.go @@ -81,8 +81,8 @@ func problemDetailsForBoulderError(err *berrors.BoulderError, msg string) *probs // of an type unknown to ProblemDetailsForError, it will return a ServerInternal // ProblemDetails. func ProblemDetailsForError(err error, msg string) *probs.ProblemDetails { - var bErr *berrors.BoulderError - if errors.As(err, &bErr) { + bErr, ok := errors.AsType[*berrors.BoulderError](err) + if ok { return problemDetailsForBoulderError(bErr, msg) } else { // Internal server error messages may include sensitive data, so we do diff --git a/wfe2/verify.go b/wfe2/verify.go index 88ff4864f15..a6f84d057b4 100644 --- a/wfe2/verify.go +++ b/wfe2/verify.go @@ -374,8 +374,8 @@ func (wfe *WebFrontEndImpl) parseJWS(body []byte) (*bJSONWebSignature, error) { bodyStr := string(body) parsedJWS, err := jose.ParseSigned(bodyStr, getSupportedAlgs()) if err != nil { - var unexpectedSignAlgoErr *jose.ErrUnexpectedSignatureAlgorithm - if errors.As(err, &unexpectedSignAlgoErr) { + unexpectedSignAlgoErr, ok := errors.AsType[*jose.ErrUnexpectedSignatureAlgorithm](err) + if ok { wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSAlgorithmCheckFailed"}).Inc() return nil, berrors.BadSignatureAlgorithmError( "JWS signature header contains unsupported algorithm %q, expected one of %s", diff --git a/wfe2/verify_test.go b/wfe2/verify_test.go index e861a43f014..1b62c3cfa5f 100644 --- a/wfe2/verify_test.go +++ b/wfe2/verify_test.go @@ -1188,8 +1188,7 @@ func TestLookupJWK(t *testing.T) { test.AssertMarshaledEquals(t, gotAcct, tc.WantAccount) test.AssertEquals(t, inputLogEvent.Requester, gotAcct.ID) } else { - var berr *berrors.BoulderError - ok := errors.As(gotErr, &berr) + berr, ok := errors.AsType[*berrors.BoulderError](gotErr) if !ok { t.Fatalf("lookupJWK(%#v) returned %T, want BoulderError", in, gotErr) } diff --git a/wfe2/wfe.go b/wfe2/wfe.go index 0665db0960d..03b7740ad32 100644 --- a/wfe2/wfe.go +++ b/wfe2/wfe.go @@ -643,8 +643,8 @@ func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *we prob.Algorithms = getSupportedAlgs() } - var bErr *berrors.BoulderError - if errors.As(ierr, &bErr) { + bErr, ok := errors.AsType[*berrors.BoulderError](ierr) + if ok { retryAfterSeconds := int(bErr.RetryAfter.Round(time.Second).Seconds()) if retryAfterSeconds > 0 { response.Header().Add(headerRetryAfter, strconv.Itoa(retryAfterSeconds))