apivalidation

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Feb 14, 2026 License: MIT Imports: 18 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

Index

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 AddPath

func AddPath(path, method string, s *openapi3.T, op *openapi3.Operation)

AddPath adds an operation to the OpenAPI spec at the given path and method.

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.

func DecodeAndValidate

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

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 DocBase

func DocBase(serviceName, description, version string) *openapi3.T

DocBase returns a basic OpenAPI 3.0.3 document structure.

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 NewRequest

func NewRequest(vs ...any) (*openapi3.RequestBodyRef, error)

NewRequest generates an OpenAPI request body schema from the given value types.

func NewRequestMust

func NewRequestMust(vs ...any) *openapi3.RequestBodyRef

NewRequestMust is like NewRequest but panics on error.

func NewResponse

func NewResponse(vs map[string]Response) (*openapi3.Responses, error)

NewResponse creates an OpenAPI responses object. Map key is status code (e.g. "200", "4xx").

func NewResponseMust

func NewResponseMust(vs map[string]Response) *openapi3.Responses

NewResponseMust is like NewResponse but panics on error. Map key is status code (e.g. "200", "4xx").

func StructMulti

func StructMulti(v any, fns ...func(any))

StructMulti runs all given functions on the struct pointer sequentially.

func StructStringFunc

func StructStringFunc(v any, f func(string) string)

StructStringFunc applies f to every string field in the struct recursively.

func StructToLower

func StructToLower(v any)

StructToLower runs strings.ToLower on all string fields in the struct recursively.

func StructTrimSpace

func StructTrimSpace(v any)

StructTrimSpace runs strings.TrimSpace on all string fields in the struct recursively, including nested structs, pointer fields, slices, and map values.

func SwaggerHandler

func SwaggerHandler(prefix string, s *openapi3.T) (http.Handler, error)

SwaggerHandler returns an http.Handler that serves the Swagger UI for the given OpenAPI spec. The prefix is stripped automatically, so just mount it:

http.Handle("/swagger/", apivalidation.SwaggerHandlerMust("/swagger/", spec))

func SwaggerHandlerMust

func SwaggerHandlerMust(prefix string, s *openapi3.T) http.Handler

SwaggerHandlerMust is like SwaggerHandler but panics on error.

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.

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.

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.

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.

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 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 Response

type Response struct {
	Desc string
	V    []any
}

Response describes an HTTP response with a description and body types for schema generation.

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

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)}
}

Directories

Path Synopsis
Package is provides a list of commonly used string validation rules.
Package is provides a list of commonly used string validation rules.

Jump to

Keyboard shortcuts

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