Skip to content
2 changes: 2 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ linters:
msg: Use errors.Is(err, fs.ErrPermission) instead.
- pattern: 'sync\.Once\b($|[^FV])'
msg: Use sync.OnceFunc, sync.OnceValue, or sync.OnceValues instead.
- pattern: 'errors\.As\b'
msg: 'Use errors.AsType[T](err) for type-safe error unwrapping (Go 1.26+).'
analyze-types: true
copyloopvar:
check-alias: true
Expand Down
4 changes: 2 additions & 2 deletions bundle/config/mutator/translate_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,10 @@ func (t *translateContext) translateLocalRelativeWithPrefixPath(ctx context.Cont
func (t *translateContext) rewriteValue(ctx context.Context, p dyn.Path, v dyn.Value, dir string, opts translateOptions) (dyn.Value, error) {
out, err := t.rewritePath(ctx, dir, v.MustString(), opts)
if err != nil {
if target := (&ErrIsNotebook{}); errors.As(err, target) {
if target, ok := errors.AsType[ErrIsNotebook](err); ok {
return dyn.InvalidValue, fmt.Errorf(`expected a file for "%s" but got a notebook: %w`, p, target)
}
if target := (&ErrIsNotNotebook{}); errors.As(err, target) {
if target, ok := errors.AsType[ErrIsNotNotebook](err); ok {
return dyn.InvalidValue, fmt.Errorf(`expected a notebook for "%s" but got a file: %w`, p, target)
}
return dyn.InvalidValue, err
Expand Down
3 changes: 1 addition & 2 deletions bundle/config/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ func LoadFromBytes(path string, raw []byte) (*Root, diag.Diagnostics) {
// Load configuration tree from YAML.
v, err := yamlloader.LoadYAML(path, bytes.NewBuffer(raw))
if err != nil {
var le *yamlloader.LocationError
if errors.As(err, &le) {
if le, ok := errors.AsType[*yamlloader.LocationError](err); ok {
return nil, diag.Diagnostics{{
Severity: diag.Error,
Summary: le.Summary,
Expand Down
3 changes: 1 addition & 2 deletions bundle/deploy/resource_path_mkdir.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ func (m *resourcePathMkdir) Apply(ctx context.Context, b *bundle.Bundle) diag.Di

// Optimisitcally create the resource path. If it already exists ignore the error.
err := w.Workspace.MkdirsByPath(ctx, b.Config.Workspace.ResourcePath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately.
var aerr *apierr.APIError
if errors.As(err, &aerr) && aerr.ErrorCode == "RESOURCE_ALREADY_EXISTS" {
if aerr, ok := errors.AsType[*apierr.APIError](err); ok && aerr.ErrorCode == "RESOURCE_ALREADY_EXISTS" {
return nil
}
return diag.FromErr(err)
Expand Down
4 changes: 2 additions & 2 deletions bundle/direct/bundle_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,8 @@ func prepareChanges(ctx context.Context, adapter *dresources.Adapter, localDiff,
// we have difference for remoteState but not difference for localState
// from remoteDiff we can find out remote value (ch.Old) and new config value (ch.New) but we don't know oldState value
oldStateVal, err := structaccess.Get(oldState, ch.Path)
var notFound *structaccess.NotFoundError
if err != nil && !errors.As(err, &notFound) {
_, isNotFound := errors.AsType[*structaccess.NotFoundError](err)
if err != nil && !isNotFound {
log.Debugf(ctx, "Constructing diff: accessing %q on %T: %s", ch.Path, oldState, err)
}
m[ch.Path.String()] = &deployplan.ChangeDesc{
Expand Down
3 changes: 1 addition & 2 deletions bundle/direct/dresources/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,9 @@ func (r *ResourceCluster) DoUpdate(ctx context.Context, id string, config *compu
return wait, nil
}

var apiErr *apierr.APIError
// Only Running and Terminated clusters can be modified. In particular, autoscaling clusters cannot be modified
// while the resizing is ongoing. We retry in this case. Scaling can take several minutes.
if errors.As(err, &apiErr) && apiErr.ErrorCode == "INVALID_STATE" {
if apiErr, ok := errors.AsType[*apierr.APIError](err); ok && apiErr.ErrorCode == "INVALID_STATE" {
return nil, retries.Continues(fmt.Sprintf("cluster %s cannot be modified in its current state: %s", id, apiErr.Message))
}
return nil, retries.Halt(err)
Expand Down
3 changes: 1 addition & 2 deletions bundle/direct/dresources/postgres_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,7 @@ func (r *ResourcePostgresEndpoint) DoDelete(ctx context.Context, id string) erro
})
if err != nil {
// Check if this is a reconciliation in progress error
var apiErr *apierr.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusConflict &&
if apiErr, ok := errors.AsType[*apierr.APIError](err); ok && apiErr.StatusCode == http.StatusConflict &&
strings.Contains(apiErr.Message, "reconciliation") {
// Check if we've exceeded the timeout
if time.Now().After(deadline) {
Expand Down
3 changes: 1 addition & 2 deletions bundle/direct/dresources/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import (
// This is copied from the retries package of the databricks-sdk-go. It should be made public,
// but for now, I'm copying it here.
func shouldRetry(err error) bool {
var e *retries.Err
if errors.As(err, &e) {
if e, ok := errors.AsType[*retries.Err](err); ok {
return !e.Halt
}
return false
Expand Down
4 changes: 2 additions & 2 deletions bundle/direct/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ func isResourceGone(err error) bool {
// the parent's Delete will cascade-clean. Mirrors the TF provider's
// declarative.IsDeleteError suppression.
func isManagedByParent(err error) bool {
var apiErr *apierr.APIError
if !errors.As(err, &apiErr) || apiErr == nil {
apiErr, ok := errors.AsType[*apierr.APIError](err)
if !ok || apiErr == nil {
return false
}
info := apiErr.ErrorDetails().ErrorInfo
Expand Down
3 changes: 1 addition & 2 deletions bundle/phases/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ func assertRootPathExists(ctx context.Context, b *bundle.Bundle) (bool, error) {
w := b.WorkspaceClient(ctx)
_, err := w.Workspace.GetStatusByPath(ctx, b.Config.Workspace.RootPath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately.

var aerr *apierr.APIError
if errors.As(err, &aerr) && aerr.StatusCode == http.StatusNotFound {
if aerr, ok := errors.AsType[*apierr.APIError](err); ok && aerr.StatusCode == http.StatusNotFound {
log.Infof(ctx, "Root path does not exist: %s", b.Config.Workspace.RootPath)
return false, nil
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/bundle/deployment/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ func runPlanCheck(cmd *cobra.Command, extraArgs []string, extraArgsStr string) e
fmt.Fprint(cmd.OutOrStdout(), output)

if err != nil {
var exitErr *exec.ExitError
msg := ""
if errors.As(err, &exitErr) {
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
msg = fmt.Sprintf("exit code %d", exitErr.ExitCode())
} else {
msg = err.Error()
Expand Down
3 changes: 1 addition & 2 deletions cmd/bundle/generate/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ After generation, you can deploy this alert to other targets using:
alert, err := w.AlertsV2.GetAlert(ctx, sql.GetAlertV2Request{Id: alertID})
if err != nil {
// Check if it's a not found error to provide a better message
var apiErr *apierr.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
if apiErr, ok := errors.AsType[*apierr.APIError](err); ok && apiErr.StatusCode == http.StatusNotFound {
return fmt.Errorf("alert with ID %s not found", alertID)
}
return err
Expand Down
3 changes: 1 addition & 2 deletions cmd/labs/localcache/jsonfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ type cached[T any] struct {

func (r *LocalCache[T]) refreshCache(ctx context.Context, refresh func() (T, error), offlineVal T) (T, error) {
data, err := refresh()
var urlError *url.Error
if errors.As(err, &urlError) {
if urlError, ok := errors.AsType[*url.Error](err); ok {
log.Warnf(ctx, "System offline. Cannot refresh cache: %s", urlError)
return offlineVal, nil
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/labs/project/interpreters.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ func DetectInterpreters(ctx context.Context) (allInterpreters, error) {
// Keep in mind, that mswin installations get python.exe and pythonw.exe,
// which are slightly different: see https://stackoverflow.com/a/30313091
out, err := process.Background(ctx, []string{resolved, "--version"})
var processErr *process.ProcessError
if errors.As(err, &processErr) {
if processErr, ok := errors.AsType[*process.ProcessError](err); ok {
log.Debugf(ctx, "failed to check version for %s: %s", resolved, processErr.Err)
continue
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/root/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ func MustAnyClient(cmd *cobra.Command, args []string) (bool, error) {
// If the error indicates a wrong config type (workspace host used for account client,
// or config type mismatch detected by workspaceClientOrPrompt), fall through to try
// account client.
if !errors.Is(werr, errNotWorkspaceClient) && !errors.As(werr, &ErrNoWorkspaceProfiles{}) {
if _, ok := errors.AsType[ErrNoWorkspaceProfiles](werr); !errors.Is(werr, errNotWorkspaceClient) && !ok {
return false, werr
}

// Otherwise, the config used is account client one, so try to create an account client
aerr := MustAccountClient(cmd, args)
if errors.As(aerr, &ErrNoAccountProfiles{}) {
if _, ok := errors.AsType[ErrNoAccountProfiles](aerr); ok {
return false, aerr
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/root/flag_suggestions.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ func levenshteinDistance(a, b string) int {
// If a close match is found among the command's flags, it returns an enhanced error
// with a "Did you mean" suggestion appended. Otherwise it returns the original error.
func suggestFlagFromError(cmd *cobra.Command, err error) error {
var notExist *pflag.NotExistError
if !errors.As(err, &notExist) {
notExist, ok := errors.AsType[*pflag.NotExistError](err)
if !ok {
return err
}

Expand Down
7 changes: 3 additions & 4 deletions cmd/workspace/apps/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ const tailLinesSuggestedValue = 100
// These are errors wrapped by retries.Halt() during GetWithTimeout().
// Excludes API client errors (4xx) which are validation errors before deployment starts.
func isDeploymentWaitError(err error) bool {
var retriesErr *retries.Err
if !errors.As(err, &retriesErr) || !retriesErr.Halt {
retriesErr, ok := errors.AsType[*retries.Err](err)
if !ok || !retriesErr.Halt {
return false
}

// Exclude API client errors (4xx) (e.g. app not found)
var apiErr *apierr.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 {
if apiErr, ok := errors.AsType[*apierr.APIError](err); ok && apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 {
return false
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/workspace/workspace/export_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ type exportDirOptions struct {

// isFileSizeError checks if the error is due to file size limits.
func isFileSizeError(err error) bool {
var aerr *apierr.APIError
if !errors.As(err, &aerr) || aerr.StatusCode != http.StatusBadRequest {
aerr, ok := errors.AsType[*apierr.APIError](err)
if !ok || aerr.StatusCode != http.StatusBadRequest {
return false
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/workspace/workspace/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ func exportOverride(exportCmd *cobra.Command, exportReq *workspace.ExportRequest

// Give better errors / hints for common API errors.
func wrapImportAPIErrors(err error, importReq *workspace.Import) error {
apiErr := &apierr.APIError{}
if !errors.As(err, &apiErr) {
apiErr, ok := errors.AsType[*apierr.APIError](err)
if !ok {
return err
}
isFormatSource := importReq.Format == workspace.ImportFormatSource || importReq.Format == ""
Expand Down
9 changes: 3 additions & 6 deletions experimental/postgres/cmd/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ func isRetryableConnectError(err error) bool {
return false
}

var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
switch {
// 08xxx is the connection_exception class.
case len(pgErr.Code) == 5 && pgErr.Code[:2] == "08":
Expand All @@ -169,13 +168,11 @@ func isRetryableConnectError(err error) bool {
}
}

var connectErr *pgconn.ConnectError
if errors.As(err, &connectErr) {
if connectErr, ok := errors.AsType[*pgconn.ConnectError](err); ok {
return isRetryableConnectError(connectErr.Unwrap())
}

var opErr *net.OpError
if errors.As(err, &opErr) {
if opErr, ok := errors.AsType[*net.OpError](err); ok {
return opErr.Op == "dial"
}

Expand Down
4 changes: 2 additions & 2 deletions experimental/postgres/cmd/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
// surface it directly. The richer LINE+caret rendering is out of scope for
// this PR; we stick with the plain shape for now.
func formatPgError(err error) string {
var pgErr *pgconn.PgError
if !errors.As(err, &pgErr) {
pgErr, ok := errors.AsType[*pgconn.PgError](err)
if !ok {
return err.Error()
}

Expand Down
3 changes: 1 addition & 2 deletions integration/libs/filer/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ func setupWsfsFiler(t testutil.TestingT) (filer.Filer, string) {

// Check if we can use this API here, skip test if we cannot.
_, err = f.Read(ctx, "we_use_this_call_to_test_if_this_api_is_enabled")
var aerr *apierr.APIError
if errors.As(err, &aerr) && aerr.StatusCode == http.StatusBadRequest {
if aerr, ok := errors.AsType[*apierr.APIError](err); ok && aerr.StatusCode == http.StatusBadRequest {
t.Skip(aerr.Message)
}

Expand Down
10 changes: 4 additions & 6 deletions libs/apps/logstream/streamer.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,7 @@ func (s *logStreamer) consume(ctx context.Context, conn *websocket.Conn) (retErr
if ctx.Err() != nil {
return ctx.Err()
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
if netErr, ok := errors.AsType[net.Error](err); ok && netErr.Timeout() {
if state.HasPendingFlushDeadline() {
shouldContinue, flushErr := state.HandleFlushTimeout()
if flushErr != nil {
Expand Down Expand Up @@ -308,8 +307,7 @@ func (s *logStreamer) shouldRefreshForStatus(respStatusCode *int) bool {
}

func (s *logStreamer) shouldRefreshForError(err error) bool {
var closeErr *websocket.CloseError
if errors.As(err, &closeErr) {
if closeErr, ok := errors.AsType[*websocket.CloseError](err); ok {
switch closeErr.Code {
case closeCodeUnauthorized, closeCodeForbidden:
return true
Expand All @@ -336,8 +334,8 @@ func decorateDialError(err error, resp *http.Response) error {
}

func handleCloseError(err error) (bool, error) {
var closeErr *websocket.CloseError
if !errors.As(err, &closeErr) {
closeErr, ok := errors.AsType[*websocket.CloseError](err)
if !ok {
return false, err
}
if closeErr.Code == websocket.CloseNormalClosure || closeErr.Code == websocket.CloseGoingAway {
Expand Down
7 changes: 3 additions & 4 deletions libs/auth/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ func AuthTypeDisplayName(authType string) string {
// RewriteAuthError rewrites the error message for invalid refresh token error.
// It returns whether the error was rewritten and the rewritten error.
func RewriteAuthError(ctx context.Context, host, accountId, profile string, err error) (bool, error) {
target := &u2m.InvalidRefreshTokenError{}
if errors.As(err, &target) {
if _, ok := errors.AsType[*u2m.InvalidRefreshTokenError](err); ok {
oauthArgument, err := AuthArguments{
Host: host,
AccountID: accountId,
Expand All @@ -73,8 +72,8 @@ func RewriteAuthError(ctx context.Context, host, accountId, profile string, err
// EnrichAuthError appends identity context and remediation steps to 401/403 API errors.
// For non-API errors or other status codes, the original error is returned unchanged.
func EnrichAuthError(ctx context.Context, cfg *config.Config, err error) error {
var apiErr *apierr.APIError
if !errors.As(err, &apiErr) {
apiErr, ok := errors.AsType[*apierr.APIError](err)
if !ok {
return err
}
if apiErr.StatusCode != http.StatusUnauthorized && apiErr.StatusCode != http.StatusForbidden {
Expand Down
6 changes: 2 additions & 4 deletions libs/auth/storage/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,7 @@ func applyReadFallback(ctx context.Context, mode StorageMode, explicit bool, f c
return f.newKeyring(), mode, nil
}
if probeErr := f.probeKeyringRead(); probeErr != nil {
var timeoutErr *TimeoutError
if errors.As(probeErr, &timeoutErr) {
if _, ok := errors.AsType[*TimeoutError](probeErr); ok {
log.Debugf(ctx, "keyring read probe timed out (%v); staying on keyring", probeErr)
return f.newKeyring(), mode, nil
}
Expand Down Expand Up @@ -205,8 +204,7 @@ func applyLoginFallback(ctx context.Context, mode StorageMode, explicit bool, f
// during OAuth is the common case, and a misdiagnosed hang
// fails the final Store anyway, which is better than a
// silent plaintext downgrade.
var timeoutErr *TimeoutError
if errors.As(probeErr, &timeoutErr) {
if _, ok := errors.AsType[*TimeoutError](probeErr); ok {
log.Debugf(ctx, "keyring probe timed out (%v); staying on keyring", probeErr)
return f.newKeyring(), mode, nil
}
Expand Down
3 changes: 1 addition & 2 deletions libs/auth/storage/not_found_hint.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ func (e *notFoundHint) Unwrap() error { return cache.ErrNotFound }
// logic) but want to surface the actionable hint to the user instead of
// dropping it.
func HintForNotFound(err error) string {
var hint *notFoundHint
if errors.As(err, &hint) {
if hint, ok := errors.AsType[*notFoundHint](err); ok {
return hint.msg
}
return ""
Expand Down
6 changes: 2 additions & 4 deletions libs/clicompat/clicompat.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ func IsNotFoundError(err error) bool {
if errors.Is(err, ErrNotFound) {
return true
}
var httpErr *HTTPStatusError
if errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusNotFound {
if httpErr, ok := errors.AsType[*HTTPStatusError](err); ok && httpErr.StatusCode == http.StatusNotFound {
return true
}
// Git clone errors include "not found" in stderr when a branch/tag does not
Expand Down Expand Up @@ -384,8 +383,7 @@ func fetchRemoteWithRetry(ctx context.Context) (Manifest, error) {
lastErr = err

// Do not retry client errors (4xx) — they won't resolve on retry.
var httpErr *HTTPStatusError
if errors.As(err, &httpErr) && httpErr.StatusCode >= 400 && httpErr.StatusCode < 500 {
if httpErr, ok := errors.AsType[*HTTPStatusError](err); ok && httpErr.StatusCode >= 400 && httpErr.StatusCode < 500 {
return nil, lastErr
}
}
Expand Down
4 changes: 2 additions & 2 deletions libs/databrickscfg/cfgpickers/warehouses.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ func GetDefaultWarehouse(ctx context.Context, w *databricks.WorkspaceClient) (*s
State: warehouse.State,
}, nil
}
var apiErr *apierr.APIError
if !errors.As(err, &apiErr) || apiErr.StatusCode >= 500 {
apiErr, ok := errors.AsType[*apierr.APIError](err)
if !ok || apiErr.StatusCode >= 500 {
return nil, fmt.Errorf("get default warehouse: %w", err)
}

Expand Down
3 changes: 1 addition & 2 deletions libs/databrickscfg/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ func (e errMultipleProfiles) Error() string {
// AsMultipleProfiles checks if the error is caused by multiple profiles
// matching the same host. If so, it returns the matching profile names.
func AsMultipleProfiles(err error) ([]string, bool) {
var e errMultipleProfiles
if errors.As(err, &e) {
if e, ok := errors.AsType[errMultipleProfiles](err); ok {
return []string(e), true
}
return nil, false
Expand Down
Loading
Loading