yamlvalidator

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Dec 24, 2025 License: Apache-2.0 Imports: 10 Imported by: 0

README

yamlvalidator

A flexible, production-ready YAML validation library for Go with support for:

  • Type checking with YAML 1.2 (and optional YAML 1.1) compliance
  • Custom validators for values and keys
  • Conditional logic (AnyOf, ExactlyOneOf, MutuallyExclusive, Conditions)
  • Detailed error reporting with source context and precise positions
  • Multi-document YAML support
  • Anchor/alias support
  • Unicode and tab handling in error output

Installation

go get github.com/Yakwilik/go-yamlvalidator

Quick Start

package main

import (
    "fmt"
    "regexp"

    v "github.com/Yakwilik/go-yamlvalidator"
    valv "github.com/Yakwilik/go-yamlvalidator/pkg/valuevalidator"
)

func main() {
    schema := &v.FieldSchema{
        Type: v.TypeMap,
        AllowedKeys: map[string]*v.FieldSchema{
            "name": {
                Type:     v.TypeString,
                Required: true,
                Validators: []v.ValueValidator{
                    valv.RegexValidator{
                        Pattern: regexp.MustCompile(`^[a-z][a-z0-9-]*$`),
                        Message: "must be lowercase with dashes",
                    },
                },
            },
            "replicas": {
                Type: v.TypeInt,
                Validators: []v.ValueValidator{
                    valv.RangeValidator{Min: v.PtrFloat(1), Max: v.PtrFloat(100)},
                },
            },
        },
    }

    yaml := []byte(`
name: my-app
replicas: 50
`)

    validator := v.NewValidator(schema)
    result := validator.ValidateBytes(yaml)

    if result.HasErrors() {
        fmt.Println(result.FormatAll(true))
    }
}

Schema Definition

Field Schema
type FieldSchema struct {
    // Basic properties
    Type        NodeType    // Expected type (TypeString, TypeInt, etc.)
    Required    bool        // Field must be present
    Nullable    bool        // Allow null values
    Deprecated  string      // Deprecation message (empty = not deprecated)
    Default     interface{} // Default value (warning if missing)

    // Map-specific
    AllowedKeys          map[string]*FieldSchema // Known keys
    AdditionalProperties *FieldSchema            // Schema for unknown keys
    UnknownKeyPolicy     UnknownKeyPolicy        // How to handle unknown keys
    KeyValidators        []KeyValidator          // Key name validators

    // Sequence-specific
    ItemSchema *FieldSchema // Schema for items
    MinItems   *int
    MaxItems   *int

    // Value validators
    Validators []ValueValidator

    // Inter-field logic
    AnyOf             [][]string        // At least one group must be present
    ExactlyOneOf      []string          // Exactly one field must be present
    MutuallyExclusive []string          // At most one field can be present
    Conditions        []ConditionalRule // Conditional validation
}
Node Types
Type Description
TypeAny Any type (no validation)
TypeNull Null values only
TypeString String values
TypeInt Integer values
TypeFloat Float values (also accepts int)
TypeBool Boolean values
TypeMap Mapping nodes
TypeSequence Sequence/array nodes
Unknown Key Policy
Policy Behavior
UnknownKeyInherit Uses ctx.StrictKeys (default)
UnknownKeyError Unknown keys are errors
UnknownKeyWarn Unknown keys are warnings
UnknownKeyIgnore Unknown keys are ignored

Built-in Validators

Value Validators
// Enum validation
EnumValidator{Allowed: []string{"v1", "v2", "v3"}}

// Regex validation
RegexValidator{
    Pattern: regexp.MustCompile(`^[a-z]+$`),
    Message: "must be lowercase letters",
}

// Numeric range
RangeValidator{Min: v.PtrFloat(1), Max: v.PtrFloat(100)}

// Non-empty check
NonEmptyValidator{}

