Implement error types and sentinel errors

Add errors.go with:
- APIError struct with StatusCode, Message, RequestID, and Err fields
- Error() method for human-readable error messages
- Unwrap() method for errors.Is() and errors.As() compatibility
- Sentinel errors: ErrUnauthorized, ErrNotFound, ErrRateLimited,
  ErrBadRequest, ErrServerError
- NewAPIError helper function to create APIError from HTTP response
  with automatic status code mapping to sentinel errors

Closes checkvist-api-mnh
This commit is contained in:
Oliver Jakoubek 2026-01-14 13:23:48 +01:00
commit 832364a06f
2 changed files with 79 additions and 1 deletions

View file

@ -1,3 +1,81 @@
package checkvist
import (
"errors"
"fmt"
"net/http"
)
// errors.go contains the APIError type and sentinel errors for common API error conditions.
// Sentinel errors for common API error conditions.
// Use errors.Is() to check for these errors.
var (
// ErrUnauthorized is returned when authentication fails (HTTP 401).
ErrUnauthorized = errors.New("unauthorized: invalid credentials or expired token")
// ErrNotFound is returned when a resource is not found (HTTP 404).
ErrNotFound = errors.New("not found: the requested resource does not exist")
// ErrRateLimited is returned when the API rate limit is exceeded (HTTP 429).
ErrRateLimited = errors.New("rate limited: too many requests")
// ErrBadRequest is returned for invalid request parameters (HTTP 400).
ErrBadRequest = errors.New("bad request: invalid parameters")
// ErrServerError is returned for server-side errors (HTTP 5xx).
ErrServerError = errors.New("server error: the server encountered an error")
)
// APIError represents an error returned by the Checkvist API.
type APIError struct {
// StatusCode is the HTTP status code returned by the API.
StatusCode int
// Message is a human-readable error message.
Message string
// RequestID is the unique identifier for the request, if available.
RequestID string
// Err is the underlying sentinel error, if applicable.
Err error
}
// Error implements the error interface.
func (e *APIError) Error() string {
if e.RequestID != "" {
return fmt.Sprintf("checkvist API error (status %d, request %s): %s", e.StatusCode, e.RequestID, e.Message)
}
return fmt.Sprintf("checkvist API error (status %d): %s", e.StatusCode, e.Message)
}
// Unwrap returns the underlying error for use with errors.Is() and errors.As().
func (e *APIError) Unwrap() error {
return e.Err
}
// NewAPIError creates an APIError from an HTTP response.
// It automatically maps the status code to the appropriate sentinel error.
func NewAPIError(resp *http.Response, message string) *APIError {
if message == "" {
message = http.StatusText(resp.StatusCode)
}
apiErr := &APIError{
StatusCode: resp.StatusCode,
Message: message,
RequestID: resp.Header.Get("X-Request-Id"),
}
// Map status codes to sentinel errors
switch resp.StatusCode {
case http.StatusUnauthorized:
apiErr.Err = ErrUnauthorized
case http.StatusNotFound:
apiErr.Err = ErrNotFound
case http.StatusTooManyRequests:
apiErr.Err = ErrRateLimited
case http.StatusBadRequest:
apiErr.Err = ErrBadRequest
default:
if resp.StatusCode >= 500 {
apiErr.Err = ErrServerError
}
}
return apiErr
}