validator

package
v0.0.0-...-622fb6d Latest Latest
Warning

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

Go to latest
Published: Mar 2, 2026 License: MIT Imports: 5 Imported by: 0

README

State Machine Validator

The validator package provides comprehensive validation for state machine configurations with detailed error messages, warnings, and automatic fix suggestions.

Features

  • Comprehensive validation - Multiple rule types covering structural, semantic, and best practice issues
  • Detailed error messages - Clear descriptions of what's wrong and where
  • Auto-fix suggestions - Automatic fixes for common issues
  • Custom rules - Extensible rule system for domain-specific validation
  • Build-time checking - Catch errors before runtime
  • CI/CD integration - Easy to integrate into automated workflows

Installation

import "github.com/amp-labs/server/builder-mcp/statemachine/validator"

Quick Start

Basic Validation
// Validate a config object
result := validator.Validate(config)

if !result.Valid {
    fmt.Println(result.String())
    // Prints formatted errors and suggestions
}
Validate from File
result, err := validator.ValidateFile("workflow.yaml")
if err != nil {
    log.Fatal("Failed to load config:", err)
}

if result.HasErrors() {
    fmt.Println("Validation failed:")
    for _, err := range result.Errors {
        fmt.Printf("  [%s] %s\n", err.Code, err.Message)
        if err.Fix != nil {
            fmt.Printf("    Fix: %s\n", err.Fix.Description)
        }
    }
}

Validation Rules

Built-in Rules

The validator includes several built-in rules:

1. Unreachable State Rule

Detects states that cannot be reached from the initial state.

# ❌ Invalid - "orphan" state is unreachable
initialState: start
states:
  - name: start
    type: action
  - name: orphan    # Cannot be reached!
    type: action
  - name: end
    type: final
transitions:
  - from: start
    to: end

# ✓ Fixed
transitions:
  - from: start
    to: orphan
  - from: orphan
    to: end

Error Code: UNREACHABLE_STATE

Fix: Add a transition to the state or remove it

2. Missing Transition Rule

Detects non-final states without outgoing transitions.

# ❌ Invalid - "dead_end" has no outgoing transition
states:
  - name: start
    type: action
  - name: dead_end
    type: action    # Not final but no way out!
  - name: end
    type: final
transitions:
  - from: start
    to: dead_end
  # Missing transition from dead_end

# ✓ Fixed
transitions:
  - from: start
    to: dead_end
  - from: dead_end
    to: end

Error Code: MISSING_TRANSITION

Fix: Add a transition or mark as final state

3. Duplicate Transition Rule

Detects duplicate transitions with the same from/to/condition.

# ❌ Invalid - duplicate transitions
transitions:
  - from: start
    to: end
    condition: always
  - from: start
    to: end
    condition: always  # Duplicate!

# ✓ Fixed
transitions:
  - from: start
    to: end
    condition: always

Error Code: DUPLICATE_TRANSITION

Fix: Remove duplicate transition

4. Naming Convention Rule

Warns about non-snake_case state names.

# ⚠ Warning - non-standard naming
states:
  - name: StartState    # Should be start_state
    type: action
  - name: ProcessData   # Should be process_data
    type: action

# ✓ Fixed
states:
  - name: start_state
    type: action
  - name: process_data
    type: action

Error Code: NAMING_CONVENTION

Fix: Rename to snake_case

5. Cyclic Transition Rule

Detects potential infinite loops without exit conditions.

# ⚠ Warning - potential infinite loop
states:
  - name: retry
    type: action
transitions:
  - from: retry
    to: retry
    condition: always  # Loops forever!

# ✓ Fixed - loop has exit condition
transitions:
  - from: retry
    to: retry
    condition: attempts < 3
  - from: retry
    to: complete
    condition: attempts >= 3

Error Code: POTENTIAL_INFINITE_LOOP

Fix: Ensure cycle has path to final state

Auto-Fixes

The validator can automatically fix many common issues:

Apply Fixes Manually
result := validator.Validate(config)

