apivalidation

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Feb 17, 2026 License: MIT Imports: 13 Imported by: 0

README

apivalidation

Validation + OpenAPI 3 schema generation from a single source of truth. Define rules once as Go code, get runtime validation and generated docs for free.

Quick Start

Implement Ruler on your struct:

type Order struct {
    Name   string  `json:"name"`
    Amount float64 `json:"amount"`
    Status string  `json:"status"`
}

func (o *Order) Rules() []*v.FieldRules {
    return []*v.FieldRules{
        v.Field(&o.Name, v.Required, v.Length(1, 100)),
        v.Field(&o.Amount, v.Required, v.Min(0.0)),
        v.Field(&o.Status, v.Required, v.In("pending", "paid", "cancelled")),
    }
}

Validate:

err := v.Validate(&order)

Unmarshal + normalize + validate in one call:

err := v.UnmarshalAndValidate(r.Body, &order)

Generate OpenAPI schema:

req, err := v.NewRequest(Order{})

That's it. Rules() drives all three.

Struct Tags

Tag Effect
json:"name" Field name in errors and schema
json:"-" Field excluded from schema and validation
docs:"skip" Field excluded from OpenAPI schema
validate:"-" Field intentionally has no rules (for MissingRules check)

Nested Structs, Slices, Maps

Child structs that implement Ruler are validated automatically. Just declare the field in the parent's Rules():

type LineItem struct {
    SKU string
    Qty int
}

func (l *LineItem) Rules() []*v.FieldRules {
    return []*v.FieldRules{
        v.Field(&l.SKU, v.Required),
        v.Field(&l.Qty, v.Required, v.Min(1)),
    }
}

type Cart struct {
    Items []LineItem
}

func (c *Cart) Rules() []*v.FieldRules {
    return []*v.FieldRules{
        v.Field(&c.Items, v.Unique(func(i int) any { return c.Items[i].SKU }, "unique SKUs")),
    }
}

v.Validate(&cart) validates the cart, checks uniqueness, and validates every LineItem — all automatically via Rules().

Works the same for map[string]Ruler, []*Ruler, and nested collections like map[string][]Ruler.

Embedded Structs

Embedded Ruler structs get flat error keys (not nested under the embedded type name):

type Base struct {
    ID string
}

func (b *Base) Rules() []*v.FieldRules {
    return []*v.FieldRules{v.Field(&b.ID, v.Required)}
}

type Product struct {
    Base
    Name string
}

func (p *Product) Rules() []*v.FieldRules {
    return []*v.FieldRules{
        v.Field(&p.Base),
        v.Field(&p.Name, v.Required),
    }
}
// Error keys: {"ID": "...", "Name": "..."} — not {"Base": {"ID": "..."}}

Value Types (Non-Struct)

For named types like type PaymentMethod string, implement ValueRuler to define validation rules once. They apply automatically wherever the type appears as a struct field — both validation and OpenAPI schema generation.

type PaymentMethod string

const (
    PaymentACH PaymentMethod = "ach"
    PaymentCC  PaymentMethod = "cc"
)

func (p PaymentMethod) ValueRules() []v.Rule {
    return []v.Rule{v.In(PaymentACH, PaymentCC)}
}

Use it in any struct — no extra rules needed for the field's own validation:

type Checkout struct {
    Method PaymentMethod `json:"method"`
    Total  float64       `json:"total"`
}

func (c *Checkout) Rules() []*v.FieldRules {
    return []*v.FieldRules{
        v.Field(&c.Method, v.Required), // In() comes from ValueRules automatically
        v.Field(&c.Total, v.Required, v.Min(0.01)),
    }
}

Any rule works in ValueRules: In, Min, Max, Length, Describe, custom rules — all of it.

Normalization

Implement Normalizer to run custom logic after JSON decoding and before validation:

func (o *Order) Normalize() {
    v.StructTrimSpace(o)
    o.Status = strings.ToLower(o.Status)
}

UnmarshalAndValidate calls Normalize() automatically and recurses into nested structs, slices, and maps that also implement Normalizer. Top level runs first, then children.

