diff --git a/.golangci.yaml b/.golangci.yaml index 6f41b1cee22..2eb9296e0a0 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -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 diff --git a/bundle/config/mutator/translate_paths.go b/bundle/config/mutator/translate_paths.go index 99dd75dd787..dc730c1ff1c 100644 --- a/bundle/config/mutator/translate_paths.go +++ b/bundle/config/mutator/translate_paths.go @@ -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 diff --git a/bundle/config/root.go b/bundle/config/root.go index 6d4697cc1ba..caca8e1f1ad 100644 --- a/bundle/config/root.go +++ b/bundle/config/root.go @@ -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, diff --git a/bundle/deploy/resource_path_mkdir.go b/bundle/deploy/resource_path_mkdir.go index ab070b925e6..c697fd30dea 100644 --- a/bundle/deploy/resource_path_mkdir.go +++ b/bundle/deploy/resource_path_mkdir.go @@ -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) diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index 5c543619b06..1d75ad02b74 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -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, ¬Found) { + _, 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{ diff --git a/bundle/direct/dresources/cluster.go b/bundle/direct/dresources/cluster.go index 46148a2655f..34edce25168 100644 --- a/bundle/direct/dresources/cluster.go +++ b/bundle/direct/dresources/cluster.go @@ -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) diff --git a/bundle/direct/dresources/postgres_endpoint.go b/bundle/direct/dresources/postgres_endpoint.go index df9f79ebd3b..51829e6ef25 100644 --- a/bundle/direct/dresources/postgres_endpoint.go +++ b/bundle/direct/dresources/postgres_endpoint.go @@ -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) { diff --git a/bundle/direct/dresources/util.go b/bundle/direct/dresources/util.go index 658de7be528..39c7fcb284e 100644 --- a/bundle/direct/dresources/util.go +++ b/bundle/direct/dresources/util.go @@ -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 diff --git a/bundle/direct/util.go b/bundle/direct/util.go index 3b4c9abd651..f3a16692860 100644 --- a/bundle/direct/util.go +++ b/bundle/direct/util.go @@ -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 diff --git a/bundle/phases/destroy.go b/bundle/phases/destroy.go index 98e6f7fee2a..58f646d7b3b 100644 --- a/bundle/phases/destroy.go +++ b/bundle/phases/destroy.go @@ -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 } diff --git a/cmd/bundle/deployment/migrate.go b/cmd/bundle/deployment/migrate.go index 77d95e3533e..801e46f7918 100644 --- a/cmd/bundle/deployment/migrate.go +++ b/cmd/bundle/deployment/migrate.go @@ -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() diff --git a/cmd/bundle/generate/alert.go b/cmd/bundle/generate/alert.go index 87ba3eacb9e..f8c0cc2f409 100644 --- a/cmd/bundle/generate/alert.go +++ b/cmd/bundle/generate/alert.go @@ -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 diff --git a/cmd/labs/localcache/jsonfile.go b/cmd/labs/localcache/jsonfile.go index 1a7c6b940ce..d86ff2dd9e1 100644 --- a/cmd/labs/localcache/jsonfile.go +++ b/cmd/labs/localcache/jsonfile.go @@ -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 } diff --git a/cmd/labs/project/interpreters.go b/cmd/labs/project/interpreters.go index 5b6bff16fc2..dfa51a1a5ba 100644 --- a/cmd/labs/project/interpreters.go +++ b/cmd/labs/project/interpreters.go @@ -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 } diff --git a/cmd/root/auth.go b/cmd/root/auth.go index f458f0f4695..8b9bfd0810b 100644 --- a/cmd/root/auth.go +++ b/cmd/root/auth.go @@ -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 } diff --git a/cmd/root/flag_suggestions.go b/cmd/root/flag_suggestions.go index effef1fccac..8aa1d822774 100644 --- a/cmd/root/flag_suggestions.go +++ b/cmd/root/flag_suggestions.go @@ -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, ¬Exist) { + notExist, ok := errors.AsType[*pflag.NotExistError](err) + if !ok { return err } diff --git a/cmd/workspace/apps/errors.go b/cmd/workspace/apps/errors.go index 80ccc8af75f..cbcb2ce3860 100644 --- a/cmd/workspace/apps/errors.go +++ b/cmd/workspace/apps/errors.go @@ -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 } diff --git a/cmd/workspace/workspace/export_dir.go b/cmd/workspace/workspace/export_dir.go index b6b4b86c8ad..e7965bcda91 100644 --- a/cmd/workspace/workspace/export_dir.go +++ b/cmd/workspace/workspace/export_dir.go @@ -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 } diff --git a/cmd/workspace/workspace/overrides.go b/cmd/workspace/workspace/overrides.go index c57209b554b..74186ebaf70 100644 --- a/cmd/workspace/workspace/overrides.go +++ b/cmd/workspace/workspace/overrides.go @@ -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 == "" diff --git a/experimental/postgres/cmd/connect.go b/experimental/postgres/cmd/connect.go index 0cc47c998a0..79e7545b152 100644 --- a/experimental/postgres/cmd/connect.go +++ b/experimental/postgres/cmd/connect.go @@ -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": @@ -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" } diff --git a/experimental/postgres/cmd/error.go b/experimental/postgres/cmd/error.go index 02278a6c58b..5fdcf6eed99 100644 --- a/experimental/postgres/cmd/error.go +++ b/experimental/postgres/cmd/error.go @@ -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() } diff --git a/integration/libs/filer/helpers_test.go b/integration/libs/filer/helpers_test.go index a3a3aaae589..ead83d66b08 100644 --- a/integration/libs/filer/helpers_test.go +++ b/integration/libs/filer/helpers_test.go @@ -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) } diff --git a/libs/apps/logstream/streamer.go b/libs/apps/logstream/streamer.go index 5cbfc87ede9..16f4d4bf742 100644 --- a/libs/apps/logstream/streamer.go +++ b/libs/apps/logstream/streamer.go @@ -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 { @@ -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 @@ -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 { diff --git a/libs/auth/error.go b/libs/auth/error.go index 2ca8aa5f800..7cfb6b6fc76 100644 --- a/libs/auth/error.go +++ b/libs/auth/error.go @@ -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, @@ -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 { diff --git a/libs/auth/storage/cache.go b/libs/auth/storage/cache.go index c3d1fc43dcf..94a15a7ef0f 100644 --- a/libs/auth/storage/cache.go +++ b/libs/auth/storage/cache.go @@ -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 } @@ -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 } diff --git a/libs/auth/storage/not_found_hint.go b/libs/auth/storage/not_found_hint.go index d86615535b5..2e6e13e064d 100644 --- a/libs/auth/storage/not_found_hint.go +++ b/libs/auth/storage/not_found_hint.go @@ -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 "" diff --git a/libs/clicompat/clicompat.go b/libs/clicompat/clicompat.go index 8d982db132e..be9c27db4b5 100644 --- a/libs/clicompat/clicompat.go +++ b/libs/clicompat/clicompat.go @@ -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 @@ -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 } } diff --git a/libs/databrickscfg/cfgpickers/warehouses.go b/libs/databrickscfg/cfgpickers/warehouses.go index 9f0c617e34b..22c22d09022 100644 --- a/libs/databrickscfg/cfgpickers/warehouses.go +++ b/libs/databrickscfg/cfgpickers/warehouses.go @@ -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) } diff --git a/libs/databrickscfg/loader.go b/libs/databrickscfg/loader.go index 8f4f2a38dbc..5d942b0b7c0 100644 --- a/libs/databrickscfg/loader.go +++ b/libs/databrickscfg/loader.go @@ -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 diff --git a/libs/diag/sdk_error.go b/libs/diag/sdk_error.go index 1099dd738dc..d190498d6bf 100644 --- a/libs/diag/sdk_error.go +++ b/libs/diag/sdk_error.go @@ -10,8 +10,8 @@ import ( ) func FormatAPIErrorSummary(e error) string { - var apiErr *apierr.APIError - if !errors.As(e, &apiErr) { + apiErr, ok := errors.AsType[*apierr.APIError](e) + if !ok { return e.Error() } extra := strings.TrimSpace(fmt.Sprintf("%d %s", apiErr.StatusCode, apiErr.ErrorCode)) @@ -19,8 +19,8 @@ func FormatAPIErrorSummary(e error) string { } func FormatAPIErrorDetails(e error) string { - var apiErr *apierr.APIError - if !errors.As(e, &apiErr) { + apiErr, ok := errors.AsType[*apierr.APIError](e) + if !ok { return "" } diff --git a/libs/dyn/visit.go b/libs/dyn/visit.go index 1822c7db653..7ae00fa8e08 100644 --- a/libs/dyn/visit.go +++ b/libs/dyn/visit.go @@ -24,8 +24,8 @@ func (e cannotTraverseNilError) Error() string { } func IsCannotTraverseNilError(err error) bool { - var target cannotTraverseNilError - return errors.As(err, &target) + _, ok := errors.AsType[cannotTraverseNilError](err) + return ok } type noSuchKeyError struct { @@ -37,8 +37,8 @@ func (e noSuchKeyError) Error() string { } func IsNoSuchKeyError(err error) bool { - var target noSuchKeyError - return errors.As(err, &target) + _, ok := errors.AsType[noSuchKeyError](err) + return ok } type indexOutOfBoundsError struct { @@ -50,8 +50,8 @@ func (e indexOutOfBoundsError) Error() string { } func IsIndexOutOfBoundsError(err error) bool { - var target indexOutOfBoundsError - return errors.As(err, &target) + _, ok := errors.AsType[indexOutOfBoundsError](err) + return ok } type expectedMapToIndexError struct { diff --git a/libs/errs/aggregate.go b/libs/errs/aggregate.go index 08836494867..ba856100b30 100644 --- a/libs/errs/aggregate.go +++ b/libs/errs/aggregate.go @@ -60,7 +60,7 @@ func (ec errorChain) Unwrap() error { } func (ec errorChain) As(target any) bool { - return errors.As(ec[0], target) + return errors.As(ec[0], target) //nolint:forbidigo // forwarding the errors.As interface method; target type is dynamic and cannot use errors.AsType } func (ec errorChain) Is(target error) bool { diff --git a/libs/filer/dbfs_client.go b/libs/filer/dbfs_client.go index 761f279036d..ce9286ea8d9 100644 --- a/libs/filer/dbfs_client.go +++ b/libs/filer/dbfs_client.go @@ -98,8 +98,8 @@ func (w *DbfsClient) Write(ctx context.Context, name string, reader io.Reader, m if !slices.Contains(mode, CreateParentDirectories) { _, err = w.workspaceClient.Dbfs.GetStatusByPath(ctx, path.Dir(absPath)) if err != nil { - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -116,8 +116,8 @@ func (w *DbfsClient) Write(ctx context.Context, name string, reader io.Reader, m handle, err := w.workspaceClient.Dbfs.Open(ctx, absPath, fileMode) if err != nil { - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -153,8 +153,8 @@ func (w *DbfsClient) Read(ctx context.Context, name string) (io.ReadCloser, erro return nil, notAFile{absPath} } - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return nil, err } @@ -190,8 +190,8 @@ func (w *DbfsClient) Delete(ctx context.Context, name string, mode ...DeleteMode // _, err = w.workspaceClient.Dbfs.GetStatusByPath(ctx, absPath) if err != nil { - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -218,8 +218,8 @@ func (w *DbfsClient) Delete(ctx context.Context, name string, mode ...DeleteMode } // Special handling of this error only if it is an API error. - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -248,8 +248,8 @@ func (w *DbfsClient) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, e res, err := w.workspaceClient.Dbfs.ListByPath(ctx, absPath) if err != nil { - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return nil, err } @@ -294,8 +294,8 @@ func (w *DbfsClient) Stat(ctx context.Context, name string) (fs.FileInfo, error) info, err := w.workspaceClient.Dbfs.GetStatusByPath(ctx, absPath) if err != nil { - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return nil, err } diff --git a/libs/filer/files_client.go b/libs/filer/files_client.go index 2ac76166162..6abfa1e66c3 100644 --- a/libs/filer/files_client.go +++ b/libs/filer/files_client.go @@ -144,8 +144,8 @@ func (w *FilesClient) Write(ctx context.Context, name string, reader io.Reader, if !slices.Contains(mode, CreateParentDirectories) { err := w.workspaceClient.Files.GetDirectoryMetadataByDirectoryPath(ctx, path.Dir(absPath)) if err != nil { - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -172,8 +172,8 @@ func (w *FilesClient) Write(ctx context.Context, name string, reader io.Reader, } // Special handling of this error only if it is an API error. - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -200,8 +200,8 @@ func (w *FilesClient) Read(ctx context.Context, name string) (io.ReadCloser, err } // Special handling of this error only if it is an API error. - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return nil, err } @@ -237,9 +237,9 @@ func (w *FilesClient) deleteFile(ctx context.Context, name string) error { return nil } - var aerr *apierr.APIError // Special handling of this error only if it is an API error. - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -264,9 +264,9 @@ func (w *FilesClient) deleteDirectory(ctx context.Context, name string) error { err = w.workspaceClient.Files.DeleteDirectoryByDirectoryPath(ctx, absPath) - var aerr *apierr.APIError // Special handling of this error only if it is an API error. - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -402,13 +402,13 @@ func (w *FilesClient) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, } // Special handling of this error only if it is an API error. - var apierr *apierr.APIError - if !errors.As(err, &apierr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return nil, err } // This API returns a 404 if the specified path does not exist. - if apierr.StatusCode == http.StatusNotFound { + if aerr.StatusCode == http.StatusNotFound { // Check if the path is a file. If so, return not a directory error. if _, err := w.statFile(ctx, name); err == nil { return nil, notADirectory{absPath} @@ -431,8 +431,7 @@ func (w *FilesClient) Mkdir(ctx context.Context, name string) error { }) // Special handling of this error only if it is an API error. - var aerr *apierr.APIError - if errors.As(err, &aerr) && aerr.StatusCode == http.StatusConflict { + if aerr, ok := errors.AsType[*apierr.APIError](err); ok && aerr.StatusCode == http.StatusConflict { return fileAlreadyExistsError{absPath} } @@ -458,8 +457,8 @@ func (w *FilesClient) statFile(ctx context.Context, name string) (fs.FileInfo, e } // Special handling of this error only if it is an API error. - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return nil, err } @@ -486,8 +485,8 @@ func (w *FilesClient) statDir(ctx context.Context, name string) (fs.FileInfo, er } // Special handling of this error only if it is an API error. - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return nil, err } diff --git a/libs/filer/workspace_files_client.go b/libs/filer/workspace_files_client.go index a23c97724cf..3663473e92a 100644 --- a/libs/filer/workspace_files_client.go +++ b/libs/filer/workspace_files_client.go @@ -179,8 +179,8 @@ func (w *WorkspaceFilesClient) Write(ctx context.Context, name string, reader io } // Special handling of this error only if it is an API error. - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -193,7 +193,7 @@ func (w *WorkspaceFilesClient) Write(ctx context.Context, name string, reader io // Create parent directory. err = w.workspaceClient.Workspace.MkdirsByPath(ctx, path.Dir(absPath)) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. if err != nil { - if errors.As(err, &aerr) && aerr.StatusCode == http.StatusForbidden { + if mkdirErr, ok := errors.AsType[*apierr.APIError](err); ok && mkdirErr.StatusCode == http.StatusForbidden { return permissionError{absPath} } return fmt.Errorf("unable to mkdir to write file %s: %w", absPath, err) @@ -274,8 +274,8 @@ func (w *WorkspaceFilesClient) Delete(ctx context.Context, name string, mode ... } // Special handling of this error only if it is an API error. - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return err } @@ -307,8 +307,8 @@ func (w *WorkspaceFilesClient) ReadDir(ctx context.Context, name string) ([]fs.D if err != nil { // If we got an API error we deal with it below. - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return nil, err } @@ -359,8 +359,8 @@ func (w *WorkspaceFilesClient) Stat(ctx context.Context, name string) (fs.FileIn ) if err != nil { // If we got an API error we deal with it below. - var aerr *apierr.APIError - if !errors.As(err, &aerr) { + aerr, ok := errors.AsType[*apierr.APIError](err) + if !ok { return nil, err } diff --git a/libs/git/clone.go b/libs/git/clone.go index 9369686c994..17703b672d8 100644 --- a/libs/git/clone.go +++ b/libs/git/clone.go @@ -41,8 +41,7 @@ func (opts cloneOptions) clone(ctx context.Context) error { if errors.Is(err, exec.ErrNotFound) { return fmt.Errorf("please install git CLI to clone a repository: %w", err) } - var processErr *process.ProcessError - if errors.As(err, &processErr) { + if processErr, ok := errors.AsType[*process.ProcessError](err); ok { return fmt.Errorf("git clone failed: %w. %s", err, processErr.Stderr) } if err != nil { diff --git a/libs/psql/connect.go b/libs/psql/connect.go index 2cb89e95956..91ecada0f21 100644 --- a/libs/psql/connect.go +++ b/libs/psql/connect.go @@ -147,8 +147,7 @@ func attemptConnection(ctx context.Context, args, env []string) error { err = cmd.Wait() if err != nil { - var exitError *exec.ExitError - if errors.As(err, &exitError) { + if exitError, ok := errors.AsType[*exec.ExitError](err); ok { // psql returns exit code 2 for fatal errors if exitError.ExitCode() == 2 { connErr := fmt.Errorf("connection failed: psql exited with code %d", exitError.ExitCode()) diff --git a/libs/telemetry/logger.go b/libs/telemetry/logger.go index d56e087322e..f9df3a97f7b 100644 --- a/libs/telemetry/logger.go +++ b/libs/telemetry/logger.go @@ -153,8 +153,7 @@ func Upload(ctx context.Context, ec protos.ExecutionContext) error { // // The UI infra team (who owns the /telemetry-ext API) recommends retrying for // all 5xx responses. - var apiErr *apierr.APIError - if errors.As(err, &apiErr) && apiErr.StatusCode >= 500 { + if apiErr, ok := errors.AsType[*apierr.APIError](err); ok && apiErr.StatusCode >= 500 { log.Infof(ctx, "Attempt %d failed due to a server side error. Retrying status code: %d", i+1, apiErr.StatusCode) remainingTime := time.Until(deadline) diff --git a/libs/template/config.go b/libs/template/config.go index 020d155cfa3..9183b6d09fe 100644 --- a/libs/template/config.go +++ b/libs/template/config.go @@ -300,7 +300,7 @@ func (c *config) promptForValues(r *renderer) error { if err == nil { break } - if !errors.As(err, &retriableError{}) { + if _, ok := errors.AsType[retriableError](err); !ok { return err } } diff --git a/libs/template/helpers.go b/libs/template/helpers.go index 33e36c02483..f32ae86c60b 100644 --- a/libs/template/helpers.go +++ b/libs/template/helpers.go @@ -147,8 +147,7 @@ func loadHelpers(ctx context.Context) template.FuncMap { if cachedCatalog == nil { metastore, err := w.Metastores.Current(ctx) if err != nil { - var aerr *apierr.APIError - if errors.As(err, &aerr) && (slices.Contains(metastoreDisabledErrorCodes, aerr.ErrorCode) || aerr.Message == "Bad Target: /api/2.1/unity-catalog/current-metastore-assignment") { + if aerr, ok := errors.AsType[*apierr.APIError](err); ok && (slices.Contains(metastoreDisabledErrorCodes, aerr.ErrorCode) || aerr.Message == "Bad Target: /api/2.1/unity-catalog/current-metastore-assignment") { // Ignore: access denied or workspace doesn't have a metastore assigned empty_default := "" cachedCatalog = &empty_default diff --git a/libs/template/renderer.go b/libs/template/renderer.go index c1403fa071c..94745da2b0b 100644 --- a/libs/template/renderer.go +++ b/libs/template/renderer.go @@ -136,8 +136,7 @@ func (r *renderer) executeTemplate(templateDefinition string) (string, error) { if err != nil { // Parse and return a more readable error for missing values that are used // by the template definition but are not provided in the passed config. - target := &template.ExecError{} - if errors.As(err, target) { + if target, ok := errors.AsType[template.ExecError](err); ok { captureRegex := regexp.MustCompile(`map has no entry for key "(.*)"`) matches := captureRegex.FindStringSubmatch(target.Err.Error()) if len(matches) != 2 { @@ -200,7 +199,7 @@ func (r *renderer) computeFile(relPathTemplate string) (file, error) { } content, err := r.executeTemplate(string(contentTemplate)) // Capture errors caused by the "fail" helper function - if target := (&ErrFail{}); errors.As(err, target) { + if target, ok := errors.AsType[ErrFail](err); ok { return nil, target } if err != nil { diff --git a/libs/testproxy/server.go b/libs/testproxy/server.go index fd6038c8ed5..ccb67df1147 100644 --- a/libs/testproxy/server.go +++ b/libs/testproxy/server.go @@ -133,8 +133,7 @@ func (s *ProxyServer) proxyToCloud(w http.ResponseWriter, r *http.Request) { // exactly what the workspace returned. Re-marshalling from the parsed // APIError would drop fields the SDK doesn't surface (e.g. metadata in // details[]) and silently break callers that inspect them. - apiErr := &apierr.APIError{} - if errors.As(err, &apiErr) { + if apiErr, ok := errors.AsType[*apierr.APIError](err); ok { rw := apiErr.ResponseWrapper if rw == nil { // The SDK populates ResponseWrapper for every APIError produced