a18f96912d
interfaces - refactor: split UserRepository into AuthUserRepository and AdminUserRepository capabilities - refactor: split SessionRepository into AuthSessionRepository and repository-owned CLI/admin methods - refactor: replace generic Repository Update/Delete with operation-specific params and ownership predicates - refactor: replace CredentialRepository generic CRUD with passkey-specific methods - refactor: replace FileRepository generic Create/Update/Delete with UploadedFileParams, DirectoryParams, and owned soft-delete - refactor: remove repository fields from WebApp struct; repositories are now composition-time wiring only - feat: add domain error kinds ErrParentNotFound, ErrParentNotDir, ErrDirectoryNotEmpty, ErrInvalidMove - feat: add CredentialTypeAppPasskey constant - feat: add testutil.SetUserAdmin for test fixture setup that bypasses production service ports - test: add architecture test banning GORM Save in repository package - test: add capability interface contract tests ensuring each service receives the minimal interface - test: add blockingStorage helper for concurrent promotion tests - test: add preserved DSN parameter test for sqliteImmediateDSN - docs: update architecture decisions with repository write rules and capability separation - docs: update roadmap to clarify atomic single-use refresh sessions - docs: add -race test target to development docs
94 lines
3.2 KiB
Go
94 lines
3.2 KiB
Go
package model
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
var (
|
|
ErrNotFound = errors.New("resource not found")
|
|
ErrDuplicate = errors.New("resource already exists")
|
|
ErrUnauthorized = errors.New("unauthorized")
|
|
ErrForbidden = errors.New("forbidden")
|
|
ErrUploadTooLarge = errors.New("upload exceeds maximum size")
|
|
ErrParentNotFound = errors.New("parent resource not found")
|
|
ErrParentNotDir = errors.New("parent resource is not a directory")
|
|
ErrDirectoryNotEmpty = errors.New("directory is not empty")
|
|
ErrInvalidMove = errors.New("invalid file move")
|
|
)
|
|
|
|
// ErrorKind classifies domain errors without tying them to a transport.
|
|
type ErrorKind string
|
|
|
|
// Protocol-neutral error kinds.
|
|
const (
|
|
KindInvalidArgument ErrorKind = "invalid_argument"
|
|
KindUnauthenticated ErrorKind = "unauthenticated"
|
|
KindPermissionDenied ErrorKind = "permission_denied"
|
|
KindNotFound ErrorKind = "not_found"
|
|
KindConflict ErrorKind = "conflict"
|
|
KindPayloadTooLarge ErrorKind = "payload_too_large"
|
|
KindInternal ErrorKind = "internal"
|
|
)
|
|
|
|
// AppError is a service-layer error that carries a protocol-neutral kind, a
|
|
// user-safe message, and an optional internal cause for logging.
|
|
type AppError struct {
|
|
Kind ErrorKind
|
|
Message string
|
|
Err error
|
|
}
|
|
|
|
func (e *AppError) Error() string { return e.Message }
|
|
func (e *AppError) Unwrap() error { return e.Err }
|
|
|
|
// NewInvalidArgumentError creates an invalid-argument AppError.
|
|
func NewInvalidArgumentError(message string) *AppError {
|
|
return &AppError{Kind: KindInvalidArgument, Message: message}
|
|
}
|
|
|
|
// NewBadRequestError creates an invalid-argument AppError.
|
|
func NewBadRequestError(message string) *AppError {
|
|
return NewInvalidArgumentError(message)
|
|
}
|
|
|
|
// NewUnauthenticatedError creates an unauthenticated AppError.
|
|
func NewUnauthenticatedError(message string) *AppError {
|
|
return &AppError{Kind: KindUnauthenticated, Message: message}
|
|
}
|
|
|
|
// NewConflictError creates a conflict AppError.
|
|
func NewConflictError(message string) *AppError {
|
|
return &AppError{Kind: KindConflict, Message: message}
|
|
}
|
|
|
|
// NewNotFoundError creates a not-found AppError.
|
|
func NewNotFoundError(message string, cause error) *AppError {
|
|
return &AppError{Kind: KindNotFound, Message: message, Err: cause}
|
|
}
|
|
|
|
// NewPermissionDeniedError creates a permission-denied AppError.
|
|
func NewPermissionDeniedError(message string, cause error) *AppError {
|
|
return &AppError{Kind: KindPermissionDenied, Message: message, Err: cause}
|
|
}
|
|
|
|
// NewForbiddenError creates a permission-denied AppError.
|
|
func NewForbiddenError(message string, cause error) *AppError {
|
|
return NewPermissionDeniedError(message, cause)
|
|
}
|
|
|
|
// NewPayloadTooLargeError creates a payload-too-large AppError.
|
|
func NewPayloadTooLargeError(message string, cause error) *AppError {
|
|
return &AppError{Kind: KindPayloadTooLarge, Message: message, Err: cause}
|
|
}
|
|
|
|
// NewInternalError creates an internal AppError with a wrapped internal cause.
|
|
// msg describes the operation that failed; cause is the underlying error.
|
|
func NewInternalError(msg string, cause error) *AppError {
|
|
return &AppError{
|
|
Kind: KindInternal,
|
|
Message: "internal server error",
|
|
Err: fmt.Errorf("%s: %w", msg, cause),
|
|
}
|
|
}
|