Files
WeKnora/internal/errors/errors.go
T
wizardchen 7d030a6f6a feat(tenant): implement tenant creation limit and error handling
Added a new configuration option to limit the number of tenants a non-superuser can create via self-service. Introduced a new error type for handling cases where users exceed this limit, returning a 429 status code. Updated the tenant creation and update handlers to enforce this limit and provide appropriate feedback to users. Additionally, refactored the tenant update request structure to ensure only mutable fields are allowed.

Refs: #1303
2026-05-18 17:28:58 +08:00

256 lines
6.9 KiB
Go

package errors
import (
"fmt"
"net/http"
)
// ErrorCode defines the error code type
type ErrorCode int
// System error codes
const (
// Common error codes (1000-1999)
ErrBadRequest ErrorCode = 1000
ErrUnauthorized ErrorCode = 1001
ErrForbidden ErrorCode = 1002
ErrNotFound ErrorCode = 1003
ErrMethodNotAllowed ErrorCode = 1004
ErrConflict ErrorCode = 1005
ErrTooManyRequests ErrorCode = 1006
ErrInternalServer ErrorCode = 1007
ErrServiceUnavailable ErrorCode = 1008
ErrTimeout ErrorCode = 1009
ErrValidation ErrorCode = 1010
// Tenant related error codes (2000-2099)
ErrTenantNotFound ErrorCode = 2000
ErrTenantAlreadyExists ErrorCode = 2001
ErrTenantInactive ErrorCode = 2002
ErrTenantNameRequired ErrorCode = 2003
ErrTenantInvalidStatus ErrorCode = 2004
// Agent related error codes (2100-2199)
ErrAgentMissingThinkingModel ErrorCode = 2100
ErrAgentMissingAllowedTools ErrorCode = 2101
ErrAgentInvalidMaxIterations ErrorCode = 2102
ErrAgentInvalidTemperature ErrorCode = 2103
// VectorStore binding related error codes (2200-2299).
// Both map to HTTP 400 with a generic message; the typed code lets
// clients distinguish "wrong UUID / cross-tenant" from "store exists
// but is currently unavailable" without parsing the message text.
ErrVectorStoreBindingInvalid ErrorCode = 2200
ErrVectorStoreUnavailable ErrorCode = 2201
// Add more error codes here
)
// AppError defines the application error structure
type AppError struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Details any `json:"details,omitempty"`
HTTPCode int `json:"-"`
}
// Error implements the error interface
func (e *AppError) Error() string {
return fmt.Sprintf("error code: %d, error message: %s", e.Code, e.Message)
}
// WithDetails adds error details
func (e *AppError) WithDetails(details any) *AppError {
e.Details = details
return e
}
// NewBadRequestError creates a bad request error
func NewBadRequestError(message string) *AppError {
return &AppError{
Code: ErrBadRequest,
Message: message,
HTTPCode: http.StatusBadRequest,
}
}
// NewUnauthorizedError creates an unauthorized error
func NewUnauthorizedError(message string) *AppError {
return &AppError{
Code: ErrUnauthorized,
Message: message,
HTTPCode: http.StatusUnauthorized,
}
}
// NewForbiddenError creates a forbidden error
func NewForbiddenError(message string) *AppError {
return &AppError{
Code: ErrForbidden,
Message: message,
HTTPCode: http.StatusForbidden,
}
}
// NewNotFoundError creates a not found error
func NewNotFoundError(message string) *AppError {
return &AppError{
Code: ErrNotFound,
Message: message,
HTTPCode: http.StatusNotFound,
}
}
// NewConflictError creates a conflict error
func NewConflictError(message string) *AppError {
return &AppError{
Code: ErrConflict,
Message: message,
HTTPCode: http.StatusConflict,
}
}
// NewTooManyRequestsError creates a 429 error, used by quota-style
// guards (e.g. per-user self-service tenant creation cap).
func NewTooManyRequestsError(message string) *AppError {
if message == "" {
message = "too many requests"
}
return &AppError{
Code: ErrTooManyRequests,
Message: message,
HTTPCode: http.StatusTooManyRequests,
}
}
// NewInternalServerError creates an internal server error
func NewInternalServerError(message string) *AppError {
if message == "" {
message = "服务器内部错误"
}
return &AppError{
Code: ErrInternalServer,
Message: message,
HTTPCode: http.StatusInternalServerError,
}
}
// NewServiceUnavailableError creates a service unavailable (503)
// error. Used for transient failures where the caller can retry.
func NewServiceUnavailableError(message string) *AppError {
if message == "" {
message = "服务暂时不可用"
}
return &AppError{
Code: ErrServiceUnavailable,
Message: message,
HTTPCode: http.StatusServiceUnavailable,
}
}
// NewValidationError creates a validation error
func NewValidationError(message string) *AppError {
return &AppError{
Code: ErrValidation,
Message: message,
HTTPCode: http.StatusBadRequest,
}
}
// Tenant related errors
func NewTenantNotFoundError() *AppError {
return &AppError{
Code: ErrTenantNotFound,
Message: "租户不存在",
HTTPCode: http.StatusNotFound,
}
}
// NewTenantAlreadyExistsError creates a tenant already exists error
func NewTenantAlreadyExistsError() *AppError {
return &AppError{
Code: ErrTenantAlreadyExists,
Message: "租户已存在",
HTTPCode: http.StatusConflict,
}
}
// NewTenantInactiveError creates a tenant inactive error
func NewTenantInactiveError() *AppError {
return &AppError{
Code: ErrTenantInactive,
Message: "租户已停用",
HTTPCode: http.StatusForbidden,
}
}
// Agent related errors
func NewAgentMissingThinkingModelError() *AppError {
return &AppError{
Code: ErrAgentMissingThinkingModel,
Message: "启用Agent模式前,请先选择思考模型",
HTTPCode: http.StatusBadRequest,
}
}
func NewAgentMissingAllowedToolsError() *AppError {
return &AppError{
Code: ErrAgentMissingAllowedTools,
Message: "至少需要选择一个允许的工具",
HTTPCode: http.StatusBadRequest,
}
}
func NewAgentInvalidMaxIterationsError() *AppError {
return &AppError{
Code: ErrAgentInvalidMaxIterations,
Message: "最大迭代次数必须在1-20之间",
HTTPCode: http.StatusBadRequest,
}
}
func NewAgentInvalidTemperatureError() *AppError {
return &AppError{
Code: ErrAgentInvalidTemperature,
Message: "温度参数必须在0-2之间",
HTTPCode: http.StatusBadRequest,
}
}
// NewVectorStoreBindingInvalidError signals that a knowledge base create
// request referenced a vector store that does not exist under the caller's
// tenant (or carried a malformed UUID). The user-facing message is
// intentionally generic to avoid enumeration oracles — see the structured
// log at the call site for the tenant/store pair.
func NewVectorStoreBindingInvalidError(message string) *AppError {
if message == "" {
message = "vector store not found"
}
return &AppError{
Code: ErrVectorStoreBindingInvalid,
Message: message,
HTTPCode: http.StatusBadRequest,
}
}
// NewVectorStoreUnavailableError signals that the requested vector store
// row exists in the database but is not currently wired into the in-memory
// engine registry (factory failure on CreateStore, dynamic config error,
// or stale state after a connection-config rotation).
func NewVectorStoreUnavailableError(message string) *AppError {
if message == "" {
message = "vector store is currently unavailable"
}
return &AppError{
Code: ErrVectorStoreUnavailable,
Message: message,
HTTPCode: http.StatusBadRequest,
}
}
// IsAppError checks if the error is an AppError type
func IsAppError(err error) (*AppError, bool) {
appErr, ok := err.(*AppError)
return appErr, ok
}