// Collect all auto-fixes
var fixes []*validator.Fix
for _, err := range result.Errors {
    if err.Fix != nil {
        fixes = append(fixes, err.Fix)
    }
}

// Apply fixes
if err := validator.ApplyFixes(config, fixes); err != nil {
    log.Fatal("Failed to apply fixes:", err)
}

// Re-validate
result = validator.Validate(config)
Available Fix Functions
// Add missing transition
fix := validator.AddMissingTransition("state_a", "state_b")

// Remove unreachable state
fix := validator.RemoveUnreachableState("orphan_state")

// Rename state to follow conventions
fix := validator.RenameState("OldName", "new_name")

// Mark state as final
fix := validator.MarkAsFinalState("end_state")

// Remove duplicate transition
fix := validator.RemoveDuplicateTransition("from", "to", "condition")

Custom Validation Rules

Create custom rules for domain-specific validation:

Define a Custom Rule
type myCustomRule struct{}

func (r *myCustomRule) Name() string {
    return "MyCustomRule"
}

func (r *myCustomRule) Check(config *statemachine.Config) []validator.ValidationError {
    var errors []validator.ValidationError

    // Your validation logic
    for _, state := range config.States {
        if !isValid(state) {
            errors = append(errors, validator.ValidationError{
                Code:    "CUSTOM_ERROR",
                Message: fmt.Sprintf("State %s violates custom rule", state.Name),
                Location: validator.Location{State: state.Name},
            })
        }
    }

    return errors
}
Register and Use Custom Rule
// Register custom rule
validator.RegisterRule(&myCustomRule{})

// Validate with all rules (including custom)
rules := append(validator.DefaultRules(), validator.RegisteredRules...)
result := validator.ValidateWithRules(config, rules)

Validation Result

The ValidationResult struct provides comprehensive information:

type ValidationResult struct {
    Valid       bool                    // Overall validation status
    Errors      []ValidationError       // Critical errors
    Warnings    []ValidationWarning     // Non-critical warnings
    Suggestions []Suggestion            // Improvement suggestions
}
Check Results
result := validator.Validate(config)

// Quick checks
if result.HasErrors() {
    // Handle errors
}

if result.HasWarnings() {
    // Handle warnings
}

// Detailed inspection
for _, err := range result.Errors {
    fmt.Printf("Error [%s]: %s\n", err.Code, err.Message)
    fmt.Printf("  Location: %s\n", err.Location.State)
    if err.Fix != nil {
        fmt.Printf("  Fix: %s\n", err.Fix.Description)
    }
}

// Suggestions
for _, sug := range result.Suggestions {
    fmt.Printf("💡 %s\n", sug.Message)
    if sug.Example != "" {
        fmt.Printf("Example:\n%s\n", sug.Example)
    }
}
Formatted Output
// Print human-readable summary
fmt.Println(result.String())

// Output:
// ✗ Configuration has 2 error(s)
//   [UNREACHABLE_STATE] State 'orphan' cannot be reached from initial state 'start'
//     Fix: Add a transition to 'orphan' or remove the state
//   [MISSING_TRANSITION] Non-final state 'dead_end' has no outgoing transitions
//     Fix: Add a transition or mark as final state
//
// ⚠ 1 warning(s):
//   [NAMING_CONVENTION] State 'ProcessData' should use snake_case naming
//
// 💡 2 suggestion(s) for improvement

Integration Examples

In Tests
func TestWorkflowConfig(t *testing.T) {
    config := loadTestConfig()

    result := validator.Validate(config)
    require.True(t, result.Valid, "Config should be valid:\n%s", result.String())
}
In CI/CD
func main() {
    result, err := validator.ValidateFile(os.Args[1])
    if err != nil {
        log.Fatal(err)
    }

    if !result.Valid {
        fmt.Println(result.String())
        os.Exit(1)
    }

    fmt.Println("✓ Configuration is valid")
}
Pre-commit Hook
#!/bin/bash
# .git/hooks/pre-commit

