go-create-validator
Go Create Validator
Generate validator files for GO modular architecture conventions.
Two-File Pattern
Every validator requires two files:
- Port interface:
internal/modules/<module>/ports/<validator_name>_validator.go - Validator implementation:
internal/modules/<module>/validator/<validator_name>_validator.go
Port File Structure
The port file contains only the interface definition with its documentation comment.
Example structure:
package ports
// PasswordValidator validates password strength according to security policies.
type PasswordValidator interface {
Validate(password string) error
}
Validator File Structure
The validator implementation file follows this order:
- Package imports
- Constants - validation rules, thresholds, limits
- Struct definition - the validator implementation struct
- Interface assertion - compile-time check with
var _ ports.XxxValidator = (*XxxValidator)(nil) - Constructor -
NewXxxValidatorfunction - Methods - validation methods (e.g.,
Validate)
Example structure:
package validator
import (
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/errs"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/ports"
)
// 1. Constants
const (
minLength = 8
maxLength = 128
)
// 2. Struct definition
type PasswordValidator struct{}
// 3. Interface assertion
var _ ports.PasswordValidator = (*PasswordValidator)(nil)
// 4. Constructor
func NewPasswordValidator() *PasswordValidator {
return &PasswordValidator{}
}
// 5. Methods
func (v *PasswordValidator) Validate(password string) error {
// validation logic
return nil
}
Port Interface Structure
Location: internal/modules/<module>/ports/<validator_name>_validator.go
package ports
// PasswordValidator validates password strength according to security policies.
type PasswordValidator interface {
Validate(password string) error
}
Validator Variants
Stateless validator (no dependencies)
Most validators are stateless utilities with no external dependencies.
type EmailValidator struct{}
func NewEmailValidator() *EmailValidator {
return &EmailValidator{}
}
func (v *EmailValidator) Validate(email string) error {
// Validation logic
return nil
}
Stateful validator (with dependencies)
Use when validation requires external data or configuration.
type UsernameValidator struct {
userRepo ports.UserRepository
minLen int
maxLen int
}
func NewUsernameValidator(
userRepo ports.UserRepository,
minLen int,
maxLen int,
) *UsernameValidator {
return &UsernameValidator{
userRepo: userRepo,
minLen: minLen,
maxLen: maxLen,
}
}
func (v *UsernameValidator) Validate(ctx context.Context, username string) error {
if len(username) < v.minLen {
return errs.ErrUsernameTooShort
}
// Check uniqueness using repository
exists, err := v.userRepo.ExistsByUsername(ctx, username)
if err != nil {
return err
}
if exists {
return errs.ErrUsernameAlreadyExists
}
return nil
}
Multi-field validator
Use when validation involves multiple related fields.
Port interface:
type RegistrationValidator interface {
ValidateEmail(email string) error
ValidatePassword(password string) error
ValidatePasswordMatch(password, confirmPassword string) error
}
Implementation:
type RegistrationValidator struct{}
func NewRegistrationValidator() *RegistrationValidator {
return &RegistrationValidator{}
}
func (v *RegistrationValidator) ValidateEmail(email string) error {
// Email validation logic
return nil
}
func (v *RegistrationValidator) ValidatePassword(password string) error {
// Password validation logic
return nil
}
func (v *RegistrationValidator) ValidatePasswordMatch(password, confirmPassword string) error {
if password != confirmPassword {
return errs.ErrPasswordMismatch
}
return nil
}
Validation Constants
Define validation rules as constants at the package level for clarity and maintainability.
const (
minPasswordLength = 8
maxPasswordLength = 128
minUsernameLength = 3
maxUsernameLength = 32
)
Error Handling
Validators MUST return typed domain errors from the module's errs package.
When adding new custom errors, translations are mandatory in locale files.
// In internal/modules/<module>/errs/errs.go
var (
ErrPasswordTooShort = errors.New("password must be at least 8 characters")
ErrPasswordMissingUppercase = errors.New("password must contain at least one uppercase letter")
ErrPasswordMissingLowercase = errors.New("password must contain at least one lowercase letter")
ErrPasswordMissingDigit = errors.New("password must contain at least one digit")
ErrPasswordMissingSpecial = errors.New("password must contain at least one special character")
)
For every new custom error added to internal/modules/<module>/errs/errs.go:
- Add the translation key to
locales/en.json - Add the same translation key to every other existing locale file (e.g.,
locales/pt_BR.json)
Context Usage
Validators that perform I/O operations (database lookups, API calls) MUST accept context.Context as the first parameter.
// Stateless validator - no context needed
func (v *PasswordValidator) Validate(password string) error
// Stateful validator with I/O - context required
func (v *UsernameValidator) Validate(ctx context.Context, username string) error
Naming
- Port interface:
XxxValidator(inportspackage) - Implementation struct:
XxxValidator(invalidatorpackage, same name — disambiguated by package) - Constructor:
NewXxxValidator, returns a pointer of the struct implementation - Validation method:
Validatefor single-purpose validators, or descriptive names for multi-purpose validators
Fx Wiring
Add to internal/modules/<module>/fx.go:
fx.Provide(
fx.Annotate(
validator.NewPasswordValidator,
fx.As(new(ports.PasswordValidator)),
),
),
Dependencies
Validators depend on interfaces only. Common dependencies:
ports.XxxRepository— for uniqueness checks or data lookupsports.XxxService— for external validation services- Configuration values — passed as constructor parameters
Testing
Validators MUST have comprehensive unit tests covering:
- Valid input passes validation
- Each invalid condition returns the correct error
- Edge cases (empty strings, boundary values, special characters)
Test file location: internal/modules/<module>/validator/<validator_name>_validator_test.go
package validator_test
import (
"testing"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/errs"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/validator"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPasswordValidator_ValidPassword_Passes(t *testing.T) {
// Arrange
v := validator.NewPasswordValidator()
// Act
err := v.Validate("SecureP@ssw0rd")
// Assert
require.NoError(t, err)
}
func TestPasswordValidator_TooShort_ReturnsError(t *testing.T) {
// Arrange
v := validator.NewPasswordValidator()
// Act
err := v.Validate("Ab1!")
// Assert
require.Error(t, err)
assert.ErrorIs(t, err, errs.ErrPasswordTooShort)
}
Critical Rules
- Two files: Port interface in
ports/, implementation invalidator/ - Interface in ports: Interface lives in
ports/<name>_validator.go - Interface assertion: Add
var _ ports.XxxValidator = (*XxxValidator)(nil)below the struct - Constructor: MUST return pointer
*XxxValidator - Stateless by default: Only add dependencies when validation requires external data
- Context when needed: Accept
context.Contextonly for validators performing I/O - Typed errors: Return domain errors from module's
errspackage - Error translations: Every new custom error must have entries in
locales/en.jsonand all other existing locale files - Constants: Define validation rules as package-level constants
- No comments on implementations: Do not add redundant comments above methods in the implementations
- Add detailed comment on interfaces: Provide comprehensive comments on the port interfaces to describe their purpose and validation rules
- Comprehensive tests: Test valid cases and all invalid conditions
Workflow
- Create port interface in
ports/<name>_validator.go - Create validator implementation in
validator/<name>_validator.go - Define validation constants
- Add typed errors to module's
errs/errs.goif needed - Add translations for each new custom error in
locales/en.jsonand all other existing locale files - Create comprehensive unit tests in
validator/<name>_validator_test.go - Add Fx wiring to module's
fx.go - Run
make testto verify tests pass - Run
make lintto verify code quality - Run
make nilawayfor static analysis