Files
atomctl/pkg/ast/provider/validator.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

1207 lines
35 KiB
Go

package provider
import (
"fmt"
"regexp"
"strings"
"time"
"unicode"
)
// Validator defines the interface for validating provider configurations
type Validator interface {
// Validate validates a provider configuration
Validate(provider *Provider) error
// ValidateComment validates a provider comment annotation
ValidateComment(comment string) error
// AddRule adds a validation rule to the validator
AddRule(rule ValidationRule)
// RemoveRule removes a validation rule from the validator
RemoveRule(name string)
// GetRules returns all validation rules
GetRules() []ValidationRule
// ValidateAll validates multiple providers and returns a comprehensive report
ValidateAll(providers []*Provider) *ValidationReport
}
// ValidationRule defines the interface for validation rules
type ValidationRule interface {
// Name returns the name of the validation rule
Name() string
// Validate validates a provider against this rule
Validate(provider *Provider) *ValidationError
// Description returns a human-readable description of the rule
Description() string
}
// ValidationError represents a validation error
type ValidationError struct {
RuleName string `json:"rule_name"`
Message string `json:"message"`
Field string `json:"field,omitempty"`
Value string `json:"value,omitempty"`
Severity string `json:"severity"` // "error", "warning", "info"
Suggestion string `json:"suggestion,omitempty"`
ProviderRef string `json:"provider_ref,omitempty"`
Cause error `json:"cause,omitempty"`
}
// Error implements the error interface
func (e *ValidationError) Error() string {
if e.ProviderRef != "" {
return fmt.Sprintf("[%s] %s: %s", e.ProviderRef, e.RuleName, e.Message)
}
return fmt.Sprintf("%s: %s", e.RuleName, e.Message)
}
// Unwrap implements the error unwrapping interface
func (e *ValidationError) Unwrap() error {
return e.Cause
}
// ValidationReport represents the result of validating multiple providers
type ValidationReport struct {
Timestamp time.Time `json:"timestamp"`
TotalProviders int `json:"total_providers"`
ValidCount int `json:"valid_count"`
InvalidCount int `json:"invalid_count"`
Errors []ValidationError `json:"errors"`
Warnings []ValidationError `json:"warnings"`
Infos []ValidationError `json:"infos"`
IsValid bool `json:"is_valid"`
Statistics *ValidationStatistics `json:"statistics,omitempty"`
}
// ValidationStatistics contains detailed statistics about validation results
type ValidationStatistics struct {
ProvidersByMode map[string]int `json:"providers_by_mode"`
ProvidersByStatus map[string]int `json:"providers_by_status"`
RuleViolations map[string]int `json:"rule_violations"`
ErrorByField map[string]int `json:"error_by_field"`
CommonErrors []ValidationErrorDetail `json:"common_errors"`
}
// ValidationErrorDetail represents a detailed validation error with count
type ValidationErrorDetail struct {
ErrorKey string `json:"error_key"`
Count int `json:"count"`
}
// GoValidator implements the Validator interface
type GoValidator struct {
rules []ValidationRule
}
// NewGoValidator creates a new GoValidator with default rules
func NewGoValidator() *GoValidator {
validator := &GoValidator{
rules: make([]ValidationRule, 0),
}
// Add default validation rules
validator.AddDefaultRules()
return validator
}
// Validate implements Validator.Validate
func (v *GoValidator) Validate(provider *Provider) error {
var errors []ValidationError
for _, rule := range v.rules {
if err := rule.Validate(provider); err != nil {
errors = append(errors, *err)
}
}
if len(errors) > 0 {
return &ValidationErrors{Errors: errors}
}
return nil
}
// ValidateComment implements Validator.ValidateComment
func (v *GoValidator) ValidateComment(comment string) error {
if !strings.HasPrefix(comment, "@provider") {
return &ValidationError{
RuleName: "CommentFormat",
Message: "provider comment must start with @provider",
Severity: "error",
}
}
// Parse the comment to check its structure
providerDoc := parseProvider(comment)
// Validate the parsed structure
if providerDoc.Mode != "" && !IsValidProviderMode(providerDoc.Mode) {
return &ValidationError{
RuleName: "ProviderMode",
Message: fmt.Sprintf("invalid provider mode: %s", providerDoc.Mode),
Severity: "error",
}
}
return nil
}
// AddRule implements Validator.AddRule
func (v *GoValidator) AddRule(rule ValidationRule) {
v.rules = append(v.rules, rule)
}
// RemoveRule implements Validator.RemoveRule
func (v *GoValidator) RemoveRule(name string) {
for i, rule := range v.rules {
if rule.Name() == name {
v.rules = append(v.rules[:i], v.rules[i+1:]...)
break
}
}
}
// GetRules implements Validator.GetRules
func (v *GoValidator) GetRules() []ValidationRule {
return v.rules
}
// ValidateAll implements Validator.ValidateAll
func (v *GoValidator) ValidateAll(providers []*Provider) *ValidationReport {
report := &ValidationReport{
Timestamp: time.Now(),
TotalProviders: len(providers),
ValidCount: 0,
InvalidCount: 0,
Errors: make([]ValidationError, 0),
Warnings: make([]ValidationError, 0),
Infos: make([]ValidationError, 0),
}
for _, provider := range providers {
providerRef := fmt.Sprintf("%s:%s", provider.PkgName, provider.StructName)
providerValid := true
for _, rule := range v.rules {
if err := rule.Validate(provider); err != nil {
err.ProviderRef = providerRef
switch err.Severity {
case "error":
report.Errors = append(report.Errors, *err)
providerValid = false
case "warning":
report.Warnings = append(report.Warnings, *err)
case "info":
report.Infos = append(report.Infos, *err)
}
}
}
if providerValid {
report.ValidCount++
} else {
report.InvalidCount++
}
}
report.IsValid = len(report.Errors) == 0
return report
}
// ValidateWithDetails validates all providers and returns a detailed report with statistics
func (v *GoValidator) ValidateWithDetails(providers []*Provider) *ValidationReport {
report := v.ValidateAll(providers)
// Add detailed statistics
stats := calculateValidationStatistics(report, providers)
report.Statistics = &stats
return report
}
// calculateValidationStatistics calculates detailed validation statistics
func calculateValidationStatistics(report *ValidationReport, providers []*Provider) ValidationStatistics {
stats := ValidationStatistics{
ProvidersByMode: make(map[string]int),
ProvidersByStatus: make(map[string]int),
RuleViolations: make(map[string]int),
ErrorByField: make(map[string]int),
CommonErrors: make([]ValidationErrorDetail, 0),
}
// Count providers by mode
for _, provider := range providers {
mode := string(provider.Mode)
stats.ProvidersByMode[mode]++
}
// Count violations by rule
allIssues := append(append(report.Errors, report.Warnings...), report.Infos...)
for _, issue := range allIssues {
stats.RuleViolations[issue.RuleName]++
stats.ErrorByField[issue.Field]++
}
// Find common errors
errorCounts := make(map[string]int)
for _, err := range report.Errors {
key := fmt.Sprintf("%s:%s", err.RuleName, err.Message)
errorCounts[key]++
}
// Get top 5 common errors
for key, count := range errorCounts {
stats.CommonErrors = append(stats.CommonErrors, ValidationErrorDetail{
ErrorKey: key,
Count: count,
})
}
// Sort by count (descending)
for i := 0; i < len(stats.CommonErrors)-1; i++ {
for j := i + 1; j < len(stats.CommonErrors); j++ {
if stats.CommonErrors[i].Count < stats.CommonErrors[j].Count {
stats.CommonErrors[i], stats.CommonErrors[j] = stats.CommonErrors[j], stats.CommonErrors[i]
}
}
}
// Keep only top 5
if len(stats.CommonErrors) > 5 {
stats.CommonErrors = stats.CommonErrors[:5]
}
return stats
}
// GenerateReport generates a formatted validation report
func (v *GoValidator) GenerateReport(providers []*Provider, format ReportFormat) (string, error) {
report := v.ValidateWithDetails(providers)
generator := NewReportGenerator(report)
return generator.GenerateReport(format)
}
// SaveReport saves a validation report to a file
func (v *GoValidator) SaveReport(providers []*Provider, filename string, format ReportFormat) error {
report := v.ValidateWithDetails(providers)
writer := NewReportWriter(report)
return writer.WriteToFile(filename, format)
}
// PrintReport prints a validation report to the console
func (v *GoValidator) PrintReport(providers []*Provider, format ReportFormat) error {
report := v.ValidateWithDetails(providers)
writer := NewReportWriter(report)
return writer.WriteToConsole(format)
}
// AddDefaultRules adds the default validation rules
func (v *GoValidator) AddDefaultRules() {
v.AddRule(&StructNameRule{})
v.AddRule(&ReturnTypeRule{})
v.AddRule(&ProviderModeRule{})
v.AddRule(&InjectionParamsRule{})
v.AddRule(&PackageAliasRule{})
}
// ValidationErrors represents multiple validation errors
type ValidationErrors struct {
Errors []ValidationError
}
// Error implements the error interface
func (e *ValidationErrors) Error() string {
if len(e.Errors) == 0 {
return "no validation errors"
}
if len(e.Errors) == 1 {
return e.Errors[0].Message
}
return fmt.Sprintf("%d validation errors occurred, first: %s", len(e.Errors), e.Errors[0].Message)
}
// StructNameRule validates struct names
type StructNameRule struct{}
func (r *StructNameRule) Name() string { return "StructName" }
func (r *StructNameRule) Description() string {
return "Validates that struct names follow Go naming conventions"
}
func (r *StructNameRule) Validate(provider *Provider) *ValidationError {
if provider.StructName == "" {
return &ValidationError{
RuleName: r.Name(),
Message: "struct name cannot be empty",
Field: "StructName",
Severity: "error",
}
}
if !isExportedName(provider.StructName) {
return &ValidationError{
RuleName: r.Name(),
Message: "struct name must be exported (start with uppercase letter)",
Field: "StructName",
Value: provider.StructName,
Severity: "error",
}
}
// Check for Go naming conventions
if !isValidGoIdentifier(provider.StructName) {
return &ValidationError{
RuleName: r.Name(),
Message: "struct name must be a valid Go identifier",
Field: "StructName",
Value: provider.StructName,
Severity: "error",
}
}
// Check for common naming conventions (CamelCase for structs)
if !isCamelCase(provider.StructName) {
return &ValidationError{
RuleName: r.Name(),
Message: "struct name should use CamelCase convention",
Field: "StructName",
Value: provider.StructName,
Severity: "warning",
Suggestion: "consider using CamelCase (e.g., UserService instead of userService or USER_SERVICE)",
}
}
// Check for reserved words or problematic names
if isReservedWord(provider.StructName) {
return &ValidationError{
RuleName: r.Name(),
Message: "struct name should not use reserved words or common Go types",
Field: "StructName",
Value: provider.StructName,
Severity: "warning",
Suggestion: "choose a more descriptive name that doesn't conflict with Go built-ins",
}
}
return nil
}
// ReturnTypeRule validates return types
type ReturnTypeRule struct{}
func (r *ReturnTypeRule) Name() string { return "ReturnType" }
func (r *ReturnTypeRule) Description() string {
return "Validates that return types are properly formatted and consistent"
}
func (r *ReturnTypeRule) Validate(provider *Provider) *ValidationError {
if provider.ReturnType == "" {
return &ValidationError{
RuleName: r.Name(),
Message: "return type cannot be empty",
Field: "ReturnType",
Severity: "error",
}
}
// Check if return type is a valid Go type identifier
if !isValidGoType(provider.ReturnType) {
return &ValidationError{
RuleName: r.Name(),
Message: fmt.Sprintf("invalid return type format: %s", provider.ReturnType),
Field: "ReturnType",
Value: provider.ReturnType,
Severity: "error",
}
}
// Check for mode-specific return type requirements
if err := validateModeSpecificReturnType(provider); err != nil {
return err
}
// Check for interface types (should generally be avoided for providers)
if strings.HasPrefix(provider.ReturnType, "*") &&
(strings.HasSuffix(provider.ReturnType, "Interface") ||
strings.HasSuffix(provider.ReturnType, "IFace")) {
return &ValidationError{
RuleName: r.Name(),
Message: "pointer to interface types should generally be avoided",
Field: "ReturnType",
Value: provider.ReturnType,
Severity: "warning",
Suggestion: "consider using the interface type directly or a concrete type",
}
}
// Check for overly complex generic types
if strings.Count(provider.ReturnType, "[") > 2 || strings.Count(provider.ReturnType, "]") > 2 {
return &ValidationError{
RuleName: r.Name(),
Message: "return type appears overly complex with too many generic parameters",
Field: "ReturnType",
Value: provider.ReturnType,
Severity: "warning",
Suggestion: "consider simplifying the type or using type aliases",
}
}
// Check for pointer vs value type consistency
if strings.HasPrefix(provider.ReturnType, "*") {
// Pointer type validations
pointedType := strings.TrimPrefix(provider.ReturnType, "*")
if !isExportedName(pointedType) {
return &ValidationError{
RuleName: r.Name(),
Message: "pointed type in pointer return type must be exported",
Field: "ReturnType",
Value: provider.ReturnType,
Severity: "error",
}
}
} else {
// Value type validations
if !isExportedName(provider.ReturnType) && !isBuiltInType(provider.ReturnType) {
return &ValidationError{
RuleName: r.Name(),
Message: "non-pointer return type must be either exported or a built-in type",
Field: "ReturnType",
Value: provider.ReturnType,
Severity: "error",
}
}
}
return nil
}
// validateModeSpecificReturnType checks return type requirements for specific provider modes
func validateModeSpecificReturnType(provider *Provider) *ValidationError {
switch provider.Mode {
case ProviderModeGrpc:
// gRPC providers should typically return interface types
if !strings.HasSuffix(provider.ReturnType, "Client") &&
!strings.HasSuffix(provider.ReturnType, "Service") {
return &ValidationError{
RuleName: "ReturnType",
Message: "gRPC providers should typically return client or service interface types",
Field: "ReturnType",
Value: provider.ReturnType,
Severity: "warning",
Suggestion: "consider naming the return type ending with Client or Service",
}
}
case ProviderModeModel:
// Model providers should return struct types
if strings.Contains(provider.ReturnType, "interface") ||
strings.Contains(provider.ReturnType, "Interface") {
return &ValidationError{
RuleName: "ReturnType",
Message: "model providers should return concrete struct types, not interfaces",
Field: "ReturnType",
Value: provider.ReturnType,
Severity: "error",
}
}
case ProviderModeJob, ProviderModeCronJob:
// Job providers should return types that implement job interfaces
if !strings.Contains(provider.ReturnType, "Job") {
return &ValidationError{
RuleName: "ReturnType",
Message: "job providers should return types that implement job interfaces",
Field: "ReturnType",
Value: provider.ReturnType,
Severity: "warning",
Suggestion: "consider naming the return type to indicate it's a job handler",
}
}
}
return nil
}
// isBuiltInType checks if a type is a Go built-in type
func isBuiltInType(typeStr string) bool {
builtInTypes := map[string]bool{
"string": true, "int": true, "int8": true, "int16": true, "int32": true, "int64": true,
"uint": true, "uint8": true, "uint16": true, "uint32": true, "uint64": true,
"float32": true, "float64": true, "complex64": true, "complex128": true,
"bool": true, "byte": true, "rune": true, "error": true,
}
return builtInTypes[typeStr]
}
// ProviderModeRule validates provider modes
type ProviderModeRule struct{}
func (r *ProviderModeRule) Name() string { return "ProviderMode" }
func (r *ProviderModeRule) Description() string {
return "Validates that provider modes are valid and appropriate for the provider configuration"
}
func (r *ProviderModeRule) Validate(provider *Provider) *ValidationError {
if !IsValidProviderMode(string(provider.Mode)) {
return &ValidationError{
RuleName: r.Name(),
Message: fmt.Sprintf("invalid provider mode: %s", provider.Mode),
Field: "Mode",
Value: string(provider.Mode),
Severity: "error",
}
}
// Validate mode-specific configurations
if err := validateModeSpecificConfiguration(provider); err != nil {
return err
}
// Check for deprecated or discouraged mode combinations
if err := validateModeCombinations(provider); err != nil {
return err
}
return nil
}
// validateModeSpecificConfiguration checks mode-specific configuration requirements
func validateModeSpecificConfiguration(provider *Provider) *ValidationError {
switch provider.Mode {
case ProviderModeGrpc:
// gRPC providers need gRPC register function
if provider.GrpcRegisterFunc == "" {
return &ValidationError{
RuleName: "ProviderMode",
Message: "gRPC providers must specify a gRPC register function",
Field: "GrpcRegisterFunc",
Value: provider.GrpcRegisterFunc,
Severity: "error",
Suggestion: "add gRPC register function to the provider annotation",
}
}
case ProviderModeJob, ProviderModeCronJob:
// Job providers should have prepare function
if !provider.NeedPrepareFunc {
return &ValidationError{
RuleName: "ProviderMode",
Message: "job providers should typically need a prepare function",
Field: "NeedPrepareFunc",
Value: fmt.Sprintf("%t", provider.NeedPrepareFunc),
Severity: "warning",
Suggestion: "consider setting prepare function for job initialization",
}
}
// Check if job provider has proper injection parameters
hasJobParam := false
for paramName := range provider.InjectParams {
if paramName == "__job" {
hasJobParam = true
break
}
}
if !hasJobParam {
return &ValidationError{
RuleName: "ProviderMode",
Message: "job providers should inject __job parameter",
Field: "InjectParams",
Severity: "warning",
Suggestion: "add __job *job.Job parameter to injection parameters",
}
}
case ProviderModeEvent:
// Event providers should have __event parameter
hasEventParam := false
for paramName := range provider.InjectParams {
if paramName == "__event" {
hasEventParam = true
break
}
}
if !hasEventParam {
return &ValidationError{
RuleName: "ProviderMode",
Message: "event providers should inject __event parameter",
Field: "InjectParams",
Severity: "warning",
Suggestion: "add __event parameter to injection parameters",
}
}
case ProviderModeBasic:
// Basic providers should not have special parameters
for paramName := range provider.InjectParams {
if paramName == "__job" || paramName == "__event" || paramName == "__grpc" {
return &ValidationError{
RuleName: "ProviderMode",
Message: "basic providers should not inject special parameters (__job, __event, __grpc)",
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
}
}
return nil
}
// validateModeCombinations checks for problematic mode combinations
func validateModeCombinations(provider *Provider) *ValidationError {
// Check for incompatible mode combinations
if provider.ProviderGroup != "" {
groupLower := strings.ToLower(provider.ProviderGroup)
modeStr := string(provider.Mode)
// Basic checks for common issues
if (modeStr == "grpc" && groupLower == "job") ||
(modeStr == "job" && groupLower == "grpc") {
return &ValidationError{
RuleName: "ProviderMode",
Message: "incompatible combination of provider mode and group",
Field: "ProviderGroup",
Value: provider.ProviderGroup,
Severity: "warning",
Suggestion: "ensure provider mode and group are compatible",
}
}
}
// Check for mode-specific naming suggestions
if err := validateModeNamingConventions(provider); err != nil {
return err
}
return nil
}
// validateModeNamingConventions provides naming suggestions for different modes
func validateModeNamingConventions(provider *Provider) *ValidationError {
structName := provider.StructName
switch provider.Mode {
case ProviderModeGrpc:
if !strings.HasSuffix(structName, "Client") &&
!strings.HasSuffix(structName, "Service") &&
!strings.HasSuffix(structName, "Provider") {
return &ValidationError{
RuleName: "ProviderMode",
Message: "gRPC provider struct names should typically end with Client, Service, or Provider",
Field: "StructName",
Value: structName,
Severity: "warning",
Suggestion: "consider naming the struct to indicate it's a gRPC provider",
}
}
case ProviderModeJob, ProviderModeCronJob:
if !strings.HasSuffix(structName, "Job") &&
!strings.HasSuffix(structName, "Worker") &&
!strings.HasSuffix(structName, "Handler") {
return &ValidationError{
RuleName: "ProviderMode",
Message: "job provider struct names should typically end with Job, Worker, or Handler",
Field: "StructName",
Value: structName,
Severity: "warning",
Suggestion: "consider naming the struct to indicate it's a job provider",
}
}
case ProviderModeEvent:
if !strings.HasSuffix(structName, "Handler") &&
!strings.HasSuffix(structName, "Listener") &&
!strings.HasSuffix(structName, "Subscriber") {
return &ValidationError{
RuleName: "ProviderMode",
Message: "event provider struct names should typically end with Handler, Listener, or Subscriber",
Field: "StructName",
Value: structName,
Severity: "warning",
Suggestion: "consider naming the struct to indicate it's an event provider",
}
}
case ProviderModeModel:
if !strings.HasSuffix(structName, "Model") &&
!strings.HasSuffix(structName, "Entity") &&
!strings.HasSuffix(structName, "Repo") &&
!strings.HasSuffix(structName, "Repository") {
return &ValidationError{
RuleName: "ProviderMode",
Message: "model provider struct names should typically end with Model, Entity, Repo, or Repository",
Field: "StructName",
Value: structName,
Severity: "warning",
Suggestion: "consider naming the struct to indicate it's a model provider",
}
}
}
return nil
}
// InjectionParamsRule validates injection parameters
type InjectionParamsRule struct{}
func (r *InjectionParamsRule) Name() string { return "InjectionParams" }
func (r *InjectionParamsRule) Description() string {
return "Validates injection parameters for consistency and correctness"
}
func (r *InjectionParamsRule) Validate(provider *Provider) *ValidationError {
if len(provider.InjectParams) == 0 {
// Some providers might not need injection parameters
if provider.Mode == ProviderModeBasic {
return nil // Basic providers can have no injection params
}
return &ValidationError{
RuleName: r.Name(),
Message: "providers should have at least one injection parameter",
Field: "InjectParams",
Severity: "warning",
Suggestion: "consider adding injection parameters for dependency injection",
}
}
// Check for duplicate parameter names
paramNames := make(map[string]bool)
for paramName := range provider.InjectParams {
if paramNames[paramName] {
return &ValidationError{
RuleName: r.Name(),
Message: "duplicate injection parameter name",
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
paramNames[paramName] = true
}
// Validate each injection parameter
for paramName, param := range provider.InjectParams {
if err := validateInjectionParameter(paramName, param, provider.Mode); err != nil {
return err
}
}
// Check for parameter consistency
if err := validateParameterConsistency(provider); err != nil {
return err
}
return nil
}
// validateInjectionParameter validates a single injection parameter
func validateInjectionParameter(paramName string, param InjectParam, mode ProviderMode) *ValidationError {
// Validate parameter name
if paramName == "" {
return &ValidationError{
RuleName: "InjectionParams",
Message: "injection parameter name cannot be empty",
Field: "InjectParams",
Severity: "error",
}
}
// Special parameters are allowed to be unexported
if !isSpecialParameter(paramName) && !isExportedName(paramName) {
return &ValidationError{
RuleName: "InjectionParams",
Message: "injection parameter names must be exported",
Field: "InjectParams",
Value: paramName,
Severity: "warning",
Suggestion: "use exported names for injection parameters",
}
}
// Validate parameter type
if param.Type == "" {
return &ValidationError{
RuleName: "InjectionParams",
Message: "injection parameter type cannot be empty",
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
// Validate type format
if !isValidGoType(param.Type) {
return &ValidationError{
RuleName: "InjectionParams",
Message: fmt.Sprintf("invalid injection parameter type format: %s", param.Type),
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
// Validate package and alias consistency
if param.Package != "" && param.PackageAlias == "" {
return &ValidationError{
RuleName: "InjectionParams",
Message: "package alias is required when package is specified",
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
if param.Package == "" && param.PackageAlias != "" {
return &ValidationError{
RuleName: "InjectionParams",
Message: "package cannot be empty when package alias is specified",
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
// Validate special parameters
if isSpecialParameter(paramName) {
if err := validateSpecialParameter(paramName, param, mode); err != nil {
return err
}
}
return nil
}
// validateParameterConsistency checks for consistency across all parameters
func validateParameterConsistency(provider *Provider) *ValidationError {
// Check for conflicting parameter types
paramTypes := make(map[string]string)
for paramName, param := range provider.InjectParams {
// Check if same type is used with different aliases
if existingAlias, exists := paramTypes[param.Type]; exists {
// Extract package alias from existing parameter
existingParam := provider.InjectParams[existingAlias]
if existingParam.PackageAlias != param.PackageAlias {
return &ValidationError{
RuleName: "InjectionParams",
Message: "same type used with different package aliases",
Field: "InjectParams",
Value: fmt.Sprintf("%s (%s vs %s)", param.Type, existingParam.PackageAlias, param.PackageAlias),
Severity: "warning",
Suggestion: "use consistent package aliases for the same type",
}
}
}
paramTypes[param.Type] = paramName
}
// Check for circular dependencies (simplified check)
for paramName, param := range provider.InjectParams {
if param.Type == provider.StructName {
return &ValidationError{
RuleName: "InjectionParams",
Message: "provider cannot inject itself",
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
}
// Check for mode-specific parameter requirements
if err := validateModeSpecificParameters(provider); err != nil {
return err
}
return nil
}
// validateModeSpecificParameters checks mode-specific parameter requirements
func validateModeSpecificParameters(provider *Provider) *ValidationError {
switch provider.Mode {
case ProviderModeJob, ProviderModeCronJob:
// Job providers should have __job parameter
if _, hasJobParam := provider.InjectParams["__job"]; !hasJobParam {
return &ValidationError{
RuleName: "InjectionParams",
Message: "job providers should inject __job parameter",
Field: "InjectParams",
Severity: "warning",
Suggestion: "add __job *job.Job parameter for job context",
}
}
case ProviderModeEvent:
// Event providers should have __event parameter
if _, hasEventParam := provider.InjectParams["__event"]; !hasEventParam {
return &ValidationError{
RuleName: "InjectionParams",
Message: "event providers should inject __event parameter",
Field: "InjectParams",
Severity: "warning",
Suggestion: "add __event parameter for event context",
}
}
case ProviderModeGrpc:
// gRPC providers should have __grpc parameter
if _, hasGrpcParam := provider.InjectParams["__grpc"]; !hasGrpcParam {
return &ValidationError{
RuleName: "InjectionParams",
Message: "gRPC providers should inject __grpc parameter",
Field: "InjectParams",
Severity: "warning",
Suggestion: "add __grpc parameter for gRPC context",
}
}
}
return nil
}
// validateSpecialParameter validates special parameters like __job, __event, etc.
func validateSpecialParameter(paramName string, param InjectParam, mode ProviderMode) *ValidationError {
switch paramName {
case "__job":
if mode != ProviderModeJob && mode != ProviderModeCronJob {
return &ValidationError{
RuleName: "InjectionParams",
Message: "__job parameter should only be used in job providers",
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
if param.Type != "*job.Job" {
return &ValidationError{
RuleName: "InjectionParams",
Message: "__job parameter should have type *job.Job",
Field: "InjectParams",
Value: param.Type,
Severity: "error",
}
}
case "__event":
if mode != ProviderModeEvent {
return &ValidationError{
RuleName: "InjectionParams",
Message: "__event parameter should only be used in event providers",
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
if !strings.Contains(param.Type, "Event") {
return &ValidationError{
RuleName: "InjectionParams",
Message: "__event parameter should have an event type",
Field: "InjectParams",
Value: param.Type,
Severity: "warning",
Suggestion: "use a type that indicates it's an event",
}
}
case "__grpc":
if mode != ProviderModeGrpc {
return &ValidationError{
RuleName: "InjectionParams",
Message: "__grpc parameter should only be used in gRPC providers",
Field: "InjectParams",
Value: paramName,
Severity: "error",
}
}
if !strings.Contains(param.Type, "grpc") {
return &ValidationError{
RuleName: "InjectionParams",
Message: "__grpc parameter should have a gRPC-related type",
Field: "InjectParams",
Value: param.Type,
Severity: "warning",
Suggestion: "use a type that indicates it's gRPC-related",
}
}
}
return nil
}
// isSpecialParameter checks if a parameter name is a special parameter
func isSpecialParameter(paramName string) bool {
specialParams := map[string]bool{
"__job": true,
"__event": true,
"__grpc": true,
}
return specialParams[paramName]
}
// PackageAliasRule validates package aliases
type PackageAliasRule struct{}
func (r *PackageAliasRule) Name() string { return "PackageAlias" }
func (r *PackageAliasRule) Description() string {
return "Validates package aliases for consistency"
}
func (r *PackageAliasRule) Validate(provider *Provider) *ValidationError {
for alias, path := range provider.Imports {
if alias == "" {
return &ValidationError{
RuleName: r.Name(),
Message: "package alias cannot be empty",
Field: "Imports",
Value: path,
Severity: "error",
}
}
if path == "" {
return &ValidationError{
RuleName: r.Name(),
Message: "package path cannot be empty",
Field: "Imports",
Value: alias,
Severity: "error",
}
}
if !isValidGoIdentifier(alias) {
return &ValidationError{
RuleName: r.Name(),
Message: "package alias must be a valid Go identifier",
Field: "Imports",
Value: alias,
Severity: "error",
}
}
}
return nil
}
// Helper functions
func isExportedName(name string) bool {
if name == "" {
return false
}
return unicode.IsUpper(rune(name[0]))
}
func isValidGoType(typeStr string) bool {
// Enhanced validation for Go type identifiers including pointers
return regexp.MustCompile(`^(\*[a-zA-Z_][a-zA-Z0-9_.]*|[a-zA-Z_][a-zA-Z0-9_.]*(\[\])?|[a-zA-Z_][a-zA-Z0-9_.]*(\[\])?\*?)$`).MatchString(typeStr)
}
func isValidGoIdentifier(name string) bool {
if name == "" {
return false
}
// Check if it's a valid Go identifier
for i, r := range name {
if i == 0 && !unicode.IsLetter(r) && r != '_' {
return false
}
if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' {
return false
}
}
return true
}
func isCamelCase(name string) bool {
if !isValidGoIdentifier(name) {
return false
}
// Check if first character is uppercase (exported)
if !unicode.IsUpper(rune(name[0])) {
return false
}
// Check for snake_case or kebab-case patterns
if strings.Contains(name, "_") || strings.Contains(name, "-") {
return false
}
// Check for ALL_CAPS (usually used for constants)
if strings.ToUpper(name) == name && len(name) > 1 {
return false
}
return true
}
func isReservedWord(name string) bool {
reservedWords := map[string]bool{
// Go keywords
"break": true, "case": true, "chan": true, "const": true, "continue": true,
"default": true, "defer": true, "else": true, "fallthrough": true, "for": true,
"func": true, "go": true, "goto": true, "if": true, "import": true,
"interface": true, "map": true, "package": true, "range": true, "return": true,
"select": true, "struct": true, "switch": true, "type": true, "var": true,
// Predeclared identifiers
"bool": true, "byte": true, "complex64": true, "complex128": true,
"error": true, "float32": true, "float64": true, "int": true, "int8": true,
"int16": true, "int32": true, "int64": true, "rune": true, "string": true,
"uint": true, "uint8": true, "uint16": true, "uint32": true, "uint64": true,
"uintptr": true, "true": true, "false": true, "nil": true, "iota": true,
// Common problematic names for providers
"Provider": true, "Service": true, "Handler": true, "Manager": true,
"Controller": true, "Repository": true, "Config": true, "Client": true,
}
return reservedWords[name]
}
func isValidPackageName(name string) bool {
if name == "" {
return false
}
// Package names should be lowercase, short, and descriptive
if !isValidGoIdentifier(name) {
return false
}
// Package names should typically be lowercase
if name != strings.ToLower(name) {
return false
}
// Avoid common problematic package names
problematicNames := map[string]bool{
"main": true, "testing": true, "fmt": true, "strings": true,
"container": true, "service": true, "utils": true, "common": true,
}
return !problematicNames[name]
}