validation

package
v1.1.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2025 License: MIT Imports: 7 Imported by: 0

README

Common Validation Framework

This package provides a reusable validation framework that extracts common validation patterns used throughout the Hitch codebase.

Overview

The validation framework consists of:

  1. CommonValidator - Configurable validator with reusable rules
  2. ValidationAdapter - Adapts validators to work with Hitch error system
  3. ValidationChain - Chains multiple validators for complex validation
  4. Pre-built Validators - Ready-to-use validators for common use cases

Key Features

  • Reusable Rules - Common validation rules (no injection, no traversal, etc.)
  • Type-safe Errors - Integrates with Hitch's error handling system
  • Chainable - Multiple validators can be chained together
  • Configurable - Easy to configure for different validation needs
  • Tested - Comprehensive test coverage

Usage Examples

Basic Validation
import "github.com/DoomedRamen/hitch/internal/validation"

// Create a branch name validator
validator := validation.BranchNameValidator()

// Validate a branch name
result := validator.Validate("feature/test-branch")
if !result.Valid {
    fmt.Printf("Validation failed: %s\n", result.Message)
}
Using Validation Adapter
import (
    "github.com/DoomedRamen/hitch/internal/validation"
    hitcherrors "github.com/DoomedRamen/hitch/internal/errors"
)

// Create an adapter that returns Hitch-specific errors
adapter := validation.BranchValidationAdapter()

// Validate and get Hitch error
err := adapter.ValidateWithErrors("invalid;rm -rf", "branch-name")
if err != nil {
    // Returns *hitcherrors.ValidationError
    fmt.Printf("Error: %v\n", err)
}
Validation Chains
// Chain multiple validators
chain := validation.NewValidationChain().
    Add(validation.BranchNameValidator(), validation.ErrorTypeBranch).
    Add(validation.EnvironmentNameValidator(), validation.ErrorTypeEnvironment)

// Validate with first-error semantics
err := chain.Validate("production", "env-name")

// Validate with all-errors semantics
err := chain.ValidateAll("invalid;rm -rf", "test-field")
Multiple Field Validation
adapter := validation.BranchValidationAdapter()

fields := map[string]string{
    "source": "feature/test",
    "target": "main;rm -rf", // This will fail
}

err := adapter.ValidateMultiple(fields)
if err != nil {
    // Returns combined error with all validation failures
    fmt.Printf("Validation errors: %v\n", err)
}
Custom Validators
// Create custom validation rules
customRule := validation.ValidationRule{
    Name:        "custom_rule",
    Description: "Custom validation logic",
    Validate: func(value string) error {
        if !strings.HasPrefix(value, "feature/") {
            return fmt.Errorf("must start with 'feature/'")
        }
        return nil
    },
}

// Create custom validator with configuration
config := validation.ValidatorConfig{
    MaxLength:      50,
    AllowedChars:   regexp.MustCompile(`^[a-z0-9/-]+$`),
    AllowEmpty:     false,
    TrimWhitespace: true,
}

validator := validation.NewCommonValidator(config,
    validation.NoPathTraversalRule(),
    validation.NoCommandInjectionRule(),
    customRule,
)

Pre-built Validators

BranchNameValidator

Validates git branch names:

  • Max 255 characters
  • No dangerous characters or injection patterns
  • Git-specific rules (no .lock, no reflog syntax, etc.)
validator := validation.BranchNameValidator()
EnvironmentNameValidator

Validates environment names:

  • Max 100 characters
  • Alphanumeric with hyphens and underscores
  • No path traversal
validator := validation.EnvironmentNameValidator()
CommitMessageValidator

Validates commit messages:

  • Max 2000 characters
  • No control characters or suspicious unicode
  • No command injection patterns
validator := validation.CommitMessageValidator()
RemoteNameValidator

Validates git remote names:

  • Max 255 characters
  • Alphanumeric with dots, hyphens, underscores
  • No suspicious patterns or URLs
validator := validation.RemoteNameValidator()

Common Validation Rules

Security Rules
// Prevents path traversal attacks
validation.NoPathTraversalRule()

// Prevents command injection
validation.NoCommandInjectionRule()

// Prevents control characters
validation.NoControlCharactersRule()

// Prevents suspicious unicode
validation.NoSuspiciousUnicodeRule()
Git-specific Rules
// Git branch name specific rules
validation.GitBranchNameRule()