for config in statemachine/configs/*.yaml; do
    if ! statemachine-cli validate "$config"; then
        echo "Validation failed for $config"
        exit 1
    fi
done
GitHub Actions
name: Validate Configs

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
      - name: Validate State Machine Configs
        run: |
          go run ./scripts/statemachine-cli validate configs/*.yaml

Advanced Usage

Strict Mode

Treat warnings as errors:

result := validator.Validate(config)

if result.HasWarnings() {
    // In strict mode, warnings fail the build
    log.Fatal("Strict mode: warnings present")
}
Incremental Validation

Validate specific aspects:

// Only check reachability
rule := &validator.unreachableStateRule{}
errors := rule.Check(config)

// Only check naming
rule := &validator.namingConventionRule{}
errors := rule.Check(config)
Validation Reports

Generate detailed reports:

result := validator.Validate(config)

report := generateReport(result)
saveToFile("validation-report.html", report)

Best Practices

  1. Validate early - Run validation during development, not just in CI/CD
  2. Fix errors first - Address critical errors before warnings
  3. Use auto-fixes cautiously - Review auto-fixes before applying
  4. Add custom rules - Encode domain knowledge in custom validation rules
  5. Document exceptions - If you must violate a rule, document why
  6. Version control configs - Track validation results over time
  7. Integrate with editor - Use CLI tool for real-time feedback

Troubleshooting

False Positives

If a rule reports false positives:

// Create custom rules list without problematic rule
rules := []validator.Rule{
    &validator.unreachableStateRule{},
    &validator.missingTransitionRule{},
    // Exclude: &validator.cyclicTransitionRule{},
}

result := validator.ValidateWithRules(config, rules)
Performance

For large configs:

// Validate only critical rules
rules := []validator.Rule{
    &validator.unreachableStateRule{},
    &validator.missingTransitionRule{},
}

result := validator.ValidateWithRules(config, rules)

Error Codes Reference

Code Severity Description Auto-Fix
UNREACHABLE_STATE Error State cannot be reached from initial state Yes
MISSING_TRANSITION Error Non-final state has no outgoing transitions Yes
DUPLICATE_TRANSITION Error Duplicate transition exists Yes
NAMING_CONVENTION Error State name violates naming convention Yes
POTENTIAL_INFINITE_LOOP Warning Cycle detected without clear exit No
CONFIG_LOAD_FAILED Error Failed to load config file No

See Also

Documentation

Overview

Package validator provides validation and auto-fixing for state machine configurations.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrTransitionExists is returned when attempting to add a transition that already exists.
	ErrTransitionExists = errors.New("transition already exists")
	// ErrStateNotFound is returned when attempting to remove a state that doesn't exist.
	ErrStateNotFound = errors.New("state not found")
	// ErrDuplicateNotFound is returned when attempting to remove a duplicate that doesn't exist.
	ErrDuplicateNotFound = errors.New("duplicate not found")
	// ErrStateAlreadyExists is returned when attempting to rename to an existing state name.
	ErrStateAlreadyExists = errors.New("state already exists")
	// ErrAlreadyFinalState is returned when attempting to mark a state as final that already is.
	ErrAlreadyFinalState = errors.New("already a final state")
)
View Source
var RegisteredRules []Rule

RegisteredRules stores custom validation rules.

Functions

func ApplyFixes

func ApplyFixes(config *statemachine.Config, fixes []*Fix) error

ApplyFixes applies a list of fixes to a config.

func RegisterRule

func RegisterRule(rule Rule)

RegisterRule adds a custom validation rule.

Types

type Fix

type Fix struct {
	Description string
	Apply       func(config *statemachine.Config) error
}

Fix represents an automatic fix for a validation error.

func AddMissingTransition

func AddMissingTransition(from, to string) *Fix

AddMissingTransition creates a fix that adds a transition between states.

func MarkAsFinalState

func MarkAsFinalState(stateName string) *Fix

MarkAsFinalState creates a fix that marks a state as final.

func RemoveDuplicateTransition

func RemoveDuplicateTransition(from, to, condition string) *Fix

RemoveDuplicateTransition creates a fix that removes a duplicate transition.

func RemoveUnreachableState

func RemoveUnreachableState(stateName string) *Fix

RemoveUnreachableState creates a fix that removes an unreachable state.

func RenameState

func RenameState(oldName, newName string) *Fix

RenameState creates a fix that renames a state.

type Location

type Location struct {
	File   string // Config file path
	Line   int    // Line number (0 if unknown)
	Column int    // Column number (0 if unknown)
	State  string // State name if applicable
}

Location identifies where an issue occurred.

type Rule

type Rule interface {
	Name() string
	Severity() Severity
	Check(config *statemachine.Config) RuleResult
}

Rule defines a validation rule that can check a config for specific issues.

func DefaultRules

func DefaultRules() []Rule

DefaultRules returns the standard set of validation rules.

type RuleResult

type RuleResult struct {
	Errors   []ValidationError
	Warnings []ValidationWarning
}

RuleResult contains both errors and warnings from a rule check.

type Severity

type Severity int

Severity defines the severity level of a validation issue.

const (
	SeverityError Severity = iota
	SeverityWarning
	SeverityInfo
)

type Suggestion

type Suggestion struct {
	Message string // Suggestion description
	Example string // Code example showing the improvement
}

Suggestion provides improvement recommendations.

type ValidationError

type ValidationError struct {
	Code     string   // Error code like "UNREACHABLE_STATE", "MISSING_TRANSITION"
	Message  string   // Human-readable error message
	Location Location // Where the error occurred
	Fix      *Fix     // Optional auto-fix suggestion
}

ValidationError represents a validation error with fix suggestions.

func ValidateOTELInstrumentation

func ValidateOTELInstrumentation(config *statemachine.Config) []ValidationError

ValidateOTELInstrumentation checks that spans are created correctly. This validator ensures that the state machine is properly instrumented for OpenTelemetry tracing.

type ValidationResult

type ValidationResult struct {
	Valid       bool
	Errors      []ValidationError
	Warnings    []ValidationWarning
	Suggestions []Suggestion
}

ValidationResult contains the results of validating a state machine config.

func Validate

func Validate(config *statemachine.Config) ValidationResult

Validate performs comprehensive validation on a state machine config.

func ValidateFile

func ValidateFile(path string) (ValidationResult, error)

ValidateFile loads a config from a file and validates it.

func ValidateFileStrict

func ValidateFileStrict(path string) (ValidationResult, error)

ValidateFileStrict loads a config from a file and validates it in strict mode.

func ValidateFileWithOptions

func ValidateFileWithOptions(path string, strict bool) (ValidationResult, error)

ValidateFileWithOptions loads a config from a file and validates it with options.

func ValidateWithRules

func ValidateWithRules(config *statemachine.Config, rules []Rule) ValidationResult

ValidateWithRules validates using custom rules.

func ValidateWithRulesStrict

func ValidateWithRulesStrict(config *statemachine.Config, rules []Rule) ValidationResult

ValidateWithRulesStrict validates with strict mode (treats warnings as errors).

func (ValidationResult) HasErrors

func (r ValidationResult) HasErrors() bool

HasErrors returns true if the result has any errors.

func (ValidationResult) HasWarnings

func (r ValidationResult) HasWarnings() bool

HasWarnings returns true if the result has any warnings.

func (ValidationResult) String

func (r ValidationResult) String() string

String returns a human-readable summary of validation results.

type ValidationWarning

type ValidationWarning struct {
	Code     string   // Warning code
	Message  string   // Human-readable warning message
	Location Location // Where the warning occurred
}

ValidationWarning represents a non-critical issue.

func ValidateOTELContextMetadata

func ValidateOTELContextMetadata(ctx *statemachine.Context) []ValidationWarning

ValidateOTELContextMetadata validates that the Context has required metadata for span attributes. This is a runtime validation that should be called after Context is created. Returns warnings, not errors, since missing metadata doesn't prevent execution.

func ValidateOTELDebugMode

func ValidateOTELDebugMode() []ValidationWarning

ValidateOTELDebugMode validates that debug mode is properly configured.

Jump to

Keyboard shortcuts

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