package usecase import ( "errors" "fmt" ) type ErrorKind string const ( ErrorKindInvalid ErrorKind = "INVALID" ErrorKindNotFound ErrorKind = "NOT_FOUND" ErrorKindConflict ErrorKind = "CONFLICT" ErrorKindUnavailable ErrorKind = "UNAVAILABLE" ErrorKindInternal ErrorKind = "INTERNAL" ) type Error struct { Kind ErrorKind Code string Message string Retryable bool Fields map[string]string Cause error } func (e *Error) Error() string { if e.Message != "" { return e.Message } return "use case failed" } func (e *Error) Unwrap() error { return e.Cause } func newError( kind ErrorKind, code string, message string, cause error, ) *Error { return &Error{ Kind: kind, Code: code, Message: message, Cause: cause, Fields: map[string]string{}, } } func invalidError(code, message string, fields map[string]string) *Error { return &Error{ Kind: ErrorKindInvalid, Code: code, Message: message, Fields: fields, } } var ( ErrRepositoryNotFound = errors.New("repository resource not found") ErrIdempotencyConflict = errors.New("idempotency key payload conflict") ErrSourceReferenceConflict = errors.New("source reference conflict") ErrAssetUnavailable = errors.New("asset is unavailable") ErrTaskStateConflict = errors.New("task state conflict") ErrRepositoryUnavailable = errors.New("repository unavailable") ErrRepositoryInvariant = errors.New("repository invariant failed") ) func wrapRepositoryError(err error) error { switch { case errors.Is(err, ErrRepositoryNotFound): return newError( ErrorKindNotFound, "RESOURCE_NOT_FOUND", "resource not found", err, ) case errors.Is(err, ErrIdempotencyConflict): return newError( ErrorKindConflict, "IDEMPOTENCY_KEY_CONFLICT", "idempotency key was already used for a different request", err, ) case errors.Is(err, ErrSourceReferenceConflict): return newError( ErrorKindConflict, "TASK_SOURCE_REF_CONFLICT", "source reference already exists", err, ) case errors.Is(err, ErrAssetUnavailable): return newError( ErrorKindConflict, "TASK_ASSET_UNAVAILABLE", "reference image is not available for this task", err, ) case errors.Is(err, ErrTaskStateConflict): return newError( ErrorKindConflict, "TASK_STATE_CONFLICT", "task state does not allow this operation", err, ) case errors.Is(err, ErrRepositoryUnavailable): result := newError( ErrorKindUnavailable, "STORAGE_UNAVAILABLE", "storage is temporarily unavailable", err, ) result.Retryable = true return result default: return newError( ErrorKindInternal, "INTERNAL_ERROR", "internal server error", fmt.Errorf("%w: %v", ErrRepositoryInvariant, err), ) } }