// Environment name format rules
validation.EnvironmentNameRule()

Integration with Existing Code

To migrate existing validation code to use the common framework:

Before (Old Pattern)
// Repeated validation logic
if strings.Contains(branch, ";") {
    return fmt.Errorf("branch name contains unsafe character: ;")
}
if strings.Contains(branch, "..") {
    return fmt.Errorf("branch name contains path traversal sequences")
}
// ... more repeated logic
After (New Pattern)
// Use pre-built validator
adapter := validation.BranchValidationAdapter()
return adapter.ValidateWithErrors(branch, branch)

Benefits

  1. Reduced Code Duplication - Common patterns extracted and reused
  2. Consistent Security - All validators use the same security rules
  3. Type Safety - Proper error types and handling
  4. Testability - Each rule can be tested independently
  5. Maintainability - Changes to security rules apply everywhere
  6. Performance - Optimized regex patterns and validation logic

Migration Guide

  1. Identify validation patterns in existing code
  2. Choose appropriate pre-built validator or create custom one
  3. Replace manual validation with framework calls
  4. Test to ensure behavior is preserved
  5. Remove duplicate validation code

The framework is designed to be a drop-in replacement for most validation scenarios while providing better security and maintainability.

Documentation

Overview

Package validation provides input validation and pre-flight checks for Hitch operations.

Overview

This package implements validation logic that ensures operations are safe to execute before any Git modifications occur. It provides helper functions for checking repository state, validating user input, and generating helpful error messages.

Purpose

Validation serves as a critical safety layer:

  • Verify repository state before operations
  • Check for uncommitted changes
  • Validate branch names and environment names
  • Generate user-friendly error messages
  • Prevent operations that would fail or corrupt state

Core Functions

CheckUnstagedChanges:

func CheckUnstagedChanges(repo *git.Repo, operation, context string) error

Validates that the repository has no uncommitted changes before proceeding with an operation. This prevents data loss and ensures clean operation state.

Usage:

err := validation.CheckUnstagedChanges(repo, "promote", "feature/login to dev")
if err != nil {
    return err
}

If changes are detected, returns a detailed error with resolution steps.

FormatUnstagedChangesMessage:

func FormatUnstagedChangesMessage(operation, context string) string

Generates user-friendly error messages for unstaged changes, including:

  • Clear explanation of the problem
  • Step-by-step resolution instructions
  • Example commands to fix the issue
  • Context about what operation was blocked

Error Messages

The package generates helpful, actionable error messages:

🚫 SAFETY CHECK FAILED
Cannot promote with uncommitted changes.

Hitch refuses to operate on repositories with uncommitted changes to prevent data loss.

To proceed:
  1. Commit your changes:
     git add .
     git commit -m "Save work before hitch operation"

  2. Stash your changes:
     git stash push -m "Temporary stash before hitch operation"

After resolving changes, run: hitch promote feature/login to dev

✓ Repository state preserved

Unstaged Changes Detection

CheckUnstagedChanges performs comprehensive checks:

  • Modified files
  • New files (untracked)
  • Deleted files
  • Staged but uncommitted changes

Any of these conditions will block the operation.

Usage in Commands

All commands that modify repository state use validation:

Promote Command:

import "github.com/DoomedRamen/hitch/internal/validation"

func runPromote(cmd *cobra.Command, args []string) error {
    // Parse arguments
    branch, env := parsePromoteArgs(args)

    // Open repository
    repo, err := git.OpenRepo(".")
    if err != nil {
        return err
    }

    // VALIDATION: Check for unstaged changes
    err = validation.CheckUnstagedChanges(repo, "promote",
        fmt.Sprintf("%s to %s", branch, env))
    if err != nil {
        return err
    }

    // Safe to proceed with promote
    // ...
}

Rebuild Command:

err := validation.CheckUnstagedChanges(repo, "rebuild", environment)
if err != nil {
    return err
}

Release Command:

err := validation.CheckUnstagedChanges(repo, "release",
    fmt.Sprintf("%s to %s", environment, baseBranch))
if err != nil {
    return err
}

Command Helpers

The package also provides command-specific helper functions in command_helpers.go for parsing and validating command arguments.

Validation Flow

