Skip to content

AnyVali Go SDK Reference

The Go SDK provides a native, idiomatic API for schema validation with full portable interchange support. Schemas are defined using exported builder functions, validated at runtime, and can be exported to or imported from AnyVali's canonical JSON format.

Installation

go get github.com/BetterCorp/AnyVali/sdk/go

Quick Start

package main

import (
    "encoding/json"
    "fmt"
    "log"

    v "github.com/BetterCorp/AnyVali/sdk/go"
)

func main() {
    // Define a schema
    userSchema := v.Object(map[string]v.Schema{
        "id":    v.Int64(),
        "name":  v.String().MinLength(1).MaxLength(100),
        "email": v.String().Format("email"),
        "age":   v.Optional(v.Int().Min(0).Max(150)),
        "role":  v.Enum("admin", "user", "guest").Default("user"),
    })

    // Parse input (returns error on failure)
    user, err := userSchema.Parse(map[string]any{
        "id":    42,
        "name":  "Alice",
        "email": "alice@example.com",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(user)
    // map[age:<nil> email:alice@example.com id:42 name:Alice role:user]

    // SafeParse returns a result struct instead of an error
    result := userSchema.SafeParse(map[string]any{
        "id":   "not-a-number",
        "name": "",
    })
    if !result.Success {
        for _, issue := range result.Issues {
            fmt.Printf("%v: [%s] %s\n", issue.Path, issue.Code, issue.Message)
        }
    }
}

Type Inference

Go interfaces are not generic, so the Schema interface returns any from Parse. The SDK provides generic helper functions that parse and cast in one step.

TypedParse

schema := v.String().MinLength(1)

// Without TypedParse -- requires manual assertion
raw, err := schema.Parse("hello")
str := raw.(string)

// With TypedParse -- returns the correct type directly
str, err := v.TypedParse[string](schema, "hello")
// str is string, no cast needed

TypedSafeParse

schema := v.Int().Min(0)

result := v.TypedSafeParse[int64](schema, 42)
if result.Success {
    fmt.Println(result.Data) // result.Data is int64, not any
}

Typed result struct

TypedParseResult[T] mirrors ParseResult but with a typed Data field:

type TypedParseResult[T any] struct {
    Success bool
    Data    T
    Issues  []ValidationIssue
}

Complex typed parsing

schema := v.Object(map[string]v.Schema{
    "name":  v.String(),
    "score": v.Number(),
})

result, err := v.TypedParse[map[string]any](schema, map[string]any{
    "name":  "Alice",
    "score": 98.5,
})
// result is map[string]any with validated data

If the validated output cannot be cast to T, TypedParse returns an error and TypedSafeParse returns a result with a type_assertion_failed issue.

Schema Types

Primitives

// String
s := v.String()

// Boolean
b := v.Bool()

// Null -- accepts only nil
n := v.Null()

Numeric Types

// Number (float64, the safe cross-language default)
num := v.Number()

// Explicit float widths
f64 := v.Float64()
f32 := v.Float32()   // enforces float32 range

// Signed integers
i   := v.Int()       // int64
i8  := v.Int8()      // int8 range
i16 := v.Int16()     // int16 range
i32 := v.Int32()     // int32 range
i64 := v.Int64()     // int64 range

// Unsigned integers
u8  := v.Uint8()     // uint8 range
u16 := v.Uint16()    // uint16 range
u32 := v.Uint32()    // uint32 range
u64 := v.Uint64()    // uint64 range

Number() and Float64() both produce a Float64Schema. Int() and Int64() both produce an IntSchema with int64 range. Narrower widths (Int8, Uint16, etc.) produce the same IntSchema type with range enforcement built in.

Special Types

// Any -- accepts any value, passes it through
a := v.Any()

// Unknown -- accepts any value (semantically "not yet validated")
u := v.Unknown()

// Never -- rejects all values
n := v.Never()

Literal and Enum

// Literal -- matches a single exact value
lit := v.Literal("active")
lit2 := v.Literal(42)
lit3 := v.Literal(true)

// Enum -- matches one of several allowed values
role := v.Enum("admin", "user", "guest")
status := v.Enum(1, 2, 3)

Array and Tuple

// Array -- all items must match the given schema
tags := v.Array(v.String())

// Array with length constraints
scores := v.Array(v.Number()).MinItems(1).MaxItems(100)

// Tuple -- fixed-length array with per-position schemas
pair := v.Tuple(v.String(), v.Int())

Object

// Object -- all declared properties are required by default
user := v.Object(map[string]v.Schema{
    "name":  v.String().MinLength(1),
    "email": v.String().Format("email"),
    "age":   v.Int().Min(0),
})

// Make specific fields optional using the Optional wrapper
profile := v.Object(map[string]v.Schema{
    "name": v.String(),
    "bio":  v.Optional(v.String().MaxLength(500)),
})

// Control which fields are required explicitly
config := v.Object(map[string]v.Schema{
    "host": v.String(),
    "port": v.Int(),
    "tls":  v.Bool(),
}).Required("host", "port") // tls is now optional

Record

// Record -- validates a map where all values match a schema
headers := v.Record(v.String())

// Accepts: map[string]any{"Content-Type": "text/html", "Accept": "application/json"}

Composition

// Union -- value must match at least one of the schemas
strOrNum := v.Union(v.String(), v.Number())

// Intersection -- value must match all schemas
// Useful for combining object schemas
base := v.Object(map[string]v.Schema{
    "id": v.Int64(),
})
named := v.Object(map[string]v.Schema{
    "name": v.String(),
})
entity := v.Intersection(base, named)

Modifiers

// Optional -- absent values are accepted (returns nil)
opt := v.Optional(v.String())

// Nullable -- null values are accepted
nul := v.Nullable(v.String())

// Optional with default -- absent values get the default
optWithDefault := v.Optional(v.String()).Default("unknown")

Constraints

String Constraints

s := v.String().
    MinLength(1).            // minimum character count
    MaxLength(255).          // maximum character count
    Pattern(`^\w+$`).        // regex pattern (Go RE2 syntax)
    StartsWith("hello").     // must start with prefix
    EndsWith(".com").        // must end with suffix
    Includes("@").           // must contain substring
    Format("email")          // built-in format validator

Supported format values: "email", "url", "uuid", "ipv4", "ipv6", "date", "date-time".

Numeric Constraints

All numeric schemas (Number, Float64, Float32, Int, Int8--Int64, Uint8--Uint64) support:

// Float schemas use float64 parameters
price := v.Number().
    Min(0.0).                // value >= 0
    Max(999.99).             // value <= 999.99
    ExclusiveMin(0.0).       // value > 0
    ExclusiveMax(1000.0).    // value < 1000
    MultipleOf(0.01)         // must be a multiple of 0.01

// Int schemas use int64 parameters
age := v.Int().
    Min(0).                  // value >= 0
    Max(150).                // value <= 150
    ExclusiveMin(0).         // value > 0
    ExclusiveMax(200).       // value < 200
    MultipleOf(1)            // must be a multiple of 1

Array Constraints

items := v.Array(v.String()).
    MinItems(1).             // at least 1 item
    MaxItems(50)             // at most 50 items

Object Unknown Key Handling

The UnknownKeys option controls how keys not declared in the shape are handled:

Mode Behavior
Reject (default) Produces an unknown_key issue for each extra key
Strip Silently removes extra keys from the output
Allow Passes extra keys through to the output
// Reject unknown keys (default)
strict := v.Object(map[string]v.Schema{
    "name": v.String(),
}).UnknownKeys(v.Reject)

// Strip unknown keys silently
stripped := v.Object(map[string]v.Schema{
    "name": v.String(),
}).UnknownKeys(v.Strip)

// Allow unknown keys to pass through
loose := v.Object(map[string]v.Schema{
    "name": v.String(),
}).UnknownKeys(v.Allow)

Coercion and Defaults

Coercion

Coercions transform values before validation. They run on present values only.

// Coerce string to integer
port := v.Int().Coerce(v.CoerceToInt)
result, _ := port.Parse("8080")  // => int64(8080)

// Coerce string to float
temp := v.Number().Coerce(v.CoerceToNumber)
result, _ := temp.Parse("36.6")  // => float64(36.6)

// Coerce string to bool
flag := v.Bool().Coerce(v.CoerceToBool)
result, _ := flag.Parse("true")  // => true

// String coercions
name := v.String().Coerce(v.CoerceTrim)   // trims whitespace
tag := v.String().Coerce(v.CoerceLower)    // lowercases
code := v.String().Coerce(v.CoerceUpper)   // uppercases

Available coercion types:

Constant Effect
CoerceToInt Parse string to int64
CoerceToNumber Parse string to float64
CoerceToBool Parse string to bool
CoerceTrim Trim whitespace from string
CoerceLower Lowercase string
CoerceUpper Uppercase string

Defaults

Defaults fill in missing (absent) values. The default value must pass validation. Defaults must be static values (for portability across SDKs); use the Common Patterns section below for computed defaults.

Call .Default(value) on any schema:

role := v.Enum("admin", "user", "guest").Default("user")
age := v.Int().Min(0).Default(int64(0))
active := v.Bool().Default(true)
name := v.String().Default("Anonymous")
tags := v.Array(v.String()).Default([]any{})

Optional schemas also support defaults:

bio := v.Optional(v.String().MaxLength(500)).Default("No bio provided")

Export and Import

Export

Convert a schema to the portable AnyVali JSON document:

schema := v.Object(map[string]v.Schema{
    "name":  v.String().MinLength(1),
    "email": v.String().Format("email"),
})

// Export to Document struct
doc, err := v.Export(schema, v.Portable)
if err != nil {
    log.Fatal(err)
}

// Export directly to JSON bytes
jsonBytes, err := v.ExportJSON(schema, v.Portable)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(jsonBytes))

Output:

{
  "anyvaliVersion": "1.0",
  "schemaVersion": "1",
  "root": {
    "kind": "object",
    "properties": {
      "name": { "kind": "string", "minLength": 1 },
      "email": { "kind": "string", "format": "email" }
    },
    "required": ["name", "email"],
    "unknownKeys": "reject"
  },
  "definitions": {},
  "extensions": {}
}

Export modes:

Mode Constant Behavior
Portable v.Portable Fails if schema uses non-portable features
Extended v.Extended Includes language-specific extensions

Import

Reconstruct a schema from a JSON document:

// Import from Document struct
schema, err := v.Import(doc)
if err != nil {
    log.Fatal(err)
}

// Import from raw JSON bytes
schema, err := v.ImportJSON(jsonBytes)
if err != nil {
    log.Fatal(err)
}

// Use the imported schema normally
result, err := schema.Parse(map[string]any{
    "name":  "Bob",
    "email": "bob@test.com",
})

Error Handling

Parse (throwing)

Parse returns (any, error). The error is always a *ValidationError:

result, err := schema.Parse(input)
if err != nil {
    var ve *v.ValidationError
    if errors.As(err, &ve) {
        for _, issue := range ve.Issues {
            fmt.Printf("[%s] %s at %v\n", issue.Code, issue.Message, issue.Path)
        }
    }
}

SafeParse (non-throwing)

SafeParse returns a ParseResult struct that never errors:

result := schema.SafeParse(input)
if result.Success {
    fmt.Println("Valid:", result.Data)
} else {
    for _, issue := range result.Issues {
        fmt.Printf("[%s] %s at %v\n", issue.Code, issue.Message, issue.Path)
    }
}

ValidationIssue

Each issue has a consistent structure:

type ValidationIssue struct {
    Code     string         // machine-readable issue code
    Message  string         // human-readable description
    Path     []any          // location in the input (strings for keys, ints for indices)
    Expected string         // what was expected
    Received string         // what was received
    Meta     map[string]any // optional additional metadata
}

Issue Codes

Constant Code String Meaning
IssueInvalidType "invalid_type" Value has wrong type
IssueRequired "required" Required field is missing
IssueUnknownKey "unknown_key" Object has an undeclared key
IssueTooSmall "too_small" Below minimum (length, value, items)
IssueTooLarge "too_large" Above maximum (length, value, items)
IssueInvalidString "invalid_string" String constraint failed (pattern, format, etc.)
IssueInvalidNumber "invalid_number" Numeric constraint failed (multipleOf, range)
IssueInvalidLiteral "invalid_literal" Literal/enum value mismatch
IssueInvalidUnion "invalid_union" No union variant matched
IssueCoercionFailed "coercion_failed" Coercion could not convert the value
IssueDefaultInvalid "default_invalid" Default value fails validation
IssueUnsupportedSchemaKind "unsupported_schema_kind" Unknown or unresolved schema kind
IssueUnsupportedExtension "unsupported_extension" Non-portable extension encountered

Common Patterns

Validating Environment Variables

Use UnknownKeysStrip when parsing maps that contain many extra keys you don't care about, like environment variables:

envSchema := av.Object(map[string]av.Schema{
    "NODE_ENV":     av.Optional(av.String()).WithDefault("development"),
    "PORT":         av.Optional(av.Int()),
    "DATABASE_URL": av.String(),
}).UnknownKeys(av.UnknownKeysStrip)

Without Strip, parse would fail with unknown_key issues for every other variable in the environment (PATH, HOME, etc.) because the default mode is Reject.

Eagerly Evaluated vs Lazy Defaults

.Default() accepts any value of the correct type. Expressions like os.Getwd() are evaluated immediately when the schema is created and stored as a static value -- this works fine. What AnyVali does not support is lazy function defaults that re-evaluate on each parse call. If you need a fresh value on every parse, apply it after:

configSchema := av.Object(map[string]av.Schema{
    "profile": av.Optional(av.String()).Default("default"),
    "appDir":  av.Optional(av.String()),
}).UnknownKeys(av.Strip)

config, err := configSchema.Parse(envMap)
if err != nil {
    log.Fatal(err)
}
if config.(map[string]any)["appDir"] == nil {
    wd, _ := os.Getwd()
    config.(map[string]any)["appDir"] = wd
}

This keeps the schema fully portable -- the same JSON document can be imported in Python, JavaScript, or any other SDK without relying on language-specific function calls.

API Reference

Builder Functions

Function Returns Description
String() *StringSchema String validator
Number() *Float64Schema Float64 validator (alias: number)
Float64() *Float64Schema Float64 validator
Float32() *Float32Schema Float32 validator with range check
Int() *IntSchema Int64 validator (alias: int)
Int8() *IntSchema Int8-range validator
Int16() *IntSchema Int16-range validator
Int32() *IntSchema Int32-range validator
Int64() *IntSchema Int64-range validator
Uint8() *IntSchema Uint8-range validator
Uint16() *IntSchema Uint16-range validator
Uint32() *IntSchema Uint32-range validator
Uint64() *IntSchema Uint64-range validator
Bool() *BoolSchema Boolean validator
Null() *NullSchema Null validator
Any() *AnySchema Accepts any value
Unknown() *UnknownSchema Accepts any value (semantically unvalidated)
Never() *NeverSchema Rejects all values
Literal(v any) *LiteralSchema Exact value match
Enum(values ...any) *EnumSchema One of allowed values
Array(item Schema) *ArraySchema Homogeneous array
Tuple(items ...Schema) *TupleSchema Fixed-length typed array
Object(props map[string]Schema) *ObjectSchema Structured object
Record(value Schema) *RecordSchema String-keyed map with uniform values
Union(schemas ...Schema) *UnionSchema Matches any one variant
Intersection(schemas ...Schema) *IntersectionSchema Matches all schemas
Optional(s Schema) *OptionalSchema Allows absent values
Nullable(s Schema) *NullableSchema Allows null values

Schema Interface

type Schema interface {
    Parse(input any) (any, error)
    SafeParse(input any) ParseResult
    ToNode() map[string]any
}

Generic Helpers

func TypedParse[T any](s Schema, input any) (T, error)
func TypedSafeParse[T any](s Schema, input any) TypedParseResult[T]

Constraint Methods

StringSchema -- all return *StringSchema for chaining:

Method Parameter Description
MinLength(n) int Minimum character count
MaxLength(n) int Maximum character count
Pattern(p) string Regex pattern (RE2 syntax)
StartsWith(s) string Required prefix
EndsWith(s) string Required suffix
Includes(s) string Required substring
Format(f) string Built-in format check
Default(v) string Default value
Coerce(c) CoercionType Add coercion

Float64Schema / NumberSchema / Float32Schema -- all return *Float64Schema for chaining:

Method Parameter Description
Min(n) float64 Inclusive minimum
Max(n) float64 Inclusive maximum
ExclusiveMin(n) float64 Exclusive minimum
ExclusiveMax(n) float64 Exclusive maximum
MultipleOf(n) float64 Divisibility constraint
Default(v) float64 Default value
Coerce(c) CoercionType Add coercion

IntSchema -- all return *IntSchema for chaining:

Method Parameter Description
Min(n) int64 Inclusive minimum
Max(n) int64 Inclusive maximum
ExclusiveMin(n) int64 Exclusive minimum
ExclusiveMax(n) int64 Exclusive maximum
MultipleOf(n) int64 Divisibility constraint
Default(v) int64 Default value
Coerce(c) CoercionType Add coercion

ArraySchema -- all return *ArraySchema for chaining:

Method Parameter Description
MinItems(n) int Minimum item count
MaxItems(n) int Maximum item count
Default(v) []any Default value

ObjectSchema -- all return *ObjectSchema for chaining:

Method Parameter Description
Required(fields...) ...string Set which fields are required (overrides default)
UnknownKeys(mode) UnknownKeyMode How to handle undeclared keys
Default(v) map[string]any Default value

BoolSchema, EnumSchema, LiteralSchema, OptionalSchema, NullableSchema -- each supports Default(v) and (where applicable) Coerce(c).

Export/Import Functions

func Export(schema Schema, mode ExportMode) (*Document, error)
func ExportJSON(schema Schema, mode ExportMode) ([]byte, error)
func Import(doc *Document) (Schema, error)
func ImportJSON(data []byte) (Schema, error)

Core Types

type ParseResult struct {
    Success bool
    Data    any
    Issues  []ValidationIssue
}

type TypedParseResult[T any] struct {
    Success bool
    Data    T
    Issues  []ValidationIssue
}

type ValidationIssue struct {
    Code     string
    Message  string
    Path     []any
    Expected string
    Received string
    Meta     map[string]any
}

type ValidationError struct {
    Issues []ValidationIssue
}

type Document struct {
    AnyvaliVersion string                    `json:"anyvaliVersion"`
    SchemaVersion  string                    `json:"schemaVersion"`
    Root           map[string]any            `json:"root"`
    Definitions    map[string]map[string]any `json:"definitions,omitempty"`
    Extensions     map[string]any            `json:"extensions,omitempty"`
}

type ExportMode string   // Portable, Extended
type UnknownKeyMode string // Reject, Strip, Allow
type CoercionType string  // CoerceToInt, CoerceToNumber, CoerceToBool, CoerceTrim, CoerceLower, CoerceUpper