mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-06-05 22:23:50 +00:00
290 lines
9.0 KiB
Go
290 lines
9.0 KiB
Go
// Package errors provides structured error handling with error codes and context.
|
|
package errors
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
// ErrorCode represents a specific error condition.
|
|
type ErrorCode int
|
|
|
|
// Error codes organized by category
|
|
const (
|
|
// Search errors (1000-1099)
|
|
ErrRipgrepNotFound ErrorCode = 1001
|
|
ErrRipgrepTimeout ErrorCode = 1002
|
|
ErrInvalidPattern ErrorCode = 1003
|
|
ErrSearchFailed ErrorCode = 1004
|
|
ErrNoResults ErrorCode = 1005
|
|
|
|
// Parser errors (1100-1199)
|
|
ErrParserNotFound ErrorCode = 1101
|
|
ErrParseFailed ErrorCode = 1102
|
|
ErrInvalidLanguage ErrorCode = 1103
|
|
ErrFileTooBig ErrorCode = 1104
|
|
ErrInvalidSyntax ErrorCode = 1105
|
|
|
|
// LSP errors (1200-1299)
|
|
ErrLSPServerNotFound ErrorCode = 1201
|
|
ErrLSPInitFailed ErrorCode = 1202
|
|
ErrLSPTimeout ErrorCode = 1203
|
|
ErrLSPCommunication ErrorCode = 1204
|
|
ErrNoHoverInfo ErrorCode = 1205
|
|
ErrNoDefinition ErrorCode = 1206
|
|
ErrNoReferences ErrorCode = 1207
|
|
|
|
// Edit errors (1300-1399)
|
|
ErrEditFailed ErrorCode = 1301
|
|
ErrInvalidEdit ErrorCode = 1302
|
|
ErrFileNotFound ErrorCode = 1303
|
|
ErrFileNotReadable ErrorCode = 1304
|
|
ErrFileNotWritable ErrorCode = 1305
|
|
ErrNodeNotFound ErrorCode = 1306
|
|
ErrValidationFailed ErrorCode = 1307
|
|
ErrInvalidSelection ErrorCode = 1308
|
|
|
|
// Query errors (1400-1499)
|
|
ErrInvalidQuery ErrorCode = 1401
|
|
ErrQueryTimeout ErrorCode = 1402
|
|
ErrNoMatches ErrorCode = 1403
|
|
ErrQueryCompile ErrorCode = 1404
|
|
|
|
// Config errors (1500-1599)
|
|
ErrInvalidConfig ErrorCode = 1501
|
|
ErrPathNotAllowed ErrorCode = 1502
|
|
ErrWorkspaceNotSet ErrorCode = 1503
|
|
|
|
// Internal errors (1900-1999)
|
|
ErrInternal ErrorCode = 1900
|
|
ErrCacheFailed ErrorCode = 1901
|
|
ErrConcurrency ErrorCode = 1902
|
|
)
|
|
|
|
// StructuredError represents an error with rich context and remediation info.
|
|
type StructuredError struct {
|
|
Cause error
|
|
Context map[string]any
|
|
Message string
|
|
Remediation string
|
|
Stack string
|
|
Code ErrorCode
|
|
}
|
|
|
|
// Error implements the error interface.
|
|
func (e *StructuredError) Error() string {
|
|
var sb strings.Builder
|
|
|
|
// Error code and message
|
|
sb.WriteString(fmt.Sprintf("[%d] %s", e.Code, e.Message))
|
|
|
|
// Context if available
|
|
if len(e.Context) > 0 {
|
|
sb.WriteString("\nContext:")
|
|
for k, v := range e.Context {
|
|
sb.WriteString(fmt.Sprintf("\n %s: %v", k, v))
|
|
}
|
|
}
|
|
|
|
// Remediation if available
|
|
if e.Remediation != "" {
|
|
sb.WriteString(fmt.Sprintf("\nHow to fix: %s", e.Remediation))
|
|
}
|
|
|
|
// Underlying cause if available
|
|
if e.Cause != nil {
|
|
sb.WriteString(fmt.Sprintf("\nCaused by: %v", e.Cause))
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// Unwrap returns the underlying cause for error chain support.
|
|
func (e *StructuredError) Unwrap() error {
|
|
return e.Cause
|
|
}
|
|
|
|
// WithContext adds context to the error.
|
|
func (e *StructuredError) WithContext(key string, value any) *StructuredError {
|
|
if e.Context == nil {
|
|
e.Context = make(map[string]any)
|
|
}
|
|
e.Context[key] = value
|
|
return e
|
|
}
|
|
|
|
// WithRemediation sets the remediation message.
|
|
func (e *StructuredError) WithRemediation(msg string) *StructuredError {
|
|
e.Remediation = msg
|
|
return e
|
|
}
|
|
|
|
// New creates a new structured error with stack trace.
|
|
func New(code ErrorCode, message string) *StructuredError {
|
|
return &StructuredError{
|
|
Code: code,
|
|
Message: message,
|
|
Context: make(map[string]interface{}),
|
|
Stack: captureStack(2),
|
|
}
|
|
}
|
|
|
|
// Wrap wraps an existing error with structured error information.
|
|
func Wrap(code ErrorCode, message string, cause error) *StructuredError {
|
|
return &StructuredError{
|
|
Code: code,
|
|
Message: message,
|
|
Context: make(map[string]interface{}),
|
|
Cause: cause,
|
|
Stack: captureStack(2),
|
|
}
|
|
}
|
|
|
|
// Is checks if an error matches the given error code.
|
|
func Is(err error, code ErrorCode) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
if se, ok := err.(*StructuredError); ok {
|
|
return se.Code == code
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetCode extracts the error code from an error, or returns 0 if not a structured error.
|
|
func GetCode(err error) ErrorCode {
|
|
if err == nil {
|
|
return 0
|
|
}
|
|
|
|
if se, ok := err.(*StructuredError); ok {
|
|
return se.Code
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// captureStack captures the stack trace.
|
|
func captureStack(skip int) string {
|
|
const depth = 16
|
|
var pcs [depth]uintptr
|
|
n := runtime.Callers(skip+1, pcs[:])
|
|
|
|
var sb strings.Builder
|
|
frames := runtime.CallersFrames(pcs[:n])
|
|
|
|
for {
|
|
frame, more := frames.Next()
|
|
if !strings.Contains(frame.File, "runtime/") {
|
|
sb.WriteString(fmt.Sprintf("\n %s:%d %s", frame.File, frame.Line, frame.Function))
|
|
}
|
|
if !more {
|
|
break
|
|
}
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// Common error constructors for convenience
|
|
|
|
// NewRipgrepNotFound creates an error for missing ripgrep binary.
|
|
func NewRipgrepNotFound() *StructuredError {
|
|
os := runtime.GOOS
|
|
install := "brew install ripgrep"
|
|
|
|
switch os {
|
|
case "linux":
|
|
install = "apt-get install ripgrep (Debian/Ubuntu) or yum install ripgrep (RHEL/CentOS)"
|
|
case "windows":
|
|
install = "choco install ripgrep or scoop install ripgrep"
|
|
}
|
|
|
|
return New(ErrRipgrepNotFound, "ripgrep (rg) binary not found in system PATH").
|
|
WithContext("os", os).
|
|
WithRemediation(fmt.Sprintf("Install ripgrep: %s", install))
|
|
}
|
|
|
|
// NewLSPServerNotFound creates an error for missing LSP server.
|
|
func NewLSPServerNotFound(language, serverName string) *StructuredError {
|
|
return New(ErrLSPServerNotFound, fmt.Sprintf("LSP server '%s' not found for language %s", serverName, language)).
|
|
WithContext("language", language).
|
|
WithContext("server", serverName).
|
|
WithRemediation(fmt.Sprintf("Install the %s LSP server to enable IDE features for %s", serverName, language))
|
|
}
|
|
|
|
// NewFileTooLarge creates an error for files exceeding size limit.
|
|
func NewFileTooLarge(path string, size, limit int64) *StructuredError {
|
|
return New(ErrFileTooBig, "file exceeds maximum size limit").
|
|
WithContext("file", path).
|
|
WithContext("size_bytes", size).
|
|
WithContext("limit_bytes", limit).
|
|
WithRemediation(fmt.Sprintf("File size (%d bytes) exceeds limit (%d bytes). Consider processing smaller files or increasing the limit.", size, limit))
|
|
}
|
|
|
|
// NewParseError creates an error for parsing failures.
|
|
func NewParseError(language, file string, cause error) *StructuredError {
|
|
return Wrap(ErrParseFailed, fmt.Sprintf("failed to parse %s file", language), cause).
|
|
WithContext("language", language).
|
|
WithContext("file", file).
|
|
WithRemediation("Check file syntax and ensure it's valid source code for the specified language")
|
|
}
|
|
|
|
// NewSearchTimeout creates an error for search timeouts.
|
|
func NewSearchTimeout(pattern string, duration string) *StructuredError {
|
|
return New(ErrRipgrepTimeout, "search operation timed out").
|
|
WithContext("pattern", pattern).
|
|
WithContext("duration", duration).
|
|
WithRemediation("Try narrowing the search scope, using more specific patterns, or increasing the timeout limit")
|
|
}
|
|
|
|
// NewEditValidationError creates an error for edit validation failures.
|
|
func NewEditValidationError(file string, cause error) *StructuredError {
|
|
return Wrap(ErrValidationFailed, "edit validation failed - syntax errors detected", cause).
|
|
WithContext("file", file).
|
|
WithRemediation("Review the edit operation and ensure it produces valid syntax. The file was not modified.")
|
|
}
|
|
|
|
// NewFileNotFoundError creates an error for missing files.
|
|
func NewFileNotFoundError(file string) *StructuredError {
|
|
return New(ErrFileNotFound, fmt.Sprintf("file not found: %s", file)).
|
|
WithContext("file", file).
|
|
WithRemediation("Verify the file path is correct and the file exists")
|
|
}
|
|
|
|
// NewFileNotReadableError creates an error for unreadable files.
|
|
func NewFileNotReadableError(file string, cause error) *StructuredError {
|
|
return Wrap(ErrFileNotReadable, fmt.Sprintf("cannot read file: %s", file), cause).
|
|
WithContext("file", file).
|
|
WithRemediation("Check file permissions and ensure the file is not locked by another process")
|
|
}
|
|
|
|
// NewFileNotWritableError creates an error for write failures.
|
|
func NewFileNotWritableError(file string, cause error) *StructuredError {
|
|
return Wrap(ErrFileNotWritable, fmt.Sprintf("cannot write to file: %s", file), cause).
|
|
WithContext("file", file).
|
|
WithRemediation("Check file permissions, disk space, and ensure the file is not locked by another process")
|
|
}
|
|
|
|
// NewNodeNotFoundError creates an error when AST node selector finds no matches.
|
|
func NewNodeNotFoundError(selector string) *StructuredError {
|
|
return New(ErrNodeNotFound, "no AST nodes match the selector criteria").
|
|
WithContext("selector", selector).
|
|
WithRemediation("Verify the selector criteria (kind, name, pattern, line) match an existing code structure")
|
|
}
|
|
|
|
// NewInvalidSelectionError creates an error for ambiguous or invalid selectors.
|
|
func NewInvalidSelectionError(message string) *StructuredError {
|
|
return New(ErrInvalidSelection, message).
|
|
WithRemediation("Refine the selector to be more specific or provide a selector_index to choose between multiple matches")
|
|
}
|
|
|
|
// NewInvalidEditError creates an error for invalid edit operations.
|
|
func NewInvalidEditError(message string) *StructuredError {
|
|
return New(ErrInvalidEdit, message).
|
|
WithRemediation("Review the edit request and ensure all required fields are provided with valid values")
|
|
}
|