chore: Change errors.HasType to respect multi-errors (#63024)

With this patch, the `errors.HasType` API behaves similar to `Is` and `As`,
where it checks the full error tree instead of just checking a linearized version
of it, as cockroachdb/errors's `HasType` implementation does not respect
multi-errors.

As a consequence, a bunch of relationships between HasType and Is/As that
you'd intuitively expect to hold are now true; see changes to `invariants_test.go`.
This commit is contained in:
Varun Gandhi 2024-06-06 21:02:14 +08:00 committed by GitHub
parent 9183f528c7
commit 2955bb6cfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 203 additions and 234 deletions

View File

@ -290,7 +290,7 @@ func (s *sourcesSyncHandler) Handle(ctx context.Context) (err error) {
// If another instance is working on background syncs, we don't want to
// do anything. We should check every time still in case the current worker
// goes offline, we want to be ready to pick up the work.
if err := s.rmux.LockContext(ctx); errors.HasType(err, &redsync.ErrTaken{}) {
if err := s.rmux.LockContext(ctx); errors.HasType[*redsync.ErrTaken](err) {
skippedReason = fmt.Sprintf("did not acquire lock, another worker is likely active: %s", err.Error())
handleLogger.Debug(skippedReason)
return nil // ignore lock contention errors

View File

@ -181,7 +181,7 @@ func ListLimitsHandler(baseLogger log.Logger, redisStore limiter.RedisStore) htt
// Capture the current usage.
currentUsage, expiry, err := l.Usage(r.Context())
if err != nil {
if errors.HasType(err, limiter.NoAccessError{}) {
if errors.HasType[limiter.NoAccessError](err) {
// No access to this feature, skip.
continue
}

View File

@ -105,7 +105,7 @@ func setupLoopDevice(
// add the error to the bottom of the step's log output,
// but only if this isnt from exec.Command, as those get added
// by our logging wrapper
if !errors.HasType(err, &exec.ExitError{}) {
if !errors.HasType[*exec.ExitError](err) {
fmt.Fprint(handle, err.Error())
}
handle.Finalize(1)

View File

@ -93,7 +93,7 @@ func (r *GitCommitResolver) resolveCommit(ctx context.Context) (*gitdomain.Commi
}
r.commit, r.commitErr = r.gitserverClient.GetCommit(ctx, r.gitRepo, api.CommitID(r.oid))
if r.commitErr != nil && errors.HasType(r.commitErr, &gitdomain.RevisionNotFoundError{}) {
if r.commitErr != nil && errors.HasType[*gitdomain.RevisionNotFoundError](r.commitErr) {
// If the commit is not found, attempt to do a ensure revision call.
_, err := r.gitserverClient.ResolveRevision(ctx, r.gitRepo, string(r.oid), gitserver.ResolveRevisionOptions{EnsureRevision: true})
if err != nil {

View File

@ -52,7 +52,7 @@ func getUserToInviteToOrganization(ctx context.Context, db database.DB, username
if _, err := db.OrgMembers().GetByOrgIDAndUserID(ctx, orgID, userToInvite.ID); err == nil {
return nil, "", errors.New("user is already a member of the organization")
} else if !errors.HasType(err, &database.ErrOrgMemberNotFound{}) {
} else if !errors.HasType[*database.ErrOrgMemberNotFound](err) {
return nil, "", err
}
return userToInvite, userEmailAddress, nil

View File

@ -110,7 +110,7 @@ index 9bd8209..d2acfa9 100644
if err == nil {
t.Fatal("unexpected empty err")
}
if !errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if !errors.HasType[*gitdomain.RevisionNotFoundError](err) {
t.Fatalf("incorrect err returned %T", err)
}
})

View File

@ -300,7 +300,7 @@ func (r *RepositoryResolver) Commit(ctx context.Context, args *RepositoryCommitA
commitID, err := backend.NewRepos(r.logger, r.db, r.gitserverClient).ResolveRev(ctx, r.name, args.Rev)
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return nil, nil
}
return nil, err
@ -401,7 +401,7 @@ func (r *RepositoryResolver) FirstEverCommit(ctx context.Context) (_ *GitCommitR
commit, err := r.gitserverClient.FirstEverCommit(ctx, repo.Name)
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return nil, nil
}
return nil, err
@ -649,7 +649,7 @@ func (r *schemaResolver) ResolvePhabricatorDiff(ctx context.Context, args *struc
}
// If we already created the commit
if commit, err := getCommit(); commit != nil || (err != nil && !errors.HasType(err, &gitdomain.RevisionNotFoundError{})) {
if commit, err := getCommit(); commit != nil || (err != nil && !errors.HasType[*gitdomain.RevisionNotFoundError](err)) {
return commit, err
}

View File

@ -190,7 +190,7 @@ func newCommon(w http.ResponseWriter, r *http.Request, db database.DB, title str
// Common repo pages (blob, tree, etc).
var err error
common.Repo, common.CommitID, err = handlerutil.GetRepoAndRev(r.Context(), logger, db, mux.Vars(r))
isRepoEmptyError := routevar.ToRepoRev(mux.Vars(r)).Rev == "" && errors.HasType(err, &gitdomain.RevisionNotFoundError{}) // should reply with HTTP 200
isRepoEmptyError := routevar.ToRepoRev(mux.Vars(r)).Rev == "" && errors.HasType[*gitdomain.RevisionNotFoundError](err) // should reply with HTTP 200
if err != nil && !isRepoEmptyError {
var urlMovedError *handlerutil.URLMovedError
if errors.As(err, &urlMovedError) {
@ -214,12 +214,12 @@ func newCommon(w http.ResponseWriter, r *http.Request, db database.DB, title str
http.Redirect(w, r, u.String(), http.StatusSeeOther)
return nil, nil
}
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
// Revision does not exist.
serveError(w, r, db, err, http.StatusNotFound)
return nil, nil
}
if errors.HasType(err, &gitserver.RepoNotCloneableErr{}) {
if errors.HasType[*gitserver.RepoNotCloneableErr](err) {
if errcode.IsNotFound(err) {
// Repository is not found.
serveError(w, r, db, err, http.StatusNotFound)

View File

@ -24,7 +24,7 @@ func TestGetRepo(t *testing.T) {
})
_, err := GetRepo(context.Background(), logger, dbmocks.NewMockDB(), map[string]string{"Repo": "repo1"})
if !errors.HasType(err, &URLMovedError{}) {
if !errors.HasType[*URLMovedError](err) {
t.Fatalf("err: want type *URLMovedError but got %T", err)
}
})

View File

@ -145,7 +145,7 @@ func AccessTokenAuthMiddleware(db database.DB, baseLogger log.Logger, next http.
subjectUserID, err := db.AccessTokens().Lookup(r.Context(), token, opts)
if err != nil {
if err == database.ErrAccessTokenNotFound || errors.HasType(err, database.InvalidTokenError{}) {
if err == database.ErrAccessTokenNotFound || errors.HasType[database.InvalidTokenError](err) {
anonymousId, anonCookieSet := cookie.AnonymousUID(r)
if !anonCookieSet {
anonymousId = fmt.Sprintf("unknown user @ %s", time.Now()) // we don't have a reliable user identifier at the time of the failure

View File

@ -41,9 +41,9 @@ func handleStreamBlame(logger log.Logger, db database.DB, gitserverClient gitser
repo, commitID, err := handlerutil.GetRepoAndRev(r.Context(), logger, db, mux.Vars(r))
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
w.WriteHeader(http.StatusNotFound)
} else if errors.HasType(err, &gitserver.RepoNotCloneableErr{}) && errcode.IsNotFound(err) {
} else if errors.HasType[*gitserver.RepoNotCloneableErr](err) && errcode.IsNotFound(err) {
w.WriteHeader(http.StatusNotFound)
} else if errcode.IsNotFound(err) || errcode.IsBlocked(err) {
w.WriteHeader(http.StatusNotFound)

View File

@ -15,7 +15,7 @@ func validateNotebookWritePermissionsForUser(ctx context.Context, db database.DB
} else if notebook.NamespaceOrgID != 0 {
// Only members of the org have write access to the notebook
membership, err := db.OrgMembers().GetByOrgIDAndUserID(ctx, notebook.NamespaceOrgID, userID)
if errors.HasType(err, &database.ErrOrgMemberNotFound{}) || membership == nil {
if errors.HasType[*database.ErrOrgMemberNotFound](err) || membership == nil {
return errors.New("user is not a member of the notebook organization namespace")
} else if err != nil {
return err

View File

@ -431,7 +431,7 @@ func (r *notebookResolver) Namespace(ctx context.Context) (*graphqlbackend.Names
// On Cloud, the user can have access to an org notebook if it is public. But if the user is not a member of
// that org, then he does not have access to further information about the org. Instead of returning an error
// (which would prevent the user from viewing the notebook) we return an empty namespace.
if dotcom.SourcegraphDotComMode() && errors.HasType(err, &database.OrgNotFoundError{}) {
if dotcom.SourcegraphDotComMode() && errors.HasType[*database.OrgNotFoundError](err) {
return nil, nil
}
return nil, err

View File

@ -200,7 +200,7 @@ func (h *streamHandler) serveHTTP(r *http.Request, tr trace.Trace, eventWriter *
return h.searchClient.Execute(ctx, batchedStream, inputs)
}()
if err != nil && errors.HasType(err, &query.UnsupportedError{}) {
if err != nil && errors.HasType[*query.UnsupportedError](err) {
eventWriter.Alert(search.AlertForQuery(inputs.OriginalQuery, err))
err = nil
}

View File

@ -25,7 +25,7 @@ func (wr *Router) HandleBitbucketCloudWebhook(logger log.Logger, w http.Response
eventType := r.Header.Get("X-Event-Key")
e, err := bitbucketcloud.ParseWebhookEvent(eventType, payload)
if err != nil {
if errors.HasType(err, bitbucketcloud.UnknownWebhookEventKey("")) {
if errors.HasType[bitbucketcloud.UnknownWebhookEventKey](err) {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)

View File

@ -155,13 +155,13 @@ func TestGitCLIBackend_ArchiveReader(t *testing.T) {
t.Run("non existent commit", func(t *testing.T) {
_, err := backend.ArchiveReader(ctx, "tar", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", nil)
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("non existent ref", func(t *testing.T) {
_, err := backend.ArchiveReader(ctx, "tar", "head-2", nil)
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
// Verify that if the context is canceled, the reader returns an error.

View File

@ -126,12 +126,12 @@ func TestGitCLIBackend_Blame(t *testing.T) {
// Ambiguous ref, could be commit, could be a ref.
_, err := backend.Blame(ctx, "deadbeef", "foo.txt", git.BlameOptions{})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Definitely a commit (yes, those yield different errors from git).
_, err = backend.Blame(ctx, "e3889dff4263a2273459471739aafabc10269885", "foo.txt", git.BlameOptions{})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("file not found", func(t *testing.T) {

View File

@ -93,7 +93,7 @@ func TestGitCLIBackend_CommitLog(t *testing.T) {
})
require.NoError(t, err)
_, err = it.Next()
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
_ = it.Close()
})
@ -346,7 +346,7 @@ func TestGitCLIBackend_CommitLog(t *testing.T) {
})
require.NoError(t, err)
_, err = it.Next()
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Verify ordering doesn't matter and we return an error for any missing range:
it, err = backend.CommitLog(ctx, git.CommitLogOpts{
@ -355,7 +355,7 @@ func TestGitCLIBackend_CommitLog(t *testing.T) {
require.NoError(t, err)
_, err = it.Next()
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Bad commit in range:
it, err = backend.CommitLog(ctx, git.CommitLogOpts{
@ -364,7 +364,7 @@ func TestGitCLIBackend_CommitLog(t *testing.T) {
require.NoError(t, err)
_, err = it.Next()
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Bad commit in range LHS:
it, err = backend.CommitLog(ctx, git.CommitLogOpts{
@ -373,7 +373,7 @@ func TestGitCLIBackend_CommitLog(t *testing.T) {
require.NoError(t, err)
_, err = it.Next()
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Bad ref in range:
it, err = backend.CommitLog(ctx, git.CommitLogOpts{
@ -382,7 +382,7 @@ func TestGitCLIBackend_CommitLog(t *testing.T) {
require.NoError(t, err)
_, err = it.Next()
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Unknown SHA:
it, err = backend.CommitLog(ctx, git.CommitLogOpts{
@ -391,7 +391,7 @@ func TestGitCLIBackend_CommitLog(t *testing.T) {
require.NoError(t, err)
_, err = it.Next()
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
// Verify that if the context is canceled, the iterator returns an error.
t.Run("context cancelation", func(t *testing.T) {

View File

@ -142,25 +142,25 @@ func TestGitCLIBackend_ContributorCounts(t *testing.T) {
Range: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", // Invalid OID
})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
_, err = backend.ContributorCounts(ctx, git.ContributorCountsOpts{
Range: "unknownbranch", // Invalid ref
})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
_, err = backend.ContributorCounts(ctx, git.ContributorCountsOpts{
Range: "unknownbranch..HEAD", // Invalid left hand of range
})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
_, err = backend.ContributorCounts(ctx, git.ContributorCountsOpts{
Range: "HEAD..unknownbranch", // Invalid right hand of range
})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}

View File

@ -94,11 +94,11 @@ index 0000000000000000000000000000000000000000..8a6a2d098ecaf90105f1cf2fa90fc460
_, err := backend.RawDiff(ctx, "unknown", "test", git.GitDiffComparisonTypeOnlyInHead)
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
_, err = backend.RawDiff(ctx, "test", "unknown", git.GitDiffComparisonTypeOnlyInHead)
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("files outside repository", func(t *testing.T) {
// We use git-diff-tree, but with git-diff you can diff any files on disk
@ -250,7 +250,7 @@ func TestGitCLIBackend_ChangedFiles(t *testing.T) {
_, err := backend.ChangedFiles(ctx, "invalid", "HEAD")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("invalid head", func(t *testing.T) {
@ -264,7 +264,7 @@ func TestGitCLIBackend_ChangedFiles(t *testing.T) {
_, err := backend.ChangedFiles(ctx, "testbase", "invalid")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("empty base and single commit", func(t *testing.T) {

View File

@ -71,7 +71,7 @@ func TestGitCLIBackend_RevParseHead(t *testing.T) {
_, err := backend.RevParseHead(ctx)
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}

View File

@ -67,10 +67,10 @@ func TestGitCLIBackend_MergeBase(t *testing.T) {
_, err := backend.MergeBase(ctx, "master", "notfound")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
_, err = backend.MergeBase(ctx, "notfound", "master")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}

View File

@ -55,24 +55,24 @@ func TestGitCLIBackend_GetObject(t *testing.T) {
// Unknown revision.
_, err = backend.GetObject(ctx, "master2")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Unknown commit.
_, err = backend.GetObject(ctx, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Invalid commit sha (invalid hex format).
_, err = backend.GetObject(ctx, "notacommitsha")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
t.Run("HEAD in empty repo", func(t *testing.T) {
backend := BackendWithRepoCommands(t)
_, err := backend.GetObject(ctx, "HEAD")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}

View File

@ -300,7 +300,7 @@ func (g *gitCLIBackend) Stat(ctx context.Context, commit api.CommitID, path stri
if path == "" || path == "." {
rev, err := g.revParse(ctx, string(commit)+"^{tree}")
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return nil, &os.PathError{Op: "ls-tree", Path: path, Err: os.ErrNotExist}
}
return nil, err

View File

@ -69,7 +69,7 @@ func TestGitCLIBackend_ReadFile(t *testing.T) {
t.Run("non existent commit", func(t *testing.T) {
_, err := backend.ReadFile(ctx, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "file1")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("special file paths", func(t *testing.T) {
@ -195,7 +195,7 @@ func TestGitCLIBackend_GetCommit(t *testing.T) {
t.Run("non existent commit", func(t *testing.T) {
_, err := backend.GetCommit(ctx, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", false)
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
// This test only exists because we sometimes pass non-commit ID strings to the
@ -204,7 +204,7 @@ func TestGitCLIBackend_GetCommit(t *testing.T) {
t.Run("bad revision", func(t *testing.T) {
_, err := backend.GetCommit(ctx, "nonexisting", false)
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
// This test only exists because we sometimes pass non-commit ID strings to the
@ -214,7 +214,7 @@ func TestGitCLIBackend_GetCommit(t *testing.T) {
backend := BackendWithRepoCommands(t)
_, err := backend.GetCommit(ctx, "HEAD", false)
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("read commit", func(t *testing.T) {
@ -518,7 +518,7 @@ func TestGitCLIBackend_Stat(t *testing.T) {
t.Run("non existent commit", func(t *testing.T) {
_, err := backend.Stat(ctx, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "file1")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("stat root", func(t *testing.T) {
@ -745,13 +745,13 @@ func TestGitCLIBackend_ReadDir(t *testing.T) {
t.Cleanup(func() { it.Close() })
_, err = it.Next()
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Read no entries:
it, err = backend.ReadDir(ctx, "notfound", "nested", false)
require.NoError(t, err)
err = it.Close()
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("non existent commit", func(t *testing.T) {
@ -761,7 +761,7 @@ func TestGitCLIBackend_ReadDir(t *testing.T) {
_, err = it.Next()
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("read root", func(t *testing.T) {

View File

@ -53,7 +53,7 @@ func TestGitCLIBackend_ResolveRevision(t *testing.T) {
// Unknown commit:
_, err = backend.ResolveRevision(ctx, "dfcb84e522cab3c0b307a70917604c6d3da00dc8")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Resolve abbrev commit:
commit, err = backend.ResolveRevision(ctx, "f372e36")
@ -62,7 +62,7 @@ func TestGitCLIBackend_ResolveRevision(t *testing.T) {
// Unknown abbrev commit:
_, err = backend.ResolveRevision(ctx, "dfcb84e5")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Resolve ref:
commit, err = backend.ResolveRevision(ctx, "refs/heads/master")
@ -81,32 +81,32 @@ func TestGitCLIBackend_ResolveRevision(t *testing.T) {
// Unknown ref:
_, err = backend.ResolveRevision(ctx, "refs/heads/notfound")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
_, err = backend.ResolveRevision(ctx, "notfound")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Resolve object that is not a commit: (this is the tree object of f372e36a91bc35e5d99df8be435bdcb1f0660bc5)
_, err = backend.ResolveRevision(ctx, "92cb0143f5166452f2d45ed974a818749bc4a13f")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// :file1 gets the object ID of the file called file1 at HEAD.
// We don't allow that, since it leaks the existence of the file.
_, err = backend.ResolveRevision(ctx, ":file1")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// HEAD:file1 gets the object ID of the file called file1 at HEAD.
// We don't allow that, since it leaks the existence of the file.
_, err = backend.ResolveRevision(ctx, "HEAD:file1")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// :/foo gets a commit by commit message, but we don't want that.
_, err = backend.ResolveRevision(ctx, ":/foo")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// HEAD^{/foo} is the same as the above.
// TODO: This currently passes, but it shouldn't need to.
// _, err = backend.ResolveRevision(ctx, "HEAD^{/foo}")
@ -120,7 +120,7 @@ func TestGitCLIBackend_ResolveRevision(t *testing.T) {
// Not found range:
_, err = backend.ResolveRevision(ctx, "master..notfound")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("HEAD in empty repo", func(t *testing.T) {
@ -128,6 +128,6 @@ func TestGitCLIBackend_ResolveRevision(t *testing.T) {
_, err := backend.ResolveRevision(ctx, "HEAD")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}

View File

@ -74,12 +74,12 @@ func TestGitCLIBackend_RevAtTime(t *testing.T) {
// Invalid rev returns a rev not found error
_, err = backend.RevAtTime(ctx, "noexist", time.Date(2048, 6, 1, 0, 0, 0, 0, time.UTC))
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// Invalid OID returns a rev not found error
_, err = backend.RevAtTime(ctx, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", time.Date(2048, 6, 1, 0, 0, 0, 0, time.UTC))
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("out of order commit date", func(t *testing.T) {

View File

@ -552,7 +552,7 @@ func newOperations(observationCtx *observation.Context) *operations {
MetricLabelValues: []string{name},
Metrics: redMetrics,
ErrorFilter: func(err error) observation.ErrorFilterBehaviour {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return observation.EmitForHoney | observation.EmitForTraces
}
if errors.Is(err, os.ErrNotExist) {

View File

@ -971,7 +971,7 @@ func (gs *grpcServer) ResolveRevision(ctx context.Context, req *proto.ResolveRev
if err != nil {
// If that fails to resolve the revspec, try to ensure the revision exists,
// if requested by the caller.
if req.GetEnsureRevision() && errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if req.GetEnsureRevision() && errors.HasType[*gitdomain.RevisionNotFoundError](err) {
// We ensured the revision exists, so try to resolve it again.
if gs.svc.EnsureRevision(ctx, repoName, revspec) {
sha, err = backend.ResolveRevision(ctx, revspec)

View File

@ -151,5 +151,5 @@ func initializeUploadStore(ctx context.Context, uploadStore uploadstore.Store) e
}
func isRequestError(err error) bool {
return errors.HasType(err, &smithyhttp.RequestSendError{})
return errors.HasType[*smithyhttp.RequestSendError](err)
}

View File

@ -19,7 +19,7 @@ func syncServices(ctx context.Context, services []*types.ExternalService, newRat
for _, svc := range services {
limit, err := extsvc.ExtractEncryptableRateLimit(ctx, svc.Config, svc.Kind)
if err != nil {
if errors.HasType(err, extsvc.ErrRateLimitUnsupported{}) {
if errors.HasType[extsvc.ErrRateLimitUnsupported](err) {
continue
}
errs = errors.Append(errs, errors.Wrap(err, "getting rate limit configuration"))

View File

@ -81,7 +81,7 @@ func shortenErrHelp(cmd *ffcli.Command, cmdPath string) {
exec := cmd.Exec
cmd.Exec = func(args []string) error {
err := exec(args)
if errors.HasType(err, &usageError{}) {
if errors.HasType[*usageError](err) {
var w io.Writer
if cmd.FlagSet != nil {
w = cmd.FlagSet.Output()

View File

@ -330,7 +330,7 @@ func (e *executor) publishChangeset(ctx context.Context, asDraft bool) (afterDon
func (e *executor) syncChangeset(ctx context.Context) error {
if err := e.loadChangeset(ctx); err != nil {
if !errors.HasType(err, sources.ChangesetNotFoundError{}) {
if !errors.HasType[sources.ChangesetNotFoundError](err) {
return err
}

View File

@ -1647,7 +1647,7 @@ index e5af166..d44c3fc 100644
// Execute BatchSpec by creating execution jobs
_, err := svc.ExecuteBatchSpec(adminCtx, ExecuteBatchSpecOpts{BatchSpecRandID: spec.RandID})
if !errors.HasType(err, ErrBatchSpecResolutionErrored{}) {
if !errors.HasType[ErrBatchSpecResolutionErrored](err) {
t.Fatalf("error has wrong type: %T", err)
}
})
@ -3412,7 +3412,7 @@ func assertAuthError(t *testing.T, err error) {
if err == nil {
t.Fatalf("expected error. got none")
}
if !errors.HasType(err, &auth.InsufficientAuthorizationError{}) {
if !errors.HasType[*auth.InsufficientAuthorizationError](err) {
t.Fatalf("wrong error: %s (%T)", err, err)
}
}
@ -3424,7 +3424,7 @@ func assertOrgOrAuthError(t *testing.T, err error) {
t.Fatal("expected org authorization error, got none")
}
if !errors.HasType(err, auth.ErrNotAnOrgMember) && !errors.HasType(err, &auth.InsufficientAuthorizationError{}) {
if !errors.Is(err, auth.ErrNotAnOrgMember) && !errors.HasType[*auth.InsufficientAuthorizationError](err) {
t.Fatalf("expected authorization error, got %s", err.Error())
}
}
@ -3433,7 +3433,7 @@ func assertNoAuthError(t *testing.T, err error) {
t.Helper()
// Ignore other errors, we only want to check whether it's an auth error
if errors.HasType(err, &auth.InsufficientAuthorizationError{}) || errors.Is(err, auth.ErrNotAnOrgMember) {
if errors.HasType[*auth.InsufficientAuthorizationError](err) || errors.Is(err, auth.ErrNotAnOrgMember) {
t.Fatalf("got auth error")
}
}

View File

@ -299,7 +299,7 @@ func (wr *workspaceResolver) resolveRepositoryNameAndBranch(ctx context.Context,
commit, err := wr.gitserverClient.ResolveRevision(ctx, repo.Name, branch, gitserver.ResolveRevisionOptions{
EnsureRevision: false,
})
if err != nil && errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if err != nil && errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return nil, errors.Newf("no branch matching %q found for repository %s", branch, name)
}

View File

@ -786,7 +786,7 @@ func TestBitbucketServerSource_WithAuthenticator(t *testing.T) {
src, err := bbsSrc.WithAuthenticator(tc)
if err == nil {
t.Error("unexpected nil error")
} else if !errors.HasType(err, UnsupportedAuthenticatorError{}) {
} else if !errors.HasType[UnsupportedAuthenticatorError](err) {
t.Errorf("unexpected error of type %T: %v", err, err)
}
if src != nil {

View File

@ -1374,7 +1374,7 @@ func TestGitLabSource_WithAuthenticator(t *testing.T) {
src, err = src.WithAuthenticator(tc)
if err == nil {
t.Error("unexpected nil error")
} else if !errors.HasType(err, UnsupportedAuthenticatorError{}) {
} else if !errors.HasType[UnsupportedAuthenticatorError](err) {
t.Errorf("unexpected error of type %T: %v", err, err)
}
if src != nil {

View File

@ -514,7 +514,7 @@ func (s *changesetSyncer) SyncChangeset(ctx context.Context, id int64) error {
func SyncChangeset(ctx context.Context, syncStore SyncStore, client gitserver.Client, source sources.ChangesetSource, repo *types.Repo, c *btypes.Changeset) (err error) {
repoChangeset := &sources.Changeset{TargetRepo: repo, Changeset: c}
if err := source.LoadChangeset(ctx, repoChangeset); err != nil {
if !errors.HasType(err, sources.ChangesetNotFoundError{}) {
if !errors.HasType[sources.ChangesetNotFoundError](err) {
// Store the error as the syncer error.
errMsg := err.Error()
c.SyncErrorMessage = &errMsg

View File

@ -191,7 +191,7 @@ func (b indexSchedulerJob) handleRepository(ctx context.Context, repositoryID, p
// Attempt to queue an index if one does not exist for each of the matching commits
if _, err := b.indexEnqueuer.QueueIndexes(ctx, repositoryID, commit, "", false, false); err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
continue
}

View File

@ -101,7 +101,7 @@ func (s *Service) InferIndexConfiguration(ctx context.Context, repositoryID int,
} else {
// Verify that the commit exists.
_, err := s.gitserverClient.GetCommit(ctx, repo.Name, api.CommitID(commit))
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return nil, errors.Newf("revision %s not found for %d", commit, repositoryID)
}
if err != nil {

View File

@ -147,7 +147,7 @@ func (c *commitCache) commitsExist(ctx context.Context, commits []RepositoryComm
for i, rc := range repoCommits {
_, err := c.gitserverClient.GetCommit(ctx, rc.repoName, rc.commitID)
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
exists[i] = false
continue
}

View File

@ -298,7 +298,7 @@ func (m *Matcher) matchCommitPolicies(ctx context.Context, context matcherContex
if policy.Type == shared.GitObjectTypeCommit {
commit, err := m.gitserverClient.GetCommit(ctx, context.repo, api.CommitID(policy.Pattern))
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
continue
}
return err

View File

@ -60,7 +60,7 @@ func newCachedLocationResolver(
resolveCommit := func(ctx context.Context, repositoryResolver resolverstubs.RepositoryResolver, commit string) (resolverstubs.GitCommitResolver, error) {
commitID, err := gitserverClient.ResolveRevision(ctx, api.RepoName(repositoryResolver.Name()), commit, gitserver.ResolveRevisionOptions{EnsureRevision: false})
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return nil, nil
}
return nil, err

View File

@ -72,7 +72,7 @@ func (s *backfiller) BackfillCommittedAtBatch(ctx context.Context, batchSize int
func (s *backfiller) getCommitDate(ctx context.Context, repositoryName, commitID string) (string, error) {
commit, err := s.gitserverClient.GetCommit(ctx, api.RepoName(repositoryName), api.CommitID(commitID))
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
// Set a value here that we'll filter out on the query side so that we don't
// reprocess the same failing batch infinitely. We could alternatively soft
// delete the record, but it would be better to keep record deletion behavior

View File

@ -73,7 +73,7 @@ func shouldDeleteRecordsForCommit(ctx context.Context, gitserverClient gitserver
return false, nil
}
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
// Repository is resolvable but commit is not - remove it
return true, nil
}

View File

@ -271,7 +271,7 @@ func (h *handler) HandleRawUpload(ctx context.Context, logger log.Logger, upload
commit, err := h.gitserverClient.GetCommit(ctx, repo.Name, api.CommitID(upload.Commit))
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return errCommitDoesNotExist
}
return errors.Wrap(err, "failed to determine commit date")
@ -415,7 +415,7 @@ func requeueIfCloningOrCommitUnknown(ctx context.Context, logger log.Logger, git
}
var reason string
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
reason = "commit not found"
} else if gitdomain.IsCloneInProgress(err) {
reason = "repository still cloning"

View File

@ -85,7 +85,7 @@ func ensureRepoAndCommitExist(ctx context.Context, repoStore RepoStore, repoName
if _, err := repoStore.ResolveRev(ctx, repo.Name, commit); err != nil {
var reason string
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
reason = "commit not found"
} else if gitdomain.IsCloneInProgress(err) {
reason = "repository still cloning"

View File

@ -1760,7 +1760,7 @@ func ensureCodeHost(ctx context.Context, tx *externalServiceStore, kind string,
}
// TODO: Use this method for the OOB migrator as well.
rateLimit, isDefaultRateLimit, err := extsvc.ExtractRateLimit(config, kind)
if err != nil && !errors.HasType(err, extsvc.ErrRateLimitUnsupported{}) {
if err != nil && !errors.HasType[extsvc.ErrRateLimitUnsupported](err) {
return 0, err
}
ch := &types.CodeHost{

View File

@ -1433,7 +1433,7 @@ func TestExternalServiceStore_CancelSyncJob(t *testing.T) {
// Make sure "not found" is handled
err = store.CancelSyncJob(ctx, ExternalServicesCancelSyncJobOptions{ID: 9999})
if !errors.HasType(err, &errSyncJobNotFound{}) {
if !errors.HasType[*errSyncJobNotFound](err) {
t.Fatalf("Expected not-found error, have %q", err)
}
err = store.CancelSyncJob(ctx, ExternalServicesCancelSyncJobOptions{ExternalServiceID: 9999})
@ -1504,7 +1504,7 @@ RETURNING id
// Insert sync job in state that is not cancelable
syncJobID4 := insertSyncJob(t, "completed")
err = store.CancelSyncJob(ctx, ExternalServicesCancelSyncJobOptions{ID: syncJobID4})
if !errors.HasType(err, &errSyncJobNotFound{}) {
if !errors.HasType[*errSyncJobNotFound](err) {
t.Fatalf("Expected not-found error, have %q", err)
}
}

View File

@ -129,7 +129,7 @@ func TestOrgs_Delete(t *testing.T) {
// Org no longer exists.
_, err = db.Orgs().GetByID(ctx, org.ID)
if !errors.HasType(err, &OrgNotFoundError{}) {
if !errors.HasType[*OrgNotFoundError](err) {
t.Errorf("got error %v, want *OrgNotFoundError", err)
}
orgs, err := db.Orgs().List(ctx, &OrgsListOptions{Query: "a"})
@ -142,7 +142,7 @@ func TestOrgs_Delete(t *testing.T) {
// Can't delete already-deleted org.
err = db.Orgs().Delete(ctx, org.ID)
if !errors.HasType(err, &OrgNotFoundError{}) {
if !errors.HasType[*OrgNotFoundError](err) {
t.Errorf("got error %v, want *OrgNotFoundError", err)
}
}
@ -164,7 +164,7 @@ func TestOrgs_HardDelete(t *testing.T) {
// Org no longer exists.
_, err = db.Orgs().GetByID(ctx, org.ID)
if !errors.HasType(err, &OrgNotFoundError{}) {
if !errors.HasType[*OrgNotFoundError](err) {
t.Errorf("got error %v, want *OrgNotFoundError", err)
}
@ -176,7 +176,7 @@ func TestOrgs_HardDelete(t *testing.T) {
// Cannot hard delete an org that doesn't exist.
err = db.Orgs().HardDelete(ctx, org.ID)
if !errors.HasType(err, &OrgNotFoundError{}) {
if !errors.HasType[*OrgNotFoundError](err) {
t.Errorf("got error %v, want *OrgNotFoundError", err)
}

View File

@ -93,7 +93,7 @@ func (s *phabricatorStore) CreateOrUpdate(ctx context.Context, callsign string,
func (s *phabricatorStore) CreateIfNotExists(ctx context.Context, callsign string, name api.RepoName, phabURL string) (*types.PhabricatorRepo, error) {
repo, err := s.GetByName(ctx, name)
if err != nil {
if !errors.HasType(err, errPhabricatorRepoNotFound{}) {
if !errors.HasType[errPhabricatorRepoNotFound](err) {
return nil, err
}
return s.Create(ctx, callsign, name, phabURL)

View File

@ -233,7 +233,7 @@ func upsertRepo(ctx context.Context, db DB, op InsertRepoOp) error {
// log_statement='mod'.
r, err := s.GetByName(ctx, op.Name)
if err != nil {
if !errors.HasType(err, &RepoNotFoundErr{}) {
if !errors.HasType[*RepoNotFoundErr](err) {
return err
}
insert = true // missing

View File

@ -154,7 +154,7 @@ func TestWebhookDelete(t *testing.T) {
// Test that delete with wrong UUID returns an error
nonExistentUUID := uuid.New()
err := store.Delete(ctx, DeleteWebhookOpts{UUID: nonExistentUUID})
if !errors.HasType(err, &WebhookNotFoundError{}) {
if !errors.HasType[*WebhookNotFoundError](err) {
t.Fatalf("want WebhookNotFoundError, got: %s", err)
}
assert.EqualError(t, err, fmt.Sprintf("failed to delete webhook: webhook with UUID %s not found", nonExistentUUID))
@ -162,7 +162,7 @@ func TestWebhookDelete(t *testing.T) {
// Test that delete with wrong ID returns an error
nonExistentID := int32(123)
err = store.Delete(ctx, DeleteWebhookOpts{ID: nonExistentID})
if !errors.HasType(err, &WebhookNotFoundError{}) {
if !errors.HasType[*WebhookNotFoundError](err) {
t.Fatalf("want WebhookNotFoundError, got: %s", err)
}
assert.EqualError(t, err, fmt.Sprintf("failed to delete webhook: webhook with ID %d not found", nonExistentID))

View File

@ -41,10 +41,10 @@ func HTTP(err error) int {
return e.HTTPStatusCode()
}
if errors.HasType(err, schema.ConversionError{}) {
if errors.HasType[schema.ConversionError](err) {
return http.StatusBadRequest
}
if errors.HasType(err, schema.MultiError{}) {
if errors.HasType[schema.MultiError](err) {
return http.StatusBadRequest
}

View File

@ -50,7 +50,7 @@ var ErrNotFound = errors.New("AWS CodeCommit repository not found")
// IsNotFound reports whether err is a AWS CodeCommit API not-found error or the
// equivalent cached response error.
func IsNotFound(err error) bool {
return errors.Is(err, ErrNotFound) || errors.HasType(err, &codecommittypes.RepositoryDoesNotExistException{})
return errors.Is(err, ErrNotFound) || errors.HasType[*codecommittypes.RepositoryDoesNotExistException](err)
}
// IsUnauthorized reports whether err is a AWS CodeCommit API unauthorized error.

View File

@ -1658,7 +1658,7 @@ func (e OrgNotFoundError) NotFound() bool { return true }
// IsNotFound reports whether err is a GitHub API error of type NOT_FOUND, the equivalent cached
// response error, or HTTP 404.
func IsNotFound(err error) bool {
if errors.HasType(err, &RepoNotFoundError{}) || errors.HasType(err, &OrgNotFoundError{}) || errors.HasType(err, ErrPullRequestNotFound(0)) ||
if errors.HasType[*RepoNotFoundError](err) || errors.HasType[*OrgNotFoundError](err) || errors.HasType[ErrPullRequestNotFound](err) ||
HTTPErrorCode(err) == http.StatusNotFound {
return true
}

View File

@ -393,7 +393,7 @@ func HTTPErrorCode(err error) int {
// IsNotFound reports whether err is a GitLab API error of type NOT_FOUND, the equivalent cached
// response error, or HTTP 404.
func IsNotFound(err error) bool {
return errors.HasType(err, &ProjectNotFoundError{}) ||
return errors.HasType[*ProjectNotFoundError](err) ||
errors.Is(err, ErrMergeRequestNotFound) ||
HTTPErrorCode(err) == http.StatusNotFound
}

View File

@ -73,7 +73,7 @@ func (c *clientImplementor) Diff(ctx context.Context, repo api.RepoName, opts Di
// We start by reading the first message to early-exit on potential errors.
firstResp, firstRespErr := cc.Recv()
if firstRespErr != nil {
if errors.HasType(firstRespErr, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](firstRespErr) {
cancel()
err = firstRespErr
endObservation(1, observation.Args{})
@ -667,10 +667,10 @@ func (c *clientImplementor) GetDefaultBranch(ctx context.Context, repo api.RepoN
})
if err != nil {
// If we fail to get the default branch due to cloning or being empty, we return nothing.
if errors.HasType(err, &gitdomain.RepoNotExistError{}) {
if errors.HasType[*gitdomain.RepoNotExistError](err) {
return "", "", nil
}
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return "", "", nil
}
return "", "", err
@ -791,7 +791,7 @@ func (c *clientImplementor) NewFileReader(ctx context.Context, repo api.RepoName
}
}
}
if errors.HasType(firstRespErr, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](firstRespErr) {
cancel()
err = firstRespErr
endObservation(1, observation.Args{})
@ -1294,7 +1294,7 @@ func (c *clientImplementor) ArchiveReader(ctx context.Context, repo api.RepoName
// ie. revision not found errors or invalid git command.
firstMessage, firstErr := cli.Recv()
if firstErr != nil {
if errors.HasType(firstErr, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](firstErr) {
cancel()
err = firstErr
endObservation(1, observation.Args{})

View File

@ -247,7 +247,7 @@ func TestClient_StreamBlameFile(t *testing.T) {
_, err := c.StreamBlameFile(context.Background(), "repo", "file", &BlameOptions{})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("file not found errors are returned early", func(t *testing.T) {
source := NewTestClientSource(t, []string{"gitserver"}, func(o *TestClientSourceOptions) {
@ -387,7 +387,7 @@ func TestClient_MergeBase(t *testing.T) {
_, err := c.MergeBase(context.Background(), "repo", "master", "b2")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}
@ -500,7 +500,7 @@ func TestClient_NewFileReader(t *testing.T) {
_, err := c.NewFileReader(context.Background(), "repo", "deadbeef", "file")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("empty file", func(t *testing.T) {
source := NewTestClientSource(t, []string{"gitserver"}, func(o *TestClientSourceOptions) {
@ -565,7 +565,7 @@ func TestClient_GetCommit(t *testing.T) {
ctx := actor.WithActor(context.Background(), actor.FromUser(1))
_, err := c.GetCommit(ctx, "repo", "deadbeef")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("checks for subrepo permissions some files visible", func(t *testing.T) {
source := NewTestClientSource(t, []string{"gitserver"}, func(o *TestClientSourceOptions) {
@ -610,7 +610,7 @@ func TestClient_GetCommit(t *testing.T) {
_, err := c.GetCommit(context.Background(), "repo", "deadbeef")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}
@ -676,7 +676,7 @@ func TestClient_ArchiveReader(t *testing.T) {
_, err := c.ArchiveReader(context.Background(), "repo", ArchiveOptions{Treeish: "deadbeef", Format: ArchiveFormatTar, Paths: []string{"file"}})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("checks for subrepo permissions enabled on the repo", func(t *testing.T) {
source := NewTestClientSource(t, []string{"gitserver"}, func(o *TestClientSourceOptions) {
@ -804,7 +804,7 @@ index e5af166..d44c3fc 100644
_, err := c.Diff(context.Background(), "repo", DiffOptions{})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}
@ -848,11 +848,11 @@ func TestClient_ResolveRevision(t *testing.T) {
// First request fails with revision error
_, err := c.ResolveRevision(context.Background(), "repo", "HEAD", ResolveRevisionOptions{})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
// First request fails with clone error
_, err = c.ResolveRevision(context.Background(), "repo", "HEAD", ResolveRevisionOptions{})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RepoNotExistError{}))
require.True(t, errors.HasType[*gitdomain.RepoNotExistError](err))
})
}
@ -908,7 +908,7 @@ func TestClient_RevAtTime(t *testing.T) {
_, _, err := c.RevAtTime(context.Background(), "repo", "HEAD", time.Now())
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}
@ -960,7 +960,7 @@ func TestClient_ListRefs(t *testing.T) {
// Should fail with clone error
_, err := c.ListRefs(context.Background(), "repo", ListRefsOpts{})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RepoNotExistError{}))
require.True(t, errors.HasType[*gitdomain.RepoNotExistError](err))
})
}
@ -1009,7 +1009,7 @@ func TestClient_ContributorCounts(t *testing.T) {
_, err := c.ContributorCount(context.Background(), "repo", ContributorOptions{})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}
@ -1061,7 +1061,7 @@ func TestClient_FirstEverCommit(t *testing.T) {
// Should fail with clone error
_, err := c.FirstEverCommit(context.Background(), "repo")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RepoNotExistError{}))
require.True(t, errors.HasType[*gitdomain.RepoNotExistError](err))
})
t.Run("empty repository", func(t *testing.T) {
@ -1080,7 +1080,7 @@ func TestClient_FirstEverCommit(t *testing.T) {
// Should fail with RepositoryEmptyError
_, err := c.FirstEverCommit(context.Background(), "repo")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
})
}
@ -1131,7 +1131,7 @@ func TestClient_GetBehindAhead(t *testing.T) {
// Should fail with clone error
_, err := c.BehindAhead(context.Background(), "repo", "left", "right")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RepoNotExistError{}))
require.True(t, errors.HasType[*gitdomain.RepoNotExistError](err))
})
t.Run("revision not found", func(t *testing.T) {
@ -1150,7 +1150,7 @@ func TestClient_GetBehindAhead(t *testing.T) {
// Should fail with RevisionNotFoundError
_, err := c.BehindAhead(context.Background(), "repo", "left", "right")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
})
}
@ -1226,8 +1226,8 @@ func TestClient_ChangedFiles(t *testing.T) {
// Check to see if either the initial error or the error from the iterator is a RepoNotExistError
require.True(t,
errors.HasType(initialErr, &gitdomain.RepoNotExistError{}) ||
errors.HasType(iterErr, &gitdomain.RepoNotExistError{}))
errors.HasType[*gitdomain.RepoNotExistError](initialErr) ||
errors.HasType[*gitdomain.RepoNotExistError](iterErr))
})
t.Run("revision not found", func(t *testing.T) {
@ -1256,8 +1256,8 @@ func TestClient_ChangedFiles(t *testing.T) {
// Check to see if either the initial error or the error from the iterator is a RevisionNotFoundError
require.True(t,
errors.HasType(initialErr, &gitdomain.RevisionNotFoundError{}) ||
errors.HasType(iterErr, &gitdomain.RevisionNotFoundError{}))
errors.HasType[*gitdomain.RevisionNotFoundError](initialErr) ||
errors.HasType[*gitdomain.RevisionNotFoundError](iterErr))
})
})
@ -1431,7 +1431,7 @@ func TestClient_GetObject(t *testing.T) {
_, err := c.GetObject(context.Background(), "repo", "deadbeef")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RepoNotExistError{}))
require.True(t, errors.HasType[*gitdomain.RepoNotExistError](err))
})
t.Run("object not found", func(t *testing.T) {
@ -1449,7 +1449,7 @@ func TestClient_GetObject(t *testing.T) {
_, err := c.GetObject(context.Background(), "repo", "deadbeef")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
})
}
@ -1496,7 +1496,7 @@ func TestClient_Stat(t *testing.T) {
_, err := c.Stat(context.Background(), "repo", "HEAD", "file")
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("FileNotFound", func(t *testing.T) {
@ -1660,7 +1660,7 @@ func TestClient_ReadDir(t *testing.T) {
_, err := c.ReadDir(context.Background(), "repo", "HEAD", "file", true)
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
t.Run("FileNotFound", func(t *testing.T) {
@ -1816,7 +1816,7 @@ func TestClient_Commits(t *testing.T) {
_, err := c.Commits(context.Background(), "repo", CommitsOptions{AllRefs: true})
require.Error(t, err)
require.True(t, errors.HasType(err, &gitdomain.RevisionNotFoundError{}))
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
})
t.Run("subrepo permissions", func(t *testing.T) {

View File

@ -63,7 +63,7 @@ func (e *RepoNotExistError) Error() string {
// IsRepoNotExist reports if err is a RepoNotExistError.
func IsRepoNotExist(err error) bool {
return errors.HasType(err, &RepoNotExistError{})
return errors.HasType[*RepoNotExistError](err)
}
// IsCloneInProgress reports if err is a RepoNotExistError which has a clone

View File

@ -86,7 +86,7 @@ func newOperations(observationCtx *observation.Context) *operations {
MetricLabelValues: []string{"ResolveRevision"},
Metrics: redMetrics,
ErrorFilter: func(err error) observation.ErrorFilterBehaviour {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return observation.EmitForMetrics
}
return observation.EmitForSentry

View File

@ -241,7 +241,7 @@ func makeHistoricalSearchJobFunc(logger log.Logger, commitClient GitCommitClient
if len(bctx.execution.Revision) == 0 {
recentCommits, revErr := commitClient.RecentCommits(ctx, bctx.repoName, bctx.execution.RecordingTime, "")
if revErr != nil {
if errors.HasType(revErr, &gitdomain.RevisionNotFoundError{}) || gitdomain.IsRepoNotExist(revErr) {
if errors.HasType[*gitdomain.RevisionNotFoundError](revErr) || gitdomain.IsRepoNotExist(revErr) {
return // no error - repo may not be cloned yet (or not even pushed to code host yet)
}
err = errors.Append(err, errors.Wrap(revErr, "FindNearestCommit"))

View File

@ -191,7 +191,7 @@ type featureNotActivatedError struct{ errcode.PresentationError }
// a feature (e.g., Enterprise Starter not including an Enterprise-only feature).
func IsFeatureNotActivated(err error) bool {
// Also check for the pointer type to guard against stupid mistakes.
return errors.HasType(err, featureNotActivatedError{}) || errors.HasType(err, &featureNotActivatedError{})
return errors.HasType[featureNotActivatedError](err) || errors.HasType[*featureNotActivatedError](err)
}
// IsFeatureEnabledLenient reports whether the current license enables the given

View File

@ -217,7 +217,7 @@ func (c *diskCollector) Collect(ch chan<- prometheus.Metric) {
func mustRegisterOnce(c prometheus.Collector) {
err := registerer.Register(c)
if err != nil && !errors.HasType(err, prometheus.AlreadyRegisteredError{}) {
if err != nil && !errors.HasType[prometheus.AlreadyRegisteredError](err) {
panic(err)
}
}

View File

@ -61,7 +61,7 @@ func (i *InstrumentedLimiter) WaitN(ctx context.Context, n int) error {
// For GlobalLimiter instances, we return a special error type for BlockAll,
// since we don't want to make two preflight redis calls to check limit and burst
// above. We map it back to ErrBlockAll here then.
if err != nil && errors.HasType(err, AllBlockedError{}) {
if err != nil && errors.HasType[AllBlockedError](err) {
return ErrBlockAll
}
d := time.Since(start)

View File

@ -223,7 +223,7 @@ func TestBitbucketServerSource_WithAuthenticator(t *testing.T) {
src, err := bbsSrc.WithAuthenticator(tc)
if err == nil {
t.Error("unexpected nil error")
} else if !errors.HasType(err, UnsupportedAuthenticatorError{}) {
} else if !errors.HasType[UnsupportedAuthenticatorError](err) {
t.Errorf("unexpected error of type %T: %v", err, err)
}
if src != nil {

View File

@ -278,7 +278,7 @@ func TestGitLabSource_WithAuthenticator(t *testing.T) {
src, err = src.(UserSource).WithAuthenticator(tc)
if err == nil {
t.Error("unexpected nil error")
} else if !errors.HasType(err, UnsupportedAuthenticatorError{}) {
} else if !errors.HasType[UnsupportedAuthenticatorError](err) {
t.Errorf("unexpected error of type %T: %v", err, err)
}
if src != nil {

View File

@ -84,7 +84,7 @@ func (q *QueryDescription) QueryString() string {
// AlertForQuery converts errors in the query to search alerts.
func AlertForQuery(queryString string, err error) *Alert {
if errors.HasType(err, &query.ExpectedOperand{}) {
if errors.HasType[*query.ExpectedOperand](err) {
return &Alert{
PrometheusType: "unsupported_and_or_query",
Title: "Unable To Process Query",

View File

@ -241,7 +241,7 @@ func (o *Observer) errorToAlert(ctx context.Context, err error) (*search.Alert,
lErr *ErrLuckyQueries
)
if errors.HasType(err, authz.ErrStalePermissions{}) {
if errors.HasType[authz.ErrStalePermissions](err) {
return search.AlertForStalePermissions(), nil
}

View File

@ -210,7 +210,7 @@ func (s searchQuery) Search(ctx context.Context, repoRev types.RepositoryRevisio
// An empty repository we treat as success. When searching HEAD we haven't
// yet validated the commit actually exists so we need to ignore at this
// point. We should consider
if repoRev.Revision == "HEAD" && errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if repoRev.Revision == "HEAD" && errors.HasType[*gitdomain.RevisionNotFoundError](err) {
return nil
}
@ -231,6 +231,5 @@ func (s searchQuery) minimalRepo(ctx context.Context, repoID api.RepoID) (sgtype
}
func isReposMissingError(err error) bool {
var m repos.MissingRepoRevsError
return errors.Is(err, repos.ErrNoResolvedRepos) || errors.HasType(err, &m)
return errors.Is(err, repos.ErrNoResolvedRepos) || errors.HasType[*repos.MissingRepoRevsError](err)
}

View File

@ -1639,7 +1639,7 @@ func TestRepoSubsetTextSearch(t *testing.T) {
endpoint.Static("test"),
false,
)
if !errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
if !errors.HasType[*gitdomain.RevisionNotFoundError](err) {
t.Fatalf("searching non-existent rev expected to fail with RevisionNotFoundError got: %v", err)
}
}

View File

@ -1237,7 +1237,7 @@ func Parse(in string, searchType SearchType) ([]Node, error) {
nodes, err := parser.parseOr()
if err != nil {
if errors.HasType(err, &ExpectedOperand{}) {
if errors.HasType[*ExpectedOperand](err) {
// The query may be unbalanced or malformed as in "(" or
// "x or" and expects an operand. Try harder to parse it.
if nodes, err := parser.tryFallbackParser(in); err == nil {

View File

@ -171,7 +171,7 @@ func HandleRepoSearchResult(repoID api.RepoID, revSpecs []string, limitHit, time
} else {
status |= RepoStatusMissing
}
} else if errors.HasType(searchErr, &gitdomain.RevisionNotFoundError{}) {
} else if errors.HasType[*gitdomain.RevisionNotFoundError](searchErr) {
if len(revSpecs) == 0 || len(revSpecs) == 1 && revSpecs[0] == "" {
// If we didn't specify an input revision, then the repo is empty and can be ignored.
} else {

View File

@ -531,7 +531,7 @@ func (r *Resolver) normalizeRepoRefs(
case rev.RevAtTime != nil:
commitOID, found, err := r.gitserver.RevAtTime(ctx, repo.Name, rev.RevAtTime.RevSpec, rev.RevAtTime.Timestamp)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.HasType(err, &gitdomain.BadCommitError{}) {
if errors.Is(err, context.DeadlineExceeded) || errors.HasType[*gitdomain.BadCommitError](err) {
return nil, err
}
reportMissing(RepoRevSpecs{Repo: repo, Revs: []query.RevisionSpecifier{rev}})
@ -550,7 +550,7 @@ func (r *Resolver) normalizeRepoRefs(
trimmedRev := strings.TrimPrefix(rev.RevSpec, "^")
_, err := r.gitserver.ResolveRevision(ctx, repo.Name, trimmedRev, gitserver.ResolveRevisionOptions{EnsureRevision: false})
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.HasType(err, &gitdomain.BadCommitError{}) {
if errors.Is(err, context.DeadlineExceeded) || errors.HasType[*gitdomain.BadCommitError](err) {
return nil, err
}
reportMissing(RepoRevSpecs{Repo: repo, Revs: []query.RevisionSpecifier{rev}})
@ -619,7 +619,7 @@ func (r *Resolver) filterHasCommitAfter(
rev := rev
p.Go(func(ctx context.Context) error {
if hasCommitAfter, err := hasCommitAfter(ctx, r.gitserver, repoRev.Repo.Name, timeRef, rev); err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) || gitdomain.IsRepoNotExist(err) {
if errors.HasType[*gitdomain.RevisionNotFoundError](err) || gitdomain.IsRepoNotExist(err) {
// If the revision does not exist or the repo does not exist,
// it certainly does not have any commits after some time.
// Ignore the error, but filter this repo out.
@ -815,7 +815,7 @@ func (r *Resolver) filterRepoHasFileContent(
checkHasMatches := func(ctx context.Context, arg query.RepoHasFileContentArgs, repo types.MinimalRepo, rev string) (bool, error) {
commitID, err := r.gitserver.ResolveRevision(ctx, repo.Name, rev, gitserver.ResolveRevisionOptions{EnsureRevision: false})
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.HasType(err, &gitdomain.BadCommitError{}) {
if errors.Is(err, context.DeadlineExceeded) || errors.HasType[*gitdomain.BadCommitError](err) {
return false, err
} else if e := (&gitdomain.RevisionNotFoundError{}); errors.As(err, &e) && (rev == "HEAD" || rev == "") {
// In the case that we can't find HEAD, that means there are no commits, which means

View File

@ -398,7 +398,7 @@ func authenticateByCookie(logger log.Logger, db database.DB, r *http.Request, w
var info *sessionInfo
if err := GetData(r, "actor", &info); err != nil {
if errors.HasType(err, &net.OpError{}) {
if errors.HasType[*net.OpError](err) {
// If fetching session info failed because of a Redis error, return empty Context
// without deleting the session cookie and throw an internal server error.
// This prevents background requests made by off-screen tabs from signing

View File

@ -366,7 +366,7 @@ func (s *s3Store) create(ctx context.Context) error {
Bucket: aws.String(s.bucket),
})
if errors.HasType(err, &s3types.BucketAlreadyExists{}) || errors.HasType(err, &s3types.BucketAlreadyOwnedByYou{}) {
if errors.HasType[*s3types.BucketAlreadyExists](err) || errors.HasType[*s3types.BucketAlreadyOwnedByYou](err) {
return nil
}

View File

@ -206,7 +206,7 @@ func (e BatchSpecValidationError) Error() string {
}
func IsValidationError(err error) bool {
return errors.HasType(err, &BatchSpecValidationError{})
return errors.HasType[*BatchSpecValidationError](err)
}
// SkippedStepsForRepo calculates the steps required to run on the given repo.

View File

@ -62,7 +62,6 @@ var (
// concrete type but with different data.
Is = errors.Is
IsAny = errors.IsAny
HasType = errors.HasType
Cause = errors.Cause
Unwrap = errors.Unwrap
UnwrapAll = errors.UnwrapAll
@ -109,6 +108,21 @@ func AsInterface[I any](err error, target *I) bool {
return errors.As(err, target)
}
// HasType checks if the error tree err has a node of type T.
//
// CAVEAT: HasType is implemented via As. So strictly speaking, it is
// possible that HasType returns true via some implementation of
// `interface { As(target any) bool }` in the error tree that
// doesn't actually check the type.
func HasType[T error](err error) bool {
// At the moment, the cockroachdb/errors package's implementation
// of HasType does not correctly handle multi-errors, whereas As does,
// so we implement HasType via As.
// (See https://github.com/cockroachdb/errors/issues/145)
var zero T
return As(err, &zero)
}
// Extend multiError to work with cockroachdb errors. Implement here to keep imports in
// one place.

View File

@ -37,25 +37,23 @@ func TestInvariants(t *testing.T) {
rapid.Just(error(&notTheErrorOfInterest{})),
rapid.Just(error(payloadLessStructError{})),
)).Draw(t, "err")
// Is implies As for errors without data
// Is implies HasType and As for errors without data
if Is(err, payloadLessStructError{}) {
// This can be false, see Counter-example 1
//require.True(t, HasType(err, payloadLessStructError{}))
require.True(t, HasType[payloadLessStructError](err))
var check payloadLessStructError
require.True(t, As(err, &check))
}
// HasType implies Is and As for errors without data
if HasType(err, payloadLessStructError{}) {
if HasType[payloadLessStructError](err) {
require.True(t, Is(err, payloadLessStructError{}))
var check payloadLessStructError
require.True(t, As(err, &check))
}
var check payloadLessStructError
// As implies Is for errors without data
// As implies Is and HasType for errors without data
if As(err, &check) {
require.True(t, Is(err, payloadLessStructError{}))
// This can be false, see Counter-example 2
//require.True(t, HasType(err, payloadLessStructError{}))
require.True(t, HasType[payloadLessStructError](err))
}
})
})
@ -70,31 +68,24 @@ func TestInvariants(t *testing.T) {
rapid.Just(error(errorWithOtherData)),
)).Draw(t, "err")
// Is implies As for errors with data
// Is implies HasType and As for errors with data
if Is(err, errorOfInterest) {
// This is false, see Counter-example 5
// This is false, see Counter-example 2
//require.False(t, Is(err, errorWithOtherData))
require.False(t, Is(err, withPayloadStructError{}))
// These can be false, see Counter-example 1
//require.True(t, HasType(err, errorOfInterest))
//require.True(t, HasType(err, errorWithOtherData))
//require.True(t, HasType(err, withPayloadStructError{}))
require.True(t, HasType[withPayloadStructError](err))
var check withPayloadStructError
require.True(t, As(err, &check))
// This can be false, see Counter-example 6
// This can be false, see Counter-example 3
//require.Equal(t, errorOfInterest, check)
}
// HasType implies As for errors with data
if HasType(err, errorOfInterest) {
require.True(t, HasType(err, errorWithOtherData))
require.True(t, HasType(err, withPayloadStructError{}))
// This can be false, see Counter-example 3
if HasType[withPayloadStructError](err) {
// This can be false, see Counter-example 1
//require.True(t, Is(err, errorOfInterest))
var check withPayloadStructError
require.True(t, As(err, &check))
// This can be false, see Counter-example 4
//require.Equal(t, errorOfInterest, check)
}
// As implies a limited form of Is for errors with data
@ -102,10 +93,7 @@ func TestInvariants(t *testing.T) {
if As(err, &check) {
require.True(t, check == errorOfInterest || check == errorWithOtherData)
require.True(t, Is(err, errorOfInterest) || Is(err, errorWithOtherData))
// These can be false, see Counter-example 2
//require.True(t, HasType(err, errorOfInterest))
//require.True(t, HasType(err, errorWithOtherData))
//require.True(t, HasType(err, withPayloadStructError{}))
require.True(t, HasType[withPayloadStructError](err))
}
})
})
@ -116,25 +104,23 @@ func TestInvariants(t *testing.T) {
rapid.Just(error(&notTheErrorOfInterest{})),
rapid.Just(error(&payloadLessPtrError{})),
)).Draw(t, "err")
// Is implies As for errors without data
// Is implies HasType and As for errors without data
if Is(err, &payloadLessPtrError{}) {
// This can be false, see Counter-example 1
//require.True(t, HasType(err, &payloadLessPtrError{}))
require.True(t, HasType[*payloadLessPtrError](err))
var check *payloadLessPtrError
require.True(t, As(err, &check))
}
// HasType implies Is and As for errors without data
if HasType(err, &payloadLessPtrError{}) {
if HasType[*payloadLessPtrError](err) {
require.True(t, Is(err, &payloadLessPtrError{}))
var check *payloadLessPtrError
require.True(t, As(err, &check))
}
var check *payloadLessPtrError
// As implies Is for errors without data
// As implies Is and HasType for errors without data
if As(err, &check) {
require.True(t, Is(err, &payloadLessPtrError{}))
// This can be false, see Counter-example 2
//require.True(t, errors.HasType(err, &payloadLessPtrError{}))
require.True(t, HasType[*payloadLessPtrError](err))
}
})
})
@ -149,81 +135,51 @@ func TestInvariants(t *testing.T) {
rapid.Just(error(errorWithOtherData)),
)).Draw(t, "err")
// Is implies As for errors with data
// Is implies HasType and As for errors with data
if Is(err, errorOfInterest) {
// This is false, see Counter-example 5
// This is false, see Counter-example 2
//require.False(t, Is(err, errorWithOtherData))
require.False(t, Is(err, &withPayloadPtrError{}))
// These can be false, see Counter-example 1
//require.True(t, HasType(err, errorOfInterest))
//require.True(t, HasType(err, errorWithOtherData))
//require.True(t, HasType(err, withPayloadStructError{}))
require.True(t, HasType[*withPayloadPtrError](err))
var check *withPayloadPtrError
require.True(t, As(err, &check))
// This can be false, see Counter-example 6
// This can be false, see Counter-example 3
//require.Equal(t, *errorOfInterest, *check)
}
// HasType implies As for errors with data
if HasType(err, errorOfInterest) {
require.True(t, HasType(err, errorWithOtherData))
require.True(t, HasType(err, &withPayloadPtrError{}))
//This can be false, see Counter-example 3
if HasType[*withPayloadPtrError](err) {
//This can be false, see Counter-example 1
//require.True(t, Is(err, errorOfInterest))
var check *withPayloadPtrError
require.True(t, As(err, &check))
require.True(t, *check == *errorOfInterest || *check == *errorWithOtherData)
}
// As implies a limited form of Is for errors with data
// As implies HasType and a limited form of Is for errors with data
var check *withPayloadPtrError
if As(err, &check) {
require.True(t, *check == *errorOfInterest || *check == *errorWithOtherData)
require.True(t, Is(err, errorOfInterest) || Is(err, errorWithOtherData))
// These can be false, see Counter-example 2
//require.True(t, HasType(err, errorOfInterest))
//require.True(t, HasType(err, errorWithOtherData))
require.True(t, HasType[*withPayloadPtrError](err))
}
})
})
t.Run("Counter-examples", func(t *testing.T) {
// Counter-example 1. Is does not imply HasType
{
err := Append(payloadLessStructError{}, &notTheErrorOfInterest{})
check := payloadLessStructError{}
require.True(t, Is(err, check))
require.False(t, HasType(err, check))
}
// Counter-example 2. As does not imply HasType
{
err := Append(payloadLessStructError{}, &notTheErrorOfInterest{})
check := payloadLessStructError{}
require.True(t, As(err, &check))
require.False(t, HasType(err, payloadLessStructError{}))
}
// Counter-example 3. HasType does not imply Is
// Counter-example 1. HasType does not imply Is
{
err := error(withPayloadStructError{data: 3})
require.True(t, HasType(err, withPayloadStructError{}))
require.True(t, HasType[withPayloadStructError](err))
require.False(t, Is(err, withPayloadStructError{data: 1}))
}
// Counter-example 4. HasType does not imply As
{
err := error(withPayloadStructError{data: 3})
hasTypeCheck := withPayloadStructError{data: 1}
require.True(t, HasType(err, hasTypeCheck))
var valueFromAs withPayloadStructError
require.True(t, As(err, &valueFromAs))
require.NotEqual(t, hasTypeCheck, valueFromAs)
}
// Counter-example 5. Is can return true for distinct values
// Counter-example 2. Is can return true for distinct values
{
err := Append(withPayloadStructError{data: 3}, withPayloadStructError{data: 1})
require.True(t, Is(err, withPayloadStructError{data: 3}))
require.True(t, Is(err, withPayloadStructError{data: 1}))
}
// Counter-example 6. As can return a different value than the one passed to Is
// Counter-example 3. As can return a different value than the one passed to Is
{
err := Append(withPayloadStructError{data: 3}, withPayloadStructError{data: 1})
var check withPayloadStructError