// 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, } }