Files
atomctl/pkg/ast/provider/errors.go
Rogee e1f83ae469 feat: 重构 pkg/ast/provider 模块,优化代码组织逻辑和功能实现
## 主要改进

### 架构重构
- 将单体 provider.go 拆分为多个专门的模块文件
- 实现了清晰的职责分离和模块化设计
- 遵循 SOLID 原则,提高代码可维护性

### 新增功能
- **验证规则系统**: 实现了完整的 provider 验证框架
- **报告生成器**: 支持多种格式的验证报告 (JSON/HTML/Markdown/Text)
- **解析器优化**: 重新设计了解析流程,提高性能和可扩展性
- **错误处理**: 增强了错误处理和诊断能力

### 修复关键 Bug
- 修复 @provider(job) 注解缺失 __job 注入参数的问题
- 统一了 job 和 cronjob 模式的处理逻辑
- 确保了 provider 生成的正确性和一致性

### 代码质量提升
- 添加了完整的测试套件
- 引入了 golangci-lint 代码质量检查
- 优化了代码格式和结构
- 增加了详细的文档和规范

### 文件结构优化
```
pkg/ast/provider/
├── types.go              # 类型定义
├── parser.go             # 解析器实现
├── validator.go          # 验证规则
├── report_generator.go   # 报告生成
├── renderer.go           # 渲染器
├── comment_parser.go     # 注解解析
├── modes.go             # 模式定义
├── errors.go            # 错误处理
└── validator_test.go    # 测试文件
```

### 兼容性
- 保持向后兼容性
- 支持现有的所有 provider 模式
- 优化了 API 设计和用户体验

This completes the implementation of T025-T029 tasks following TDD principles,
including validation rules implementation and critical bug fixes.
2025-09-19 18:58:30 +08:00

435 lines
11 KiB
Go