Typical validation flow in a command:

  1. Parse and validate arguments ├─ Check argument count ├─ Validate syntax (e.g., "to", "from" keywords) └─ Extract branch and environment names

  2. Open repository ├─ Verify .git directory exists ├─ Check repository is valid └─ Load repository state

  3. Check for unstaged changes ├─ Run git status equivalent ├─ Detect any modifications └─ Block if changes found

  4. Validate metadata exists ├─ Check hitch-metadata branch exists ├─ Verify hitch.json is valid └─ Load metadata

  5. Validate operation-specific requirements ├─ Check branch exists ├─ Verify environment exists ├─ Check for conflicts └─ Validate permissions

  6. Proceed with operation └─ All validations passed

Why Validation Matters

Without validation, operations could:

  • Overwrite uncommitted work (data loss)
  • Corrupt repository state (broken git state)
  • Leave repository in inconsistent state
  • Lose track of what changes belong to what operation

Validation prevents all these issues by catching problems early.

Error Message Design

Error messages follow these principles:

  1. Clear Problem Statement: Explain what's wrong
  2. Why It Matters: Explain why operation is blocked
  3. How to Fix: Provide step-by-step resolution
  4. Next Steps: Show exact command to run after fixing
  5. Reassurance: Confirm no data was lost

Example breakdown:

🚫 SAFETY CHECK FAILED                          [1. Problem]
Cannot promote with uncommitted changes.

Hitch refuses to operate on repositories        [2. Why]
with uncommitted changes to prevent data loss.

To proceed:                                      [3. How to Fix]
  1. Commit your changes:
     git add .
     git commit -m "..."

  2. Stash your changes:
     git stash push -m "..."

After resolving changes, run:                    [4. Next Steps]
  hitch promote feature/login to dev

✓ Repository state preserved                     [5. Reassurance]

Integration with Safety System

Validation is the first layer in Hitch's safety system:

Layer 1: Validation (this package)
   ↓ Check for unstaged changes
   ↓ Validate repository state
   ↓ Check basic requirements
   ↓
Layer 2: Safe Testing (safety package)
   ↓ Test in temporary branch
   ↓ Verify merge succeeds
   ↓ Run hooks
   ↓
Layer 3: Lock Management (metadata package)
   ↓ Acquire environment lock
   ↓ Prevent concurrent modifications
   ↓
Layer 4: Actual Operation
   ↓ Modify repository
   ↓ Update metadata
   ↓
Layer 5: Cleanup
   └─ Release locks
   └─ Cleanup temp branches

Custom Validators

Extend validation for custom needs:

func ValidateCustomRequirement(repo *git.Repo) error {
    // Custom validation logic
    if !customCheck(repo) {
        return fmt.Errorf("custom requirement not met")
    }
    return nil
}

// In command
if err := ValidateCustomRequirement(repo); err != nil {
    return err
}

Testing

Validation functions are easy to test:

func TestCheckUnstagedChanges(t *testing.T) {
    testRepo := testutil.NewTestRepo(t)
    defer testRepo.Cleanup()

    // Repository is clean - should pass
    err := validation.CheckUnstagedChanges(testRepo.Repo, "test", "context")
    if err != nil {
        t.Errorf("Clean repository should pass: %v", err)
    }

    // Add uncommitted change
    testRepo.WriteFile("test.txt", "content")

    // Should now fail
    err = validation.CheckUnstagedChanges(testRepo.Repo, "test", "context")
    if err == nil {
        t.Error("Should fail with uncommitted changes")
    }
}

Performance

Validation is fast:

  • Uses git library calls (no shell execution)
  • Minimal overhead (< 10ms typically)
  • Doesn't slow down operations noticeably

Best Practices

  1. Always validate before operations
  2. Provide clear error messages
  3. Include resolution steps
  4. Don't swallow validation errors
  5. Test validation logic thoroughly
  6. Keep validation fast

Future Enhancements

Potential additions:

  • Validate branch naming conventions
  • Check for merge conflicts before operation
  • Verify environment configuration
  • Validate user permissions
  • Check disk space availability

Example: Complete Validation Pattern