Use ContextNormalizer with UnmarshalAndValidateCtx when you need a context.

Transform Utilities

v.StructTrimSpace(&s)                          // strings.TrimSpace on all string fields
v.StructToLower(&s)                             // strings.ToLower on all string fields
v.StructStringFunc(&s, myFunc)                  // any func(string) string
v.StructMulti(&s, v.StructTrimSpace, v.StructToLower) // chain multiple

These walk struct fields, pointers, slices, and map values recursively.

Catching Forgotten Fields

MissingRules returns field names that have no rule. Use in tests to ensure full coverage:

func TestRulesCoverage(t *testing.T) {
    assert.Empty(t, v.MissingRules(&Order{}))
    assert.Empty(t, v.MissingRules(&Cart{}))
}

Tag fields that intentionally have no rules with validate:"-" so they don't show up as missing.

OpenAPI Schema Generation

doc := v.DocBase("my-service", "My API", "1.0.0")

req := v.NewRequestMust(Order{})
resp, _ := v.NewResponse(map[string]v.Response{
    "200": {Desc: "success", V: []any{Order{}}},
    "400": {Desc: "bad request", V: []any{ErrorResponse{}}},
})

v.AddPath("/orders", http.MethodPost, doc, &openapi3.Operation{
    OperationID: "createOrder",
    RequestBody: req,
    Responses:   resp,
})

Serve a Swagger UI with SwaggerHandler or SwaggerHandlerMust (standard http.Handler):

http.Handle("/swagger/", v.SwaggerHandlerMust("/swagger/", doc))

Documentation

Overview

Package apivalidation provides struct validation with automatic OpenAPI 3 schema generation.

Define validation rules by implementing Ruler on your structs:

func (o *Order) Rules() []*FieldRules {
    return []*FieldRules{
        Field(&o.ID, Required),
        Field(&o.Amount, Min(0.01)),
    }
}

Then validate with a single call:

err := Validate(&order)

For HTTP handlers, UnmarshalAndValidate and DecodeAndValidate combine JSON decoding with validation in one step.

Sub-packages:

  • openapi – OpenAPI schema generation, Swagger UI serving, and endpoint helpers
  • transform – struct string transformation utilities
  • is – common string format validation rules

Index

Examples

Constants

This section is empty.

Variables

View Source
var Empty = absentRule{validation.Empty, true}

Empty checks if a not nil value is empty.

View Source
var Nil = absentRule{validation.Nil, false}

Nil is a validation rule that checks if a value is nil.

View Source
var NotNil = notNilRule{Rule: validation.NotNil}

NotNil is a validation rule that checks if a value is not nil.

View Source
var Required = requiredRule{
	validation.Required,
	"required",
}

Required is a validation rule that checks if a value is not empty.

Functions

func DecodeAndValidate

func DecodeAndValidate(r io.Reader, dst any) error

DecodeAndValidate reads JSON from r into dst using a streaming decoder, then normalizes and validates. Use this instead of UnmarshalAndValidate when reading directly from an io.Reader such as an HTTP request body.

func DecodeAndValidateContext

func DecodeAndValidateContext(ctx context.Context, r io.Reader, dst any) error

DecodeAndValidateContext is like DecodeAndValidate but passes a context to ContextNormalizer.Normalize and ContextRuler.Rules.

func MissingRules

func MissingRules(structPtr any, exclude ...string) []string

MissingRules returns the names of exported struct fields that have no corresponding rule in the Ruler's Rules(). Embedded Ruler fields are expanded and their inner fields checked recursively.

Automatically excluded:

  • json:"-"
  • docs:"skip"
  • validate:"-" (field intentionally has no rules)

Use in tests to catch forgotten fields:

assert.Empty(t, v.MissingRules(&MyStruct{}))
assert.Empty(t, v.MissingRules(&MyStruct{}, "OptionalField"))

func NewSchemaRefForValue added in v0.0.2

func NewSchemaRefForValue(value any) (*openapi3.SchemaRef, error)

