mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-06-05 22:23:50 +00:00
Ho hum.
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
// 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")
|
||||
}
|
||||
Reference in New Issue
Block a user