func SafeOperation(repo *git.Repo, branch, env string) error {
    // 1. Validate repository state
    err := validation.CheckUnstagedChanges(repo, "promote",
        fmt.Sprintf("%s to %s", branch, env))
    if err != nil {
        return err
    }

    // 2. Validate branch exists
    if !repo.BranchExists(branch) {
        return fmt.Errorf("branch %s not found", branch)
    }

    // 3. Validate metadata
    reader := metadata.NewReader(repo)
    if !reader.Exists() {
        return errors.New("hitch not initialized")
    }

    meta, err := reader.Read()
    if err != nil {
        return fmt.Errorf("failed to read metadata: %w", err)
    }

    // 4. Validate environment exists
    if _, exists := meta.Environments[env]; !exists {
        return fmt.Errorf("environment %s not found", env)
    }

    // All validations passed - safe to proceed
    return performOperation(repo, meta, branch, env)
}

This package ensures that Hitch operations are safe, predictable, and user-friendly by catching issues early and providing clear guidance for resolution.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CheckBranchAlreadyInEnvironment

func CheckBranchAlreadyInEnvironment(env metadata.Environment, branchName, envName string) error

CheckBranchAlreadyInEnvironment checks if branch is already in the target environment

func CheckUnstagedChanges

func CheckUnstagedChanges(repo *hitchgit.Repo, operation, context string) error

CheckUnstagedChanges validates that the repository has no uncommitted changes

func FormatBranchNotFoundError

func FormatBranchNotFoundError(branchName string) string

FormatBranchNotFoundError generates the error message for missing branches

func FormatEnvironmentNotFoundError

func FormatEnvironmentNotFoundError(envName string, environments map[string]metadata.Environment) string

FormatEnvironmentNotFoundError generates the error message for missing environments

func FormatRepositoryValidationError

func FormatRepositoryValidationError(validationResult hitchgit.ValidationResult, operation string) string

FormatRepositoryValidationError generates the error message for repository validation failures

func FormatUnstagedChangesMessage

func FormatUnstagedChangesMessage(operation, context string) string

FormatUnstagedChangesMessage generates the error message for unstaged changes

func InitializeRepositoryAndMetadata

func InitializeRepositoryAndMetadata() (*hitchgit.Repo, *metadata.Metadata, error)

InitializeRepositoryAndMetadata performs common repository initialization steps

func SetupBranchRestoration

func SetupBranchRestoration(repo *hitchgit.Repo) string

SetupBranchRestoration saves current branch for later restoration

func ValidateBranchExists

func ValidateBranchExists(repo *hitchgit.Repo, branchName string) error

ValidateBranchExists validates that a branch exists in the repository

func ValidateEnvironmentExists

func ValidateEnvironmentExists(meta *metadata.Metadata, envName string) (metadata.Environment, error)

ValidateEnvironmentExists validates that an environment exists in metadata

func ValidateGitUserInfo

func ValidateGitUserInfo(repo *hitchgit.Repo) (string, string, error)

ValidateGitUserInfo validates that git user information is configured

func ValidateRepositoryState

func ValidateRepositoryState(repo *hitchgit.Repo, operation string) error

ValidateRepositoryState performs comprehensive repository validation

Types

type CommonValidator added in v1.1.8

type CommonValidator struct {
	// contains filtered or unexported fields
}

CommonValidator provides reusable validation patterns

func BranchNameValidator added in v1.1.8

func BranchNameValidator() *CommonValidator

BranchNameValidator creates a validator for git branch names

func CommitMessageValidator added in v1.1.8

func CommitMessageValidator() *CommonValidator

CommitMessageValidator creates a validator for commit messages

func EnvironmentNameValidator added in v1.1.8

func EnvironmentNameValidator() *CommonValidator

EnvironmentNameValidator creates a validator for environment names

func NewCommonValidator added in v1.1.8

func NewCommonValidator(config ValidatorConfig, rules ...ValidationRule) *CommonValidator

NewCommonValidator creates a new common validator with the given rules

func RemoteNameValidator added in v1.1.8

func RemoteNameValidator() *CommonValidator

RemoteNameValidator creates a validator for git remote names

func (*CommonValidator) Validate added in v1.1.8

func (cv *CommonValidator) Validate(value string) ValidationResult

Validate performs validation on the given value

type ErrorType added in v1.1.8

type ErrorType int

ErrorType represents the type of validation error to create

