-
Notifications
You must be signed in to change notification settings - Fork 26
feat: add structured error handling for JSON/YAML output #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
CFSNM
merged 1 commit into
opendatahub-io:main
from
ShettyGaurav:feat/structured-error-handling
Apr 13, 2026
+904
−6
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| package errors | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "io/fs" | ||
| "net" | ||
|
|
||
| apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
| ) | ||
|
|
||
| const ( | ||
|
ShettyGaurav marked this conversation as resolved.
|
||
| suggestionAuthentication = "Refresh your kubeconfig credentials with 'oc login' or 'kubectl config'" | ||
| suggestionAuthorization = "Verify your RBAC permissions for the required resources" | ||
| suggestionConnection = "Check if the API server is reachable" | ||
| suggestionNotFound = "Verify the resource exists in the cluster" | ||
| suggestionAlreadyExists = "Resource already exists, use update or delete first" | ||
| suggestionConflict = "Retry the operation (resource was modified concurrently)" | ||
| suggestionValidation = "Check the request parameters and resource spec" | ||
| suggestionGone = "Resource version expired, retry with a fresh list/watch" | ||
| suggestionServer = "API server error, retry later" | ||
| suggestionTimeout = "Increase --timeout value or retry the operation" | ||
| suggestionRateLimited = "Too many requests, retry after a brief wait" | ||
| suggestionRequestTooLarge = "Reduce the size of the request payload" | ||
| suggestionInternal = "Unexpected error, please report a bug" | ||
| suggestionCanceled = "Operation was canceled" | ||
| suggestionFilePath = "Verify the file path exists and is readable (e.g. --kubeconfig)" | ||
| suggestionConfig = "Check your kubeconfig: verify the --context, --cluster, and --kubeconfig flags are correct" | ||
| ) | ||
|
|
||
| // apiErrorEntry maps an apierrors check function to its structured error fields. | ||
| type apiErrorEntry struct { | ||
| check func(error) bool | ||
| code string | ||
| category ErrorCategory | ||
| retriable bool | ||
| suggestion string | ||
| } | ||
|
|
||
| // apiErrorTable defines the classification for every Kubernetes API error type. | ||
| // Order matters: more specific checks (e.g. IsUnexpectedServerError) must | ||
| // appear before broader ones (e.g. IsInternalError) that match the same status code. | ||
| // | ||
| //nolint:gochecknoglobals // package-level lookup table is intentional | ||
| var apiErrorTable = []apiErrorEntry{ | ||
| {apierrors.IsUnauthorized, "AUTH_FAILED", CategoryAuthentication, false, suggestionAuthentication}, | ||
| {apierrors.IsForbidden, "AUTHZ_DENIED", CategoryAuthorization, false, suggestionAuthorization}, | ||
| {apierrors.IsNotFound, "NOT_FOUND", CategoryNotFound, false, suggestionNotFound}, | ||
| {apierrors.IsAlreadyExists, "ALREADY_EXISTS", CategoryConflict, false, suggestionAlreadyExists}, | ||
| {apierrors.IsConflict, "CONFLICT", CategoryConflict, true, suggestionConflict}, | ||
| {apierrors.IsInvalid, "INVALID", CategoryValidation, false, suggestionValidation}, | ||
| {apierrors.IsBadRequest, "BAD_REQUEST", CategoryValidation, false, suggestionValidation}, | ||
| {apierrors.IsMethodNotSupported, "METHOD_NOT_SUPPORTED", CategoryValidation, false, suggestionValidation}, | ||
| {apierrors.IsNotAcceptable, "NOT_ACCEPTABLE", CategoryValidation, false, suggestionValidation}, | ||
| {apierrors.IsUnsupportedMediaType, "UNSUPPORTED_MEDIA_TYPE", CategoryValidation, false, suggestionValidation}, | ||
| {apierrors.IsRequestEntityTooLargeError, "REQUEST_TOO_LARGE", CategoryValidation, false, suggestionRequestTooLarge}, | ||
| {apierrors.IsGone, "GONE", CategoryServer, true, suggestionGone}, | ||
| {apierrors.IsResourceExpired, "RESOURCE_EXPIRED", CategoryServer, true, suggestionGone}, | ||
| {apierrors.IsServerTimeout, "SERVER_TIMEOUT", CategoryTimeout, true, suggestionTimeout}, | ||
| {apierrors.IsServiceUnavailable, "SERVER_UNAVAILABLE", CategoryServer, true, suggestionServer}, | ||
| {apierrors.IsUnexpectedServerError, "UNEXPECTED_SERVER_ERROR", CategoryServer, true, suggestionServer}, | ||
| {apierrors.IsInternalError, "SERVER_ERROR", CategoryServer, true, suggestionServer}, | ||
| {apierrors.IsTimeout, "GATEWAY_TIMEOUT", CategoryTimeout, true, suggestionTimeout}, | ||
| {apierrors.IsTooManyRequests, "RATE_LIMITED", CategoryServer, true, suggestionRateLimited}, | ||
| {apierrors.IsUnexpectedObjectError, "UNEXPECTED_OBJECT", CategoryServer, false, suggestionServer}, | ||
| {apierrors.IsStoreReadError, "STORE_READ_ERROR", CategoryServer, true, suggestionServer}, | ||
| } | ||
|
|
||
| // Classify inspects an error and returns a StructuredError with the | ||
| // appropriate category, error code, retriable flag, and suggestion. | ||
| func Classify(err error) *StructuredError { | ||
| if err == nil { | ||
| return nil | ||
| } | ||
|
|
||
| var structuredErr *StructuredError | ||
| if errors.As(err, &structuredErr) && structuredErr != nil { | ||
| return structuredErr | ||
| } | ||
|
|
||
| for _, entry := range apiErrorTable { | ||
| if entry.check(err) { | ||
| return &StructuredError{ | ||
| Code: entry.code, | ||
| Message: err.Error(), | ||
| Category: entry.category, | ||
| Retriable: entry.retriable, | ||
| Suggestion: entry.suggestion, | ||
| cause: err, | ||
| } | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| switch { | ||
| case errors.Is(err, context.DeadlineExceeded): | ||
| return &StructuredError{ | ||
| Code: "TIMEOUT", | ||
| Message: err.Error(), | ||
| Category: CategoryTimeout, | ||
| Retriable: true, | ||
| Suggestion: suggestionTimeout, | ||
| cause: err, | ||
| } | ||
|
|
||
| case errors.Is(err, context.Canceled): | ||
| return &StructuredError{ | ||
| Code: "CANCELED", | ||
| Message: err.Error(), | ||
| Category: CategoryInternal, | ||
| Retriable: false, | ||
| Suggestion: suggestionCanceled, | ||
| cause: err, | ||
| } | ||
|
|
||
| case isFilesystemError(err): | ||
| return &StructuredError{ | ||
| Code: "CONFIG_INVALID", | ||
| Message: err.Error(), | ||
| Category: CategoryValidation, | ||
| Retriable: false, | ||
| Suggestion: suggestionFilePath, | ||
| cause: err, | ||
| } | ||
|
|
||
| case isConfigError(err): | ||
| return &StructuredError{ | ||
| Code: "CONFIG_INVALID", | ||
| Message: err.Error(), | ||
| Category: CategoryValidation, | ||
| Retriable: false, | ||
| Suggestion: suggestionConfig, | ||
| cause: err, | ||
| } | ||
|
|
||
| case isNetworkTimeout(err): | ||
| return &StructuredError{ | ||
| Code: "NET_TIMEOUT", | ||
| Message: err.Error(), | ||
| Category: CategoryTimeout, | ||
| Retriable: true, | ||
| Suggestion: suggestionTimeout, | ||
| cause: err, | ||
| } | ||
|
|
||
| case isNetworkError(err): | ||
| return &StructuredError{ | ||
| Code: "CONN_FAILED", | ||
| Message: err.Error(), | ||
| Category: CategoryConnection, | ||
| Retriable: true, | ||
| Suggestion: suggestionConnection, | ||
| cause: err, | ||
| } | ||
|
|
||
| default: | ||
| return &StructuredError{ | ||
| Code: "INTERNAL", | ||
| Message: err.Error(), | ||
| Category: CategoryInternal, | ||
| Retriable: false, | ||
| Suggestion: suggestionInternal, | ||
| cause: err, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func isFilesystemError(err error) bool { | ||
| var pathErr *fs.PathError | ||
|
|
||
| return errors.As(err, &pathErr) | ||
| } | ||
|
|
||
| func isConfigError(err error) bool { | ||
| var cfgErr *ConfigError | ||
|
|
||
| return errors.As(err, &cfgErr) | ||
| } | ||
|
|
||
| func isNetworkError(err error) bool { | ||
| var netErr net.Error | ||
|
|
||
| return errors.As(err, &netErr) | ||
| } | ||
|
|
||
| func isNetworkTimeout(err error) bool { | ||
| var netErr net.Error | ||
| if errors.As(err, &netErr) { | ||
| return netErr.Timeout() | ||
| } | ||
|
|
||
| return false | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.