✨ feat: implement input validation for API v2
- Added go-playground/validator dependency - Created pkg/validation/ package with custom validator wrapper - Implemented request validation for v2 greet endpoint - Added structured validation error responses - Extended BDD tests to cover validation scenarios - Updated AGENTS.md with v2 API documentation - Created ADR 0011-validation-library-selection.md - Simplified server handler creation code - Updated CHANGELOG with implementation details
This commit is contained in:
123
pkg/validation/validator.go
Normal file
123
pkg/validation/validator.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"DanceLessonsCoach/pkg/config"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/locales/en"
|
||||
en_translations "github.com/go-playground/validator/v10/translations/en"
|
||||
"github.com/go-playground/validator/v10"
|
||||
ut "github.com/go-playground/universal-translator"
|
||||
)
|
||||
|
||||
// Validator wraps the go-playground/validator with custom error handling
|
||||
type Validator struct {
|
||||
validate *validator.Validate
|
||||
translator ut.Translator
|
||||
}
|
||||
|
||||
// NewValidator creates a new validator instance with custom error handling
|
||||
func NewValidator() (*Validator, error) {
|
||||
// Create validator instance
|
||||
validate := validator.New()
|
||||
|
||||
// Register translations
|
||||
english := en.New()
|
||||
uni := ut.New(english, english)
|
||||
trans, _ := uni.GetTranslator("en")
|
||||
|
||||
// Register translation for default validator
|
||||
if err := en_translations.RegisterDefaultTranslations(validate, trans); err != nil {
|
||||
return nil, fmt.Errorf("failed to register translations: %w", err)
|
||||
}
|
||||
|
||||
// Register custom validations
|
||||
if err := registerCustomValidations(validate); err != nil {
|
||||
return nil, fmt.Errorf("failed to register custom validations: %w", err)
|
||||
}
|
||||
|
||||
return &Validator{
|
||||
validate: validate,
|
||||
translator: trans,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Validate validates a struct and returns validation errors
|
||||
func (v *Validator) Validate(structObj interface{}) error {
|
||||
if structObj == nil {
|
||||
return errors.New("validation: nil object provided")
|
||||
}
|
||||
|
||||
// Get the type of the struct
|
||||
structType := reflect.TypeOf(structObj)
|
||||
if structType.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("validation: expected struct, got %s", structType.Kind())
|
||||
}
|
||||
|
||||
// Perform validation
|
||||
if err := v.validate.Struct(structObj); err != nil {
|
||||
if _, ok := err.(*validator.InvalidValidationError); ok {
|
||||
return fmt.Errorf("validation: invalid validation error: %w", err)
|
||||
}
|
||||
|
||||
// Convert validation errors to custom format
|
||||
validationErrors := err.(validator.ValidationErrors)
|
||||
return v.formatValidationErrors(validationErrors)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// formatValidationErrors converts validator.ValidationErrors to a custom error type
|
||||
func (v *Validator) formatValidationErrors(errors validator.ValidationErrors) error {
|
||||
var errorMessages []string
|
||||
|
||||
for _, err := range errors {
|
||||
field := err.Field()
|
||||
tag := err.Tag()
|
||||
param := err.Param()
|
||||
|
||||
// Get custom error message
|
||||
message := fmt.Errorf("%s failed validation for '%s'", field, tag).Error()
|
||||
|
||||
// Add parameter if available
|
||||
if param != "" {
|
||||
message += fmt.Sprintf(" (parameter: %s)", param)
|
||||
}
|
||||
|
||||
errorMessages = append(errorMessages, message)
|
||||
}
|
||||
|
||||
return &ValidationError{
|
||||
Messages: errorMessages,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidationError represents multiple validation errors
|
||||
type ValidationError struct {
|
||||
Messages []string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return strings.Join(e.Messages, "; ")
|
||||
}
|
||||
|
||||
// registerCustomValidations registers any custom validation functions
|
||||
func registerCustomValidations(validate *validator.Validate) error {
|
||||
// Add custom validations here as needed
|
||||
// Example:
|
||||
// if err := validate.RegisterValidation("custom_tag", customValidationFunc); err != nil {
|
||||
// return err
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValidatorFromConfig creates a validator instance based on application config
|
||||
func GetValidatorFromConfig(cfg *config.Config) (*Validator, error) {
|
||||
// For now, config doesn't affect validator creation
|
||||
// But this allows future configuration (e.g., language, strict mode)
|
||||
return NewValidator()
|
||||
}
|
||||
Reference in New Issue
Block a user