const (
	// ErrorTypeBranch creates branch validation errors
	ErrorTypeBranch ErrorType = iota
	// ErrorTypeEnvironment creates environment validation errors
	ErrorTypeEnvironment
	// ErrorTypeCommit creates commit validation errors
	ErrorTypeCommit
	// ErrorTypeRemote creates remote validation errors
	ErrorTypeRemote
	// ErrorTypeGeneric creates generic validation errors
	ErrorTypeGeneric
)

type ValidationAdapter added in v1.1.8

type ValidationAdapter struct {
	// contains filtered or unexported fields
}

ValidationAdapter adapts the common validation framework to work with the Hitch error system

func BranchValidationAdapter added in v1.1.8

func BranchValidationAdapter() *ValidationAdapter

BranchValidationAdapter creates a pre-configured branch validation adapter

func CommitValidationAdapter added in v1.1.8

func CommitValidationAdapter() *ValidationAdapter

CommitValidationAdapter creates a pre-configured commit message validation adapter

func EnvironmentValidationAdapter added in v1.1.8

func EnvironmentValidationAdapter() *ValidationAdapter

EnvironmentValidationAdapter creates a pre-configured environment validation adapter

func NewValidationAdapter added in v1.1.8

func NewValidationAdapter(validator *CommonValidator, errorType ErrorType) *ValidationAdapter

NewValidationAdapter creates a new validation adapter

func RemoteValidationAdapter added in v1.1.8

func RemoteValidationAdapter() *ValidationAdapter

RemoteValidationAdapter creates a pre-configured remote name validation adapter

func (*ValidationAdapter) ValidateMultiple added in v1.1.8

func (va *ValidationAdapter) ValidateMultiple(fields map[string]string) error

ValidateMultiple validates multiple fields and combines errors

func (*ValidationAdapter) ValidateWithErrors added in v1.1.8

func (va *ValidationAdapter) ValidateWithErrors(value string, fieldName string) error

ValidateWithErrors performs validation and returns Hitch-specific errors

type ValidationChain added in v1.1.8

type ValidationChain struct {
	// contains filtered or unexported fields
}

ValidationChain allows chaining multiple validators for a single field

func NewValidationChain added in v1.1.8

func NewValidationChain() *ValidationChain

NewValidationChain creates a new validation chain

func (*ValidationChain) Add added in v1.1.8

func (vc *ValidationChain) Add(validator *CommonValidator, errorType ErrorType) *ValidationChain

Add adds a validator to the chain

func (*ValidationChain) Validate added in v1.1.8

func (vc *ValidationChain) Validate(value string, fieldName string) error

Validate runs all validators in the chain and returns the first error

func (*ValidationChain) ValidateAll added in v1.1.8

func (vc *ValidationChain) ValidateAll(value string, fieldName string) error

ValidateAll runs all validators in the chain and returns all errors

type ValidationResult added in v1.1.8

type ValidationResult struct {
	Valid   bool
	Errors  []string
	Message string
}

ValidationResult represents the result of validation

type ValidationRule added in v1.1.8

type ValidationRule struct {
	Name        string
	Description string
	Validate    func(value string) error
}

ValidationRule represents a single validation rule

func EnvironmentNameRule added in v1.1.8

func EnvironmentNameRule() ValidationRule

EnvironmentNameRule implements environment name specific validation

func GitBranchNameRule added in v1.1.8

func GitBranchNameRule() ValidationRule

GitBranchNameRule implements git branch name specific validation

func NoCommandInjectionRule added in v1.1.8

func NoCommandInjectionRule() ValidationRule

NoCommandInjectionRule prevents command injection attacks

func NoControlCharactersRule added in v1.1.8

func NoControlCharactersRule() ValidationRule

NoControlCharactersRule prevents control characters

func NoPathTraversalRule added in v1.1.8

func NoPathTraversalRule() ValidationRule

NoPathTraversalRule prevents path traversal attacks

func NoSuspiciousUnicodeRule added in v1.1.8

func NoSuspiciousUnicodeRule() ValidationRule

NoSuspiciousUnicodeRule prevents suspicious unicode characters

type ValidatorConfig added in v1.1.8

type ValidatorConfig struct {
	MaxLength         int
	MinLength         int
	AllowedChars      *regexp.Regexp
	ForbiddenChars    []string
	ForbiddenPatterns []string
	AllowEmpty        bool
	TrimWhitespace    bool
}

ValidatorConfig contains configuration for validation

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL