mirror of
https://github.com/Fishwaldo/auth2.git
synced 2025-06-03 12:21:22 +00:00
- Add comprehensive tests for pkg/log achieving 100% coverage - Add tests for basic auth provider factory and utils (98.5% coverage) - Fix missing HTTP status mapping for validation errors in internal/errors - Overall test coverage improved from 49.1% to 81.0% 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
196 lines
No EOL
5.5 KiB
Go
196 lines
No EOL
5.5 KiB
Go
package errors
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
stderrors "errors"
|
|
)
|
|
|
|
// HTTPError represents an error returned via HTTP
|
|
type HTTPError struct {
|
|
// Code is the error code
|
|
Code string `json:"code"`
|
|
|
|
// Message is the error message
|
|
Message string `json:"message"`
|
|
|
|
// Details contains additional error details
|
|
Details map[string]interface{} `json:"details,omitempty"`
|
|
|
|
// Status is the HTTP status code
|
|
Status int `json:"-"`
|
|
}
|
|
|
|
// HTTPErrorResponse is the response body for HTTP errors
|
|
type HTTPErrorResponse struct {
|
|
// Error is the error information
|
|
Error HTTPError `json:"error"`
|
|
}
|
|
|
|
// NewHTTPError creates a new HTTPError
|
|
func NewHTTPError(code ErrorCode, message string, status int) *HTTPError {
|
|
return &HTTPError{
|
|
Code: string(code),
|
|
Message: message,
|
|
Details: make(map[string]interface{}),
|
|
Status: status,
|
|
}
|
|
}
|
|
|
|
// WithDetails adds details to the HTTPError
|
|
func (e *HTTPError) WithDetails(details map[string]interface{}) *HTTPError {
|
|
// Create a new error
|
|
newError := &HTTPError{
|
|
Code: e.Code,
|
|
Message: e.Message,
|
|
Status: e.Status,
|
|
Details: make(map[string]interface{}),
|
|
}
|
|
|
|
// Copy existing details, if any
|
|
for k, v := range e.Details {
|
|
newError.Details[k] = v
|
|
}
|
|
|
|
// Add new details
|
|
for k, v := range details {
|
|
newError.Details[k] = v
|
|
}
|
|
|
|
return newError
|
|
}
|
|
|
|
// WriteResponse writes the HTTPError to the HTTP response
|
|
func (e *HTTPError) WriteResponse(w http.ResponseWriter) {
|
|
response := HTTPErrorResponse{
|
|
Error: *e,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(e.Status)
|
|
|
|
// If there's an error encoding the response, log it but don't try to write it
|
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
// In a real implementation, this would use a logger
|
|
// log.Printf("Error encoding error response: %v", err)
|
|
}
|
|
}
|
|
|
|
// ErrorToHTTP converts an Error to an HTTPError
|
|
func ErrorToHTTP(err error) *HTTPError {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// Check if we have a direct mapping for this error to a status code
|
|
status := errorToHTTPStatus(err)
|
|
|
|
// Default to internal server error for unknown errors
|
|
httpErr := &HTTPError{
|
|
Code: "internal",
|
|
Message: err.Error(),
|
|
Details: make(map[string]interface{}),
|
|
Status: status, // Use the status from errorToHTTPStatus
|
|
}
|
|
|
|
// Check if the error is an Error
|
|
var e *Error
|
|
if stderrors.As(err, &e) {
|
|
message := e.Message
|
|
if message == "" {
|
|
message = e.Error()
|
|
}
|
|
httpErr = &HTTPError{
|
|
Code: string(e.ErrorCode),
|
|
Message: message,
|
|
Details: e.Details,
|
|
Status: status,
|
|
}
|
|
}
|
|
|
|
return httpErr
|
|
}
|
|
|
|
// Common HTTP errors
|
|
var (
|
|
HTTPErrBadRequest = NewHTTPError(CodeInvalidArgument, "Bad request", http.StatusBadRequest)
|
|
HTTPErrUnauthorized = NewHTTPError(CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
|
|
HTTPErrForbidden = NewHTTPError(CodeForbidden, "Forbidden", http.StatusForbidden)
|
|
HTTPErrNotFound = NewHTTPError(CodeNotFound, "Not found", http.StatusNotFound)
|
|
HTTPErrMethodNotAllowed = NewHTTPError(CodeUnsupported, "Method not allowed", http.StatusMethodNotAllowed)
|
|
HTTPErrConflict = NewHTTPError(CodeAlreadyExists, "Conflict", http.StatusConflict)
|
|
HTTPErrTooManyRequests = NewHTTPError(CodeRateLimited, "Too many requests", http.StatusTooManyRequests)
|
|
HTTPErrInternalServerError = NewHTTPError(CodeInternal, "Internal server error", http.StatusInternalServerError)
|
|
HTTPErrServiceUnavailable = NewHTTPError(CodeUnavailable, "Service unavailable", http.StatusServiceUnavailable)
|
|
)
|
|
|
|
// errorToHTTPStatus maps errors to HTTP status codes
|
|
func errorToHTTPStatus(err error) int {
|
|
switch {
|
|
case Is(err, ErrNotFound):
|
|
return http.StatusNotFound
|
|
case Is(err, ErrAlreadyExists):
|
|
return http.StatusConflict
|
|
case Is(err, ErrInvalidArgument):
|
|
return http.StatusBadRequest
|
|
case Is(err, ErrInvalidOperation):
|
|
return http.StatusBadRequest
|
|
case Is(err, ErrUnauthenticated):
|
|
return http.StatusUnauthorized
|
|
case Is(err, ErrUnauthorized):
|
|
return http.StatusForbidden
|
|
case Is(err, ErrForbidden):
|
|
return http.StatusForbidden
|
|
case Is(err, ErrRateLimited):
|
|
return http.StatusTooManyRequests
|
|
case Is(err, ErrTimeout):
|
|
return http.StatusGatewayTimeout
|
|
case Is(err, ErrCanceled):
|
|
return http.StatusRequestTimeout
|
|
case Is(err, ErrServiceUnavailable):
|
|
return http.StatusServiceUnavailable
|
|
default:
|
|
// Check if the error has a specific code
|
|
code := GetErrorCode(err)
|
|
switch code {
|
|
case CodeNotFound:
|
|
return http.StatusNotFound
|
|
case CodeAlreadyExists:
|
|
return http.StatusConflict
|
|
case CodeInvalidArgument:
|
|
return http.StatusBadRequest
|
|
case CodeInvalidOperation:
|
|
return http.StatusBadRequest
|
|
case CodeUnauthenticated:
|
|
return http.StatusUnauthorized
|
|
case CodeUnauthorized:
|
|
return http.StatusForbidden
|
|
case CodeForbidden:
|
|
return http.StatusForbidden
|
|
case CodeRateLimited:
|
|
return http.StatusTooManyRequests
|
|
case CodeTimeout:
|
|
return http.StatusGatewayTimeout
|
|
case CodeCanceled:
|
|
return http.StatusRequestTimeout
|
|
case CodeUnavailable:
|
|
return http.StatusServiceUnavailable
|
|
case CodeValidation:
|
|
return http.StatusBadRequest
|
|
default:
|
|
return http.StatusInternalServerError
|
|
}
|
|
}
|
|
}
|
|
|
|
// WriteErrorResponse writes an error to the HTTP response
|
|
func WriteErrorResponse(w http.ResponseWriter, err error) {
|
|
httpErr := ErrorToHTTP(err)
|
|
httpErr.WriteResponse(w)
|
|
}
|
|
|
|
// WriteJSONError writes a JSON error to the HTTP response
|
|
func WriteJSONError(w http.ResponseWriter, code ErrorCode, message string, status int) {
|
|
httpErr := NewHTTPError(code, message, status)
|
|
httpErr.WriteResponse(w)
|
|
} |