// Length validation
LengthValidator{Min: v.PtrInt(1), Max: v.PtrInt(255)}

// URL validation
URLValidator{RequireScheme: true, AllowedSchemes: []string{"http", "https"}}
Key Validators
// Regex key validation
RegexKeyValidator{
    Pattern: regexp.MustCompile(`^[a-z][a-z0-9._-]*$`),
    Message: "invalid key format",
}

// Forbidden keys
ForbiddenKeyValidator{Forbidden: []string{"password", "secret"}}

// Key length
LengthKeyValidator{Min: v.PtrInt(1), Max: v.PtrInt(63)}

Inter-field Logic

AnyOf (at least one group)
// Either configFile, OR both host AND port
AnyOf: [][]string{{"configFile"}, {"host", "port"}}
ExactlyOneOf (exactly one field)
// Exactly one of inline/file/url
ExactlyOneOf: []string{"inline", "file", "url"}
MutuallyExclusive (at most one)
// debug and quiet cannot both be present
MutuallyExclusive: []string{"debug", "quiet"}
Conditional Rules
Conditions: []ConditionalRule{
    {
        ConditionField: "type",
        ConditionValue: "external",
        ThenRequired:   []string{"endpoint"},
        ThenForbidden:  []string{"local"},
    },
}

Validation Options

result := validator.ValidateWithOptions(yaml, ValidationContext{
    StrictKeys:     true,  // Unknown keys are errors
    StopOnFirst:    false, // Continue after first error
    StrictTypes:    false, // Parse values for type inference
    YAML11Booleans: false, // Don't treat YAML 1.1 boolean literals (yes/no/on/off/true/false/y/n) as booleans; when true, quoted forms are also treated as booleans
})

Error Handling

Error Levels
  • LevelError - Critical validation failures
  • LevelWarning - Non-critical issues (deprecated fields, unknown keys in permissive mode)
Error Formatting
result := validator.ValidateBytes(yaml)

// Get all errors
for _, err := range result.Collector.Errors() {
    fmt.Println(err)
}

// Format with source context
fmt.Println(result.FormatAll(true)) // true = sort by position
Example Output
[ERROR] line 2:13: invalid value "v3" (expected one of [v1 v1beta1 apps/v1], got v3) (path: apiVersion)
     1 |
>    2 | apiVersion: v3
       |             ^
     3 | kind: Deployment

[ERROR] line 5:9: must be lowercase DNS-compatible name (got MyApp) (path: metadata.name)
     4 | metadata:
>    5 |   name: MyApp
       |         ^
     6 |   namespace: production

Multi-Document Support

The validator automatically handles multi-document YAML (separated by ---):

name: first
---
name: second
---
name: third

Errors in subsequent documents are prefixed with doc[N].:

[ERROR] line 5:1: required field "name" is missing (path: doc[2].name)

Custom Validators

Value Validator
type MyValidator struct{}

func (v MyValidator) Validate(node *yaml.Node, path string, ctx *ValidationContext) {
    if node.Value != "expected" {
        ctx.AddError(ValidationError{
            Level:   LevelError,
            Path:    path,
            Line:    node.Line,
            Column:  node.Column,
            Message: "value is not expected",
            Got:     node.Value,
        })
    }
}
Key Validator
type MyKeyValidator struct{}

func (v MyKeyValidator) ValidateKey(key string, keyNode *yaml.Node, path string, ctx *ValidationContext) {
    if strings.HasPrefix(key, "_") {
        ctx.AddError(ValidationError{
            Level:   LevelWarning,
            Path:    path,
            Line:    keyNode.Line,
            Column:  keyNode.Column,
            Message: "keys starting with underscore are reserved",
            Got:     key,
        })
    }
}

Best Practices