package provider
import (
"errors"
"fmt"
"io/fs"
"os"
"runtime"
"strings"
)
// ParserError represents errors that occur during parsing
type ParserError struct {
Operation string `json:"operation"`
File string `json:"file"`
Line int `json:"line"`
Column int `json:"column"`
Message string `json:"message"`
Code string `json:"code,omitempty"`
Severity string `json:"severity"` // "error", "warning", "info"
Cause error `json:"cause,omitempty"`
Stack string `json:"stack,omitempty"`
}
// Error implements the error interface
func (e *ParserError) Error() string {
if e.File != "" {
return fmt.Sprintf("%s: %s at %s:%d:%d", e.Operation, e.Message, e.File, e.Line, e.Column)
}
return fmt.Sprintf("%s: %s", e.Operation, e.Message)
}
// Unwrap implements the error unwrapping interface
func (e *ParserError) Unwrap() error {
return e.Cause
}
// Is implements the error comparison interface
func (e *ParserError) Is(target error) bool {
var other *ParserError
if errors.As(target, &other) {
return e.Operation == other.Operation && e.Code == other.Code
}
return false
}
// RendererError represents errors that occur during rendering
type RendererError struct {
Operation string `json:"operation"`
Template string `json:"template"`
Target string `json:"target,omitempty"`
Message string `json:"message"`
Code string `json:"code,omitempty"`
Cause error `json:"cause,omitempty"`
Stack string `json:"stack,omitempty"`
}
// Error implements the error interface
func (e *RendererError) Error() string {
if e.Template != "" {
return fmt.Sprintf("renderer %s (template %s): %s", e.Operation, e.Template, e.Message)
}
return fmt.Sprintf("renderer %s: %s", e.Operation, e.Message)
}
// Unwrap implements the error unwrapping interface
func (e *RendererError) Unwrap() error {
return e.Cause
}
// Is implements the error comparison interface
func (e *RendererError) Is(target error) bool {
var other *RendererError
if errors.As(target, &other) {
return e.Operation == other.Operation && e.Code == other.Code
}
return false
}
// FileSystemError represents file system related errors
type FileSystemError struct {
Operation string `json:"operation"`
Path string `json:"path"`
Message string `json:"message"`
Code string `json:"code,omitempty"`
Cause error `json:"cause,omitempty"`
}
// Error implements the error interface
func (e *FileSystemError) Error() string {
return fmt.Sprintf("file system %s failed for %s: %s", e.Operation, e.Path, e.Message)
}
// Unwrap implements the error unwrapping interface
func (e *FileSystemError) Unwrap() error {
return e.Cause
}
// ConfigurationError represents configuration related errors
type ConfigurationError struct {
Field string `json:"field"`
Value string `json:"value,omitempty"`
Message string `json:"message"`
Code string `json:"code,omitempty"`
Cause error `json:"cause,omitempty"`
}
// Error implements the error interface
func (e *ConfigurationError) Error() string {
if e.Value != "" {
return fmt.Sprintf("configuration error for field %s (value: %s): %s", e.Field, e.Value, e.Message)
}
return fmt.Sprintf("configuration error for field %s: %s", e.Field, e.Message)
}
// Unwrap implements the error unwrapping interface
func (e *ConfigurationError) Unwrap() error {
return e.Cause
}
// Error codes
const (
ErrCodeFileNotFound = "FILE_NOT_FOUND"
ErrCodePermissionDenied = "PERMISSION_DENIED"
ErrCodeInvalidSyntax = "INVALID_SYNTAX"
ErrCodeInvalidAnnotation = "INVALID_ANNOTATION"
ErrCodeInvalidMode = "INVALID_MODE"
ErrCodeInvalidType = "INVALID_TYPE"
ErrCodeTemplateNotFound = "TEMPLATE_NOT_FOUND"
ErrCodeTemplateError = "TEMPLATE_ERROR"
ErrCodeValidationFailed = "VALIDATION_FAILED"
ErrCodeConfigurationError = "CONFIGURATION_ERROR"
ErrCodeFileSystemError = "FILE_SYSTEM_ERROR"
ErrCodeUnknownError = "UNKNOWN_ERROR"
)
// Error severity levels
const (
SeverityError = "error"
SeverityWarning = "warning"
SeverityInfo = "info"
)
// Error builder functions
// NewParserError creates a new ParserError
func NewParserError(operation, message string) *ParserError {
return &ParserError{
Operation: operation,
Message: message,
Severity: SeverityError,
}
}
// NewParserErrorWithCause creates a new ParserError with a cause
func NewParserErrorWithCause(operation, message string, cause error) *ParserError {
err := NewParserError(operation, message)
err.Cause = cause
err.Stack = captureStackTrace(2)
return err
}
// NewParserErrorAtLocation creates a new ParserError with file location
func NewParserErrorAtLocation(operation, file string, line, column int, message string) *ParserError {
return &ParserError{
Operation: operation,
File: file,
Line: line,
Column: column,
Message: message,
Severity: SeverityError,
}
}
// NewValidationError creates a new ValidationError
func NewValidationError(ruleName, message string) *ValidationError {
return &ValidationError{
RuleName: ruleName,
Message: message,
Severity: SeverityError,
}
}
// NewValidationErrorWithCause creates a new ValidationError with a cause
func NewValidationErrorWithCause(ruleName, message string, cause error) *ValidationError {
err := NewValidationError(ruleName, message)
err.Cause = cause
return err
}
// NewRendererError creates a new RendererError
func NewRendererError(operation, message string) *RendererError {
return &RendererError{
Operation: operation,
Message: message,
}
}
// NewRendererErrorWithCause creates a new RendererError with a cause
func NewRendererErrorWithCause(operation, message string, cause error) *RendererError {
err := NewRendererError(operation, message)
err.Cause = cause
err.Stack = captureStackTrace(2)
return err
}
// NewFileSystemError creates a new FileSystemError
func NewFileSystemError(operation, path, message string) *FileSystemError {
return &FileSystemError{
Operation: operation,
Path: path,
Message: message,
}
}
// NewFileSystemErrorFromError creates a new FileSystemError from an existing error
func NewFileSystemErrorFromError(operation, path string, err error) *FileSystemError {
return &FileSystemError{
Operation: operation,
Path: path,
Message: err.Error(),
Cause: err,
}
}
// NewConfigurationError creates a new ConfigurationError
func NewConfigurationError(field, message string) *ConfigurationError {
return &ConfigurationError{
Field: field,
Message: message,
}
}
// WrapError wraps an error with additional context
func WrapError(err error, operation string) error {
if err == nil {
return nil
}
switch e := err.(type) {
case *ParserError:
return NewParserErrorWithCause(e.Operation, e.Message, err)
case *ValidationError:
return NewValidationErrorWithCause(e.RuleName, e.Message, err)
case *RendererError:
return NewRendererErrorWithCause(e.Operation, e.Message, err)
case *FileSystemError:
return NewFileSystemErrorFromError(e.Operation, e.Path, err)
case *ConfigurationError:
return NewConfigurationError(e.Field, e.Message)
default:
return fmt.Errorf("%s: %w", operation, err)
}
}
// Error utility functions
// IsParserError checks if an error is a ParserError
func IsParserError(err error) bool {
var target *ParserError
return errors.As(err, &target)
}
// IsValidationError checks if an error is a ValidationError
func IsValidationError(err error) bool {
var target *ValidationError
return errors.As(err, &target)
}
// IsRendererError checks if an error is a RendererError
func IsRendererError(err error) bool {
var target *RendererError
return errors.As(err, &target)
}
// IsFileSystemError checks if an error is a FileSystemError
func IsFileSystemError(err error) bool {
var target *FileSystemError
return errors.As(err, &target)
}
// IsConfigurationError checks if an error is a ConfigurationError
func IsConfigurationError(err error) bool {
var target *ConfigurationError
return errors.As(err, &target)
}
// GetErrorCode returns the error code for a given error
func GetErrorCode(err error) string {
if err == nil {
return ""
}
switch e := err.(type) {
case *ParserError:
return e.Code
case *RendererError:
return e.Code
case *FileSystemError:
return e.Code
case *ConfigurationError:
return e.Code
default:
return ErrCodeUnknownError
}
}
// IsFileNotFoundError checks if an error is a file not found error
func IsFileNotFoundError(err error) bool {
if errors.Is(err, fs.ErrNotExist) {
return true
}
if pathErr, ok := err.(*os.PathError); ok {
return errors.Is(pathErr.Err, fs.ErrNotExist)
}
return false
}
// IsPermissionError checks if an error is a permission error
func IsPermissionError(err error) bool {
if errors.Is(err, os.ErrPermission) {
return true
}
if pathErr, ok := err.(*os.PathError); ok {
return errors.Is(pathErr.Err, os.ErrPermission)
}
return false
}
// Error recovery functions
// RecoverFromParseError attempts to recover from parsing errors
func RecoverFromParseError(err error) error {
if err == nil {
return nil
}
// If it's a file system error, provide more helpful message
if IsFileSystemError(err) {
var fsErr *FileSystemError
if errors.As(err, &fsErr) {
if IsFileNotFoundError(fsErr.Cause) {
return NewParserErrorWithCause(
"parse",
fmt.Sprintf("file not found: %s", fsErr.Path),
err,
)
}
if IsPermissionError(fsErr.Cause) {
return NewParserErrorWithCause(
"parse",
fmt.Sprintf("permission denied: %s", fsErr.Path),
err,
)
}
}
}
// For syntax errors, try to provide location information
if strings.Contains(err.Error(), "syntax") {
return NewParserErrorWithCause("parse", "syntax error in source code", err)
}
// Default: wrap the error with parsing context
return WrapError(err, "parse")
}
// captureStackTrace captures the current stack trace
func captureStackTrace(skip int) string {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(skip, pcs[:])
frames := runtime.CallersFrames(pcs[:n])
var builder strings.Builder
for {
frame, more := frames.Next()
if !more || strings.Contains(frame.Function, "runtime.") {
break
}
fmt.Fprintf(&builder, "%s\n\t%s:%d\n", frame.Function, frame.File, frame.Line)
}
return builder.String()
}
// Error aggregation
// ErrorGroup represents a group of related errors
type ErrorGroup struct {
Errors []error `json:"errors"`
}
// Error implements the error interface
func (eg *ErrorGroup) Error() string {
if len(eg.Errors) == 0 {
return "no errors"
}
if len(eg.Errors) == 1 {
return eg.Errors[0].Error()
}
var messages []string
for _, err := range eg.Errors {
messages = append(messages, err.Error())
}
return fmt.Sprintf("multiple errors occurred:\n\t%s", strings.Join(messages, "\n\t"))
}
// Unwrap implements the error unwrapping interface
func (eg *ErrorGroup) Unwrap() []error {
return eg.Errors
}
// NewErrorGroup creates a new ErrorGroup
func NewErrorGroup(errors ...error) *ErrorGroup {
var filteredErrors []error
for _, err := range errors {
if err != nil {
filteredErrors = append(filteredErrors, err)
}
}
return &ErrorGroup{Errors: filteredErrors}
}
// CollectErrors collects non-nil errors from a slice
func CollectErrors(errors ...error) *ErrorGroup {
return NewErrorGroup(errors...)
}