NewSchemaRefForValue generates an OpenAPI schema for the given value, applying validation rules from types that implement Ruler, ContextRuler, or ValueRuler.

func UnmarshalAndValidate

func UnmarshalAndValidate(b []byte, dst any) error

UnmarshalAndValidate decodes JSON from r into dst, then validates. If dst implements Normalizer, recursively normalizes (top level first, then nested structs, slices, maps) before validation.

Example
package main

import (
	"fmt"

	v "github.com/Gobd/apivalidation"
)

type User struct {
	Name  string `json:"name"`
	Email string `json:"email"`
	Age   int    `json:"age"`
}

func (u *User) Rules() []*v.FieldRules {
	return []*v.FieldRules{
		v.Field(&u.Name, v.Required, v.Length(1, 100)),
		v.Field(&u.Email, v.Required),
		v.Field(&u.Age, v.Min(0), v.Max(150)),
	}
}

func main() {
	body := []byte(`{"name":"Bob","email":"[email protected]","age":25}`)
	var user User
	if err := v.UnmarshalAndValidate(body, &user); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(user.Name)
}
Output:

Bob

func UnmarshalAndValidateCtx

func UnmarshalAndValidateCtx(ctx context.Context, b []byte, dst any) error

UnmarshalAndValidateCtx is like UnmarshalAndValidate but passes a context to ContextNormalizer.Normalize and ContextRuler.Rules.

func Validate

func Validate(value any) error

Validate is the single entry point for all validation. If value implements Ruler, validates struct fields via Rules(). If value implements ValueRuler, applies its rules to the value directly. Collection elements implementing Ruler are auto-validated.

Example
package main

import (
	"fmt"

	v "github.com/Gobd/apivalidation"
)

type User struct {
	Name  string `json:"name"`
	Email string `json:"email"`
	Age   int    `json:"age"`
}

func (u *User) Rules() []*v.FieldRules {
	return []*v.FieldRules{
		v.Field(&u.Name, v.Required, v.Length(1, 100)),
		v.Field(&u.Email, v.Required),
		v.Field(&u.Age, v.Min(0), v.Max(150)),
	}
}

func main() {
	user := &User{Name: "Alice", Email: "[email protected]", Age: 30}
	if err := v.Validate(user); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("valid")
}
Output:

valid
Example (Error)
package main

import (
	"fmt"

	v "github.com/Gobd/apivalidation"
)

type User struct {
	Name  string `json:"name"`
	Email string `json:"email"`
	Age   int    `json:"age"`
}

func (u *User) Rules() []*v.FieldRules {
	return []*v.FieldRules{
		v.Field(&u.Name, v.Required, v.Length(1, 100)),
		v.Field(&u.Email, v.Required),
		v.Field(&u.Age, v.Min(0), v.Max(150)),
	}
}

func main() {
	user := &User{Age: -1}
	err := v.Validate(user)
	fmt.Println(err)
}
Output:

age: must be no less than 0; email: cannot be blank; name: cannot be blank.

func ValidateCtx

func ValidateCtx(ctx context.Context, value any) error

ValidateCtx is like Validate but passes a context to ContextRuler.Rules().

func ValidateStruct

func ValidateStruct(structPtr any, fields []*FieldRules) error

ValidateStruct validates a struct with explicit field rules. Prefer Validate for types implementing Ruler.

Types

type ContextNormalizer

type ContextNormalizer interface {
	Normalize(context.Context)
}

ContextNormalizer is like Normalizer but receives a context. Called by UnmarshalAndValidateCtx before validation.

type ContextRuler

type ContextRuler interface {
	Rules(context.Context) []*FieldRules
}

ContextRuler is like Ruler but receives a context (for conditional rules).

type DateRule added in v0.0.2

type DateRule struct {
	validation.DateRule
	// contains filtered or unexported fields
}

DateRule validates that a string value matches the given date layout format. Use Date to create one, then chain DateRule.Min and DateRule.Max to constrain the date range for documentation.

func Date

func Date(layout string) *DateRule

Date creates a date validation rule with the given layout format. Use .Min() and .Max() to constrain the date range for documentation.

Example
package main

import (
	"fmt"
	"time"

	v "github.com/Gobd/apivalidation"
)

type Event struct {
	StartDate string `json:"start_date"`
}

func (e *Event) Rules() []*v.FieldRules {
	return []*v.FieldRules{
		v.Field(&e.StartDate, v.Required, v.Date("2006-01-02").
			Min(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)).
			Max(time.Date(2030, 12, 31, 0, 0, 0, 0, time.UTC))),
	}
}

func main() {
	e := &Event{StartDate: "2025-06-15"}
	if err := v.Validate(e); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("valid")
}
Output:

valid

func (*DateRule) Describe added in v0.0.2

func (r *DateRule) Describe(_ string, _ *openapi3.Schema, ref *openapi3.SchemaRef) error

Describe implements Rule by setting the format and date range on the schema.

func (*DateRule) Max added in v0.0.2

func (r *DateRule) Max(t time.Time) *DateRule

Max sets the maximum allowed date for documentation.

func (*DateRule) Min added in v0.0.2

func (r *DateRule) Min(t time.Time) *DateRule

Min sets the minimum allowed date for documentation.

type FieldRules

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

FieldRules binds a struct field pointer to its validation rules.

func Field

func Field[T any](fieldPtr *T, rules ...Rule) *FieldRules

Field creates a FieldRules binding a struct field pointer to its validation rules.

type Normalizer

type Normalizer interface {
	Normalize()
}

Normalizer is implemented by types that need custom normalization after unmarshaling. Called by UnmarshalAndValidate before validation. When the top-level type implements Normalizer, normalization recurses into struct fields, slices, maps, and embedded structs, calling Normalize on any nested type that also implements it. Top level is always called first, then children depth-first.

type Rule

type Rule interface {
	Validate(value any) error
	Describe(name string, schema *openapi3.Schema, ref *openapi3.SchemaRef) error
}

Rule is the interface that all validation rules must implement.

func By

func By(f RuleFunc, desc string) Rule

By wraps a RuleFunc into a Rule.

func Custom

func Custom(f func(any) error, desc string) Rule

Custom returns a validation rule that uses f for validation and desc for documentation.

func Default

func Default(a any) Rule

Default returns a documentation-only rule that sets the schema default value.

func Deprecate

func Deprecate() Rule

Deprecate returns a documentation-only rule that marks the field as deprecated in the schema.

func Describe

func Describe(desc string) Rule

Describe returns a documentation-only rule that appends desc to the schema description.

func Each

func Each(rules ...Rule) Rule

Each returns a validation rule that applies the given rules to each element of a slice or array.

func Example

func Example(ex any) Rule

Example returns a documentation-only rule that sets the schema example value.

func HasAlphabetic

func HasAlphabetic() Rule

HasAlphabetic returns a validation rule that checks if a string contains at least one alphabetic character.

func In

func In(values ...any) Rule

In returns a validation rule that checks if a value is one of the allowed values.

func KeyIn

func KeyIn(values ...string) Rule

KeyIn ensures that the keys of a map are in the allowed values

func Length

func Length(lo, hi int) Rule

Length returns a validation rule that checks if a string's rune length is within the specified range.

func Max

func Max(threshold any) Rule

Max returns a validation rule that checks if a value is less than or equal to the specified maximum.

func Min

func Min(threshold any) Rule

Min returns a validation rule that checks if a value is greater than or equal to the specified minimum.

func NewStringRule

func NewStringRule(validator func(string) bool, desc string) Rule

NewStringRule returns a string validation rule using desc as both the error message and schema description.

func NewStringRuleDecimalMax

func NewStringRuleDecimalMax(i uint) Rule

NewStringRuleDecimalMax returns a validation rule that limits the number of decimal places in a numeric string.

func NewStringRuleWithError

func NewStringRuleWithError(validator func(string) bool, err validation.Error, desc string) Rule

NewStringRuleWithError returns a string validation rule with a custom error and schema description.

func NonCreditCardNumber

