124 lines
3.5 KiB
Go
124 lines
3.5 KiB
Go
// Package apierror defines OpenAI-compatible typed errors for the Veylant proxy.
|
|
// All error responses follow the OpenAI JSON format so that existing OpenAI SDK
|
|
// clients can handle them without modification.
|
|
package apierror
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
)
|
|
|
|
// APIError represents an OpenAI-compatible error response body.
|
|
// Wire format: {"error":{"type":"...","message":"...","code":"..."}}
|
|
type APIError struct {
|
|
Type string `json:"type"`
|
|
Message string `json:"message"`
|
|
Code string `json:"code"`
|
|
HTTPStatus int `json:"-"`
|
|
RetryAfterSec int `json:"-"` // when > 0, sets the Retry-After response header (RFC 6585)
|
|
}
|
|
|
|
// envelope wraps APIError in the OpenAI {"error": ...} envelope.
|
|
type envelope struct {
|
|
Error *APIError `json:"error"`
|
|
}
|
|
|
|
// Error implements the error interface.
|
|
func (e *APIError) Error() string {
|
|
return e.Message
|
|
}
|
|
|
|
// WriteError serialises e as JSON and writes it to w with the correct HTTP status.
|
|
// When e.RetryAfterSec > 0 it also sets the Retry-After header (RFC 6585).
|
|
func WriteError(w http.ResponseWriter, e *APIError) {
|
|
if e.RetryAfterSec > 0 {
|
|
w.Header().Set("Retry-After", strconv.Itoa(e.RetryAfterSec))
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(e.HTTPStatus)
|
|
_ = json.NewEncoder(w).Encode(envelope{Error: e})
|
|
}
|
|
|
|
// WriteErrorWithRequestID is like WriteError but also echoes requestID in the
|
|
// X-Request-Id response header. Use this in middleware that has access to the
|
|
// request ID but where the header may not yet have been set by the RequestID
|
|
// middleware (e.g. when the request is short-circuited before reaching it).
|
|
func WriteErrorWithRequestID(w http.ResponseWriter, e *APIError, requestID string) {
|
|
if requestID != "" {
|
|
w.Header().Set("X-Request-Id", requestID)
|
|
}
|
|
WriteError(w, e)
|
|
}
|
|
|
|
// NewAuthError returns a 401 authentication_error.
|
|
func NewAuthError(msg string) *APIError {
|
|
return &APIError{
|
|
Type: "authentication_error",
|
|
Message: msg,
|
|
Code: "invalid_api_key",
|
|
HTTPStatus: http.StatusUnauthorized,
|
|
}
|
|
}
|
|
|
|
// NewForbiddenError returns a 403 permission_error.
|
|
func NewForbiddenError(msg string) *APIError {
|
|
return &APIError{
|
|
Type: "permission_error",
|
|
Message: msg,
|
|
Code: "insufficient_permissions",
|
|
HTTPStatus: http.StatusForbidden,
|
|
}
|
|
}
|
|
|
|
// NewBadRequestError returns a 400 invalid_request_error.
|
|
func NewBadRequestError(msg string) *APIError {
|
|
return &APIError{
|
|
Type: "invalid_request_error",
|
|
Message: msg,
|
|
Code: "invalid_request",
|
|
HTTPStatus: http.StatusBadRequest,
|
|
}
|
|
}
|
|
|
|
// NewUpstreamError returns a 502 upstream_error.
|
|
func NewUpstreamError(msg string) *APIError {
|
|
return &APIError{
|
|
Type: "api_error",
|
|
Message: msg,
|
|
Code: "upstream_error",
|
|
HTTPStatus: http.StatusBadGateway,
|
|
}
|
|
}
|
|
|
|
// NewRateLimitError returns a 429 rate_limit_error with Retry-After: 1 (RFC 6585).
|
|
func NewRateLimitError(msg string) *APIError {
|
|
return &APIError{
|
|
Type: "rate_limit_error",
|
|
Message: msg,
|
|
Code: "rate_limit_exceeded",
|
|
HTTPStatus: http.StatusTooManyRequests,
|
|
RetryAfterSec: 1,
|
|
}
|
|
}
|
|
|
|
// NewTimeoutError returns a 504 timeout_error.
|
|
func NewTimeoutError(msg string) *APIError {
|
|
return &APIError{
|
|
Type: "api_error",
|
|
Message: msg,
|
|
Code: "upstream_timeout",
|
|
HTTPStatus: http.StatusGatewayTimeout,
|
|
}
|
|
}
|
|
|
|
// NewInternalError returns a 500 internal_error.
|
|
func NewInternalError(msg string) *APIError {
|
|
return &APIError{
|
|
Type: "api_error",
|
|
Message: msg,
|
|
Code: "internal_error",
|
|
HTTPStatus: http.StatusInternalServerError,
|
|
}
|
|
}
|