mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-11-01 04:38:03 +08:00
Wires KnowledgeBase.VectorStoreID and the ownership-aware retrieve factory into the user-facing knowledge-base lifecycle: - POST /knowledge-bases validates the requested vector_store_id against the caller's tenant scope and the engine registry. New error codes ErrVectorStoreBindingInvalid (2200) and ErrVectorStoreUnavailable (2201) distinguish the typed branches without echoing UUIDs to the client. - GET / POST / PUT / PUT-pin responses embed the bound store's display metadata (name, source, engine_type, status) without exposing any connection credentials. Cross-tenant shared KBs receive a suppressed payload (vector_store_id stripped, source="shared") so operator-chosen store names cannot be enumerated across tenants. - POST /knowledge-bases/copy synchronously rejects clones whose target has a different embedding model or vector store, before the async clone task is enqueued. The async clone worker re-applies the same checks for defense in depth. - DELETE /vector-stores/:id refuses to remove a store with bound KBs, inside a transaction that row-locks the store on PostgreSQL and serializes via WAL on SQLite. unregister-from-registry is wrapped in defer/recover so a panic surfaces as a structured warning instead of silently leaking a stale engine. - vector_store_id is immutable after creation. The GORM <-:create tag blocks every ORM update path; the service-layer DTO omits the field entirely; a reflection-based regression test catches any future maintainer who adds it back to either layer. - Empty-string vector_store_id is normalized to nil at both the create path and inside SharesStoreWith, so rows persisted by callers that did not run Normalize first cannot trip false same-store comparisons. Part of #993. Depends on #994 and #1310.
230 lines
6.2 KiB
Go
230 lines
6.2 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,
|
|
}
|
|
}
|
|
|
|
// NewInternalServerError creates an internal server error
|
|
func NewInternalServerError(message string) *AppError {
|
|
if message == "" {
|
|
message = "服务器内部错误"
|
|
}
|
|
return &AppError{
|
|
Code: ErrInternalServer,
|
|
Message: message,
|
|
HTTPCode: http.StatusInternalServerError,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|