1. Use AdditionalProperties for arbitrary keys
// Allow any string values for unknown keys
labels: {
    Type:                 TypeMap,
    AdditionalProperties: &FieldSchema{Type: TypeString},
}
2. Combine with key validators
labels: {
    Type:                 TypeMap,
    AdditionalProperties: &FieldSchema{Type: TypeString},
    KeyValidators: []KeyValidator{
        RegexKeyValidator{Pattern: regexp.MustCompile(`^[a-z][a-z0-9._-]*$`)},
    },
}
3. Use Nullable for optional fields that can be explicitly null
timeout: {
    Type:     TypeInt,
    Nullable: true, // Allows: timeout: null
}
4. Use helper functions for pointers
MinItems: PtrInt(1)
Max:      PtrFloat(100)

API Reference

Validator
// Create validator
v := NewValidator(schema)

// Validate bytes
result := v.ValidateBytes(yamlData)

// Validate with options
result := v.ValidateWithOptions(yamlData, ValidationContext{...})
ValidationResult
result.HasErrors()              // bool
result.Collector.Errors()       // []ValidationError
result.Collector.Warnings()     // []ValidationError
result.Collector.All()          // []ValidationError (errors then warnings)
result.SortByPosition()         // Sort by line/column
result.FormatAll(sortByPos)     // Format with source context
ValidationError
type ValidationError struct {
    Level    ErrorLevel
    Path     string    // e.g., "spec.containers[0].image"
    Line     int       // 1-based (0 if unknown)
    Column   int       // 1-based (0 if unknown)
    Message  string
    Got      string    // Actual value/type
    Expected string    // Expected value/type
}

License

MIT License

Documentation

Overview

Package yamlvalidator provides a flexible YAML validation library with support for type checking, custom validators, conditional logic, and detailed error reporting with source context.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatErrorWithSource

func FormatErrorWithSource(err ValidationError, lines []string) string

FormatErrorWithSource formats an error with source context. Correctly handles tabs and Unicode.

func Ptr

func Ptr[T any](v T) *T

Ptr returns a pointer to the value. Useful for setting Min/Max fields.

func RenderLineWithCaret

func RenderLineWithCaret(line string, byteCol int) (string, int, int)

RenderLineWithCaret is an exported wrapper useful for external tests and tools.

Types

type ConditionalRule

type ConditionalRule struct {
	// ConditionField is the field to check.
	ConditionField string
	// ConditionValue is the expected value (scalar comparison).
	ConditionValue string
	// ThenRequired lists fields that become required when condition is met.
	ThenRequired []string
	// ThenForbidden lists fields that are forbidden when condition is met.
	ThenForbidden []string
}

ConditionalRule defines conditional validation logic. When ConditionField equals ConditionValue, additional requirements apply.

type ErrorCollector

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

ErrorCollector accumulates validation errors and warnings.

func NewErrorCollector

func NewErrorCollector() *ErrorCollector

NewErrorCollector creates a new empty ErrorCollector.

func (*ErrorCollector) Add

func (c *ErrorCollector) Add(err ValidationError)

Add adds a validation error to the collector.

func (*ErrorCollector) All

func (c *ErrorCollector) All() []ValidationError

All returns all errors followed by all warnings.

func (*ErrorCollector) Errors

func (c *ErrorCollector) Errors() []ValidationError

Errors returns all errors.

func (*ErrorCollector) HasErrors

func (c *ErrorCollector) HasErrors() bool

HasErrors returns true if there are any errors (not warnings).

func (*ErrorCollector) Warnings

func (c *ErrorCollector) Warnings() []ValidationError

Warnings returns all warnings.

type ErrorLevel

type ErrorLevel int

ErrorLevel defines the severity of a validation error.

const (
	// LevelWarning indicates a non-critical issue (e.g., deprecated field, unknown key in permissive mode).
	LevelWarning ErrorLevel = iota
	// LevelError indicates a critical validation failure.
	LevelError
)

func (ErrorLevel) String

func (l ErrorLevel) String() string

type FieldSchema

type FieldSchema struct {
	// Type is the expected node type.
	Type NodeType

	// Required indicates the field must be present.
	Required bool

	// Nullable allows null values even when Type is not TypeNull.
	Nullable bool

	// Deprecated contains a deprecation message (empty = not deprecated).
	// Use "true" for a generic message.
	Deprecated string

	// Description is a human-readable field description.
	Description string

	// Default is the default value. If set and field is missing, a warning is emitted.
	Default interface{}

	// AllowedKeys defines known keys and their schemas.
	// If nil, ALL keys are considered unknown.
	// This does NOT mean "don't check" - it means "no known keys".
	//
	// For "any keys allowed, no validation":
	//   AllowedKeys: nil,
	//   AdditionalProperties: &FieldSchema{Type: TypeAny},
	//
	// For "don't touch this map at all":
	//   AllowedKeys: nil,
	//   AdditionalProperties: nil,
	//   UnknownKeyPolicy: UnknownKeyIgnore,
	AllowedKeys map[string]*FieldSchema

	// AdditionalProperties is the schema for keys not in AllowedKeys.
	// If not nil: unknown keys are allowed and validated against this schema.
	// If nil: unknown keys are handled by UnknownKeyPolicy.
	AdditionalProperties *FieldSchema

	// UnknownKeyPolicy determines handling of keys not in AllowedKeys
	// when AdditionalProperties is nil.
	UnknownKeyPolicy UnknownKeyPolicy

	// KeyValidators validate key names (applied to ALL keys).
	KeyValidators []KeyValidator

	// ItemSchema is the schema for sequence items.
	ItemSchema *FieldSchema

	// MinItems is the minimum number of items (nil = no limit).
	MinItems *int

	// MaxItems is the maximum number of items (nil = no limit).
	MaxItems *int

	// Validators are custom value validators.
	Validators []ValueValidator

	// AnyOf requires at least one field group to be fully present.
	// Example: [][]string{{"configFile"}, {"host", "port"}}
	// Means: either configFile, OR both host AND port.
	AnyOf [][]string

	// ExactlyOneOf requires exactly one field from the list.
	// Example: []string{"inline", "file", "url"}
	// Means: exactly one of inline/file/url must be present.
	ExactlyOneOf []string

	// MutuallyExclusive allows at most one field from the list (zero is OK).
	// Example: []string{"debug", "quiet"}
	// Means: debug and quiet cannot both be present.
	MutuallyExclusive []string

	// Conditions define conditional validation rules.
	Conditions []ConditionalRule
}

FieldSchema defines the validation rules for a field.

type KeyValidator

type KeyValidator interface {
	ValidateKey(key string, keyNode *yaml.Node, path string, ctx *ValidationContext)
}

KeyValidator validates key names in mappings.

type NodeType

type NodeType int

NodeType represents the expected YAML node type.

const (
	// TypeAny accepts any type.
	TypeAny NodeType = iota
	// TypeNull represents null/nil values.
	TypeNull
	// TypeString represents string values.
	TypeString
	// TypeInt represents integer values.
	TypeInt
	// TypeFloat represents floating-point values (also accepts int).
	TypeFloat
	// TypeBool represents boolean values.
	TypeBool
	// TypeMap represents mapping nodes.
	TypeMap
	// TypeSequence represents sequence/array nodes.
	TypeSequence
)

func (NodeType) String

func (t NodeType) String() string

type UnknownKeyPolicy

type UnknownKeyPolicy int

UnknownKeyPolicy determines how unknown keys in maps are handled.

const (
	// UnknownKeyInherit uses ctx.StrictKeys to decide:
	//   StrictKeys=true  -> error
	//   StrictKeys=false -> warning
	UnknownKeyInherit UnknownKeyPolicy = iota

	// UnknownKeyError treats unknown keys as errors.
	UnknownKeyError

	// UnknownKeyWarn treats unknown keys as warnings.
	UnknownKeyWarn

	// UnknownKeyIgnore silently ignores unknown keys.
	UnknownKeyIgnore
)

type ValidationContext

type ValidationContext struct {
	// StrictKeys determines the default behavior for unknown keys
	// when UnknownKeyPolicy is UnknownKeyInherit:
	//   true  -> unknown keys are errors
	//   false -> unknown keys are warnings
	StrictKeys bool

	// StopOnFirst stops validation after the first error.
	StopOnFirst bool

	// StrictTypes uses only YAML tags for type inference.
	// When false, values are parsed to infer types (e.g., "123" -> int).
	StrictTypes bool

	// YAML11Booleans enables YAML 1.1 boolean literals (yes/no/on/off).
	// By default, only YAML 1.2 booleans (true/false) are recognized.
	YAML11Booleans bool

	// SourceLines contains the original YAML lines for error formatting.
	SourceLines []string
	// contains filtered or unexported fields
}

ValidationContext holds configuration and state for a validation run.

func NewValidationContext

func NewValidationContext() *ValidationContext

NewValidationContext creates a new ValidationContext with default settings.

func (*ValidationContext) AddError

func (ctx *ValidationContext) AddError(err ValidationError)

AddError adds an error to the context's collector.

func (*ValidationContext) Collector

func (ctx *ValidationContext) Collector() *ErrorCollector

Collector returns the error collector.

func (*ValidationContext) IsStopped

func (ctx *ValidationContext) IsStopped() bool

IsStopped returns true if validation has been stopped.

type ValidationError

type ValidationError struct {
	Level    ErrorLevel
	Path     string // Path to the problematic node, e.g., "spec.containers[0].image"
	Line     int    // 1-based line number (0 if unknown)
	Column   int    // 1-based column number (0 if unknown)
	Message  string
	Got      string // Actual value/type description
	Expected string // Expected value/type description
}

ValidationError represents a single validation issue.

func (ValidationError) Error

func (e ValidationError) Error() string

type ValidationResult

type ValidationResult struct {
	// Collector contains all errors and warnings.
	Collector *ErrorCollector
	// SourceLines contains the original YAML lines.
	SourceLines []string
}

ValidationResult contains the validation outcome and context for formatting.

func (*ValidationResult) FormatAll

func (r *ValidationResult) FormatAll(sortByPos bool) string

FormatAll formats all errors with source context.

func (*ValidationResult) HasErrors

func (r *ValidationResult) HasErrors() bool

HasErrors returns true if there are any errors.

func (*ValidationResult) SortByPosition

func (r *ValidationResult) SortByPosition()

SortByPosition sorts errors by position in the file.

type Validator

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

Validator performs YAML validation against a schema.

func NewValidator

func NewValidator(schema *FieldSchema) *Validator

NewValidator creates a new Validator with the given schema.

func (*Validator) InferTypeForPublic

func (v *Validator) InferTypeForPublic(node *yaml.Node, ctx *ValidationContext) NodeType

InferTypeForPublic exposes internal type inference for external validators.

func (*Validator) ValidateBytes

func (v *Validator) ValidateBytes(data []byte) *ValidationResult

ValidateBytes validates YAML data and returns the result. Supports multi-document YAML (separated by ---).

func (*Validator) ValidateWithOptions

func (v *Validator) ValidateWithOptions(data []byte, opts ValidationContext) *ValidationResult

ValidateWithOptions validates YAML data with custom options.

type ValueValidator

type ValueValidator interface {
	Validate(node *yaml.Node, path string, ctx *ValidationContext)
}

ValueValidator validates a node's value.

Directories

Path Synopsis
examples
basic command
Example demonstrates how to use the yamlvalidator package to validate Kubernetes-like manifests with comprehensive schema definitions.
Example demonstrates how to use the yamlvalidator package to validate Kubernetes-like manifests with comprehensive schema definitions.
easyp command
pkg

Jump to

Keyboard shortcuts

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