func NonCreditCardNumber() Rule

NonCreditCardNumber returns a validation rule that rejects strings that look like credit card numbers.

func Skip

func Skip(desc string) Rule

Skip returns a rule that skips all subsequent validation and adds desc to the schema description.

func Unique

func Unique(f func(a int) any, desc string) Rule

Unique returns a validation rule that checks if all elements in a slice are unique according to f.

type RuleFunc

type RuleFunc func(value any) error

RuleFunc is a function type that validates a value and returns an error if invalid.

type Ruler

type Ruler interface {
	Rules() []*FieldRules
}

Ruler is implemented by types that define validation rules for their fields. Use a pointer receiver so field pointers are stable:

func (s *MyStruct) Rules() []*FieldRules {
    return []*FieldRules{Field(&s.Name, Required)}
}

type ValidationErrors

type ValidationErrors = validation.Errors

ValidationErrors is a map of field names to their validation errors. It is an alias for validation.Errors from ozzo-validation and implements the error interface with a JSON-friendly string representation.

type ValueRuler

type ValueRuler interface {
	ValueRules() []Rule
}

ValueRuler is implemented by non-struct types (e.g. type PaymentMethod string) that carry their own validation rules. The returned rules are automatically applied during both validation and OpenAPI schema generation wherever the type appears as a struct field.

type PaymentMethod string

const (
    PaymentACH  PaymentMethod = "ach"
    PaymentCC   PaymentMethod = "cc"
)

func (p PaymentMethod) ValueRules() []Rule {
    return []Rule{In(PaymentACH, PaymentCC)}
}

type WhenRule added in v0.0.2

type WhenRule struct {
	validation.WhenRule
	// contains filtered or unexported fields
}

WhenRule validates conditionally: it applies one set of rules when the condition is true, and an optional alternative set (via WhenRule.Else) when false. Use When to create one.

func When

func When(condition bool, desc string, rules ...Rule) *WhenRule

When returns a conditional validation rule that applies rules only when condition is true.

Example
package main

import (
	"encoding/json"
	"fmt"

	v "github.com/Gobd/apivalidation"
)

type Payment struct {
	Amount   float64 `json:"amount"`
	Currency string  `json:"currency"`
	IsDraft  bool    `json:"-"`
}

func (p *Payment) Rules() []*v.FieldRules {
	return []*v.FieldRules{
		v.Field(&p.Amount, v.When(!p.IsDraft, "not draft", v.Required, v.Min(0.01)).
			Else(v.Min(0.0))),
		v.Field(&p.Currency, v.Required, v.In("USD", "EUR", "GBP")),
	}
}

func main() {
	p := &Payment{Amount: 10.00, Currency: "USD"}
	if err := v.Validate(p); err != nil {
		fmt.Println(err)
		return
	}

	b, _ := json.Marshal(p)
	fmt.Println(string(b))
}
Output:

{"amount":10,"currency":"USD"}

func (*WhenRule) Describe added in v0.0.2

func (r *WhenRule) Describe(name string, _ *openapi3.Schema, ref *openapi3.SchemaRef) error

Describe implements Rule by appending a human-readable summary of the conditional rules to the schema description.

func (*WhenRule) Else added in v0.0.2

func (r *WhenRule) Else(rules ...Rule) *WhenRule

Else specifies alternative rules to apply when the When condition is false.

Directories

Path Synopsis
Command example demonstrates apivalidation with an HTTP server serving a Swagger UI and a validated JSON endpoint.
Command example demonstrates apivalidation with an HTTP server serving a Swagger UI and a validated JSON endpoint.
Package is provides a list of commonly used string validation rules.
Package is provides a list of commonly used string validation rules.
Package openapi generates OpenAPI 3 specifications from struct types that implement [apivalidation.Ruler].
Package openapi generates OpenAPI 3 specifications from struct types that implement [apivalidation.Ruler].
Package transform provides struct transformation utilities for mutating string fields recursively within structs.
Package transform provides struct transformation utilities for mutating string fields recursively within structs.

Jump to

Keyboard shortcuts

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