schemix

package module
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 22 Imported by: 0

README

schemix

Schema-driven validation & transformation engine

CUE constraints + Bloblang dynamic expressions, unified.

Go Reference Go Version Release Codecov CI

English | 中文


graph TD
    subgraph Schema Definition
        CUE[CUE Constraints<br/>type / regex / enum / range]
        BLOB["@blob() Expressions<br/>validate / compute"]
        META["@meta() Field Control<br/>priority / skip / output"]
    end

    subgraph Compile Time
        CUE & BLOB & META --> FD[Pre-compiled Field Descriptors]
        FD --> FP[Go Fast Path<br/>type / regex / range / enum]
    end

    subgraph Runtime
        FP --> ENGINE[Execution Engine]
        ENGINE --> FA[FailAll<br/>collect all]
        ENGINE --> FF[FailFast<br/>stop at first]
        ENGINE --> FPR[FailPriority<br/>group isolation]
    end

    FA & FF & FPR --> RESULT[Result<br/>Valid · Output · Errors]

Table of Contents

Features

Category Capabilities
Constraints Types, regex, enums, ranges, nested structs, arrays [...{schema}], nullable null | type
Dynamic Rules Bloblang expressions — return bool for validation, other types for computed values
Built-in Validators 37+ methods: email, URL, UUID, IP, Luhn, JSON, Base64, mobile, length, range...
Custom Functions Register your own functions/methods with Bloblang-compatible API (V1 & V2 styles)
Field Control Priority groups, conditional required/skip, omit empty, fail-fast per field
Execution Three FailModes — collect all / stop at first / priority-group isolation
Performance Go-native fast path for scalar fields (2.5µs/op), pre-compiled descriptors
Error Handling Structured codes, chain API (HasCode/ErrorsByCode/ErrorsByType), custom i18n formatter
Composition Schema reuse via CUE definitions + NewFromValue, runtime introspection
Integration Method & function forms for Benthos/Redpanda Connect pipelines
Thread Safety Validator immutable after construction; Registry uses RWMutex

Install

go get github.com/mredencom/schemix@latest

Requires: Go 1.26.5 or newer

Quick Start

v, err := schemix.New(`{
    pan:      =~"^[0-9]{16}$"
    amount:   int & >0
    currency: "156" | "840"

    // Built-in validators
    luhn:       bool   @blob(this.pan.luhn_valid())
    pan_check:  bool   @blob(this.pan.has_prefix("62") || this.pan.has_prefix("4"))

    // Computed fields
    card_brand: string @blob(if this.pan.has_prefix("62") { "UnionPay" } else { "Visa" })
    fee:        number @blob(if this.currency == "156" { 0 } else { (this.amount * 0.015).ceil() })
}`)

r := v.Process(map[string]any{
    "pan": "4111111111111111", "amount": int64(10000), "currency": "840",
})

r.Valid                // true
r.Output["card_brand"] // "Visa"
r.Output["fee"]        // 150

Built-in Validators

All methods are available automatically in @blob() expressions — no registration needed.

String Format
Method Usage Description
is_email() this.email.is_email() Email address format
is_url() this.link.is_url() URL with scheme
is_full_url() this.cb.is_full_url() Must start with http/https
is_uuid() this.id.is_uuid() UUID any version
is_uuid3/4/5() this.id.is_uuid4() Specific UUID version
is_ip() this.host.is_ip() IPv4 or IPv6
is_ipv4() / is_ipv6() this.ip.is_ipv4() Specific IP version
is_cidr() this.net.is_cidr() CIDR notation
is_mac() this.mac.is_mac() MAC address
is_dns_name() this.host.is_dns_name() DNS hostname
is_json() this.body.is_json() Valid JSON string
is_base64() this.token.is_base64() Base64 encoded
is_hex() this.hash.is_hex() Hexadecimal string
is_hex_color() this.color.is_hex_color() #RGB or #RRGGBB
is_rgb_color() this.color.is_rgb_color() rgb(r,g,b)
is_data_uri() this.img.is_data_uri() data:mime;base64,...
is_latitude() this.lat.is_latitude() -90 to 90
is_longitude() this.lng.is_longitude() -180 to 180
is_isbn10/13() this.isbn.is_isbn13() ISBN format
is_cn_mobile() this.phone.is_cn_mobile() China mobile (1xx)
Character Type
Method Usage Description
is_alpha() this.name.is_alpha() Letters only
is_alpha_num() this.code.is_alpha_num() Letters + digits
is_alpha_dash() this.slug.is_alpha_dash() Letters + digits + -_
is_numeric() this.pin.is_numeric() Digits only (0-9)
is_number() this.val.is_number() Number string (±, decimal)
is_ascii() this.s.is_ascii() ASCII only
is_printable_ascii() this.s.is_printable_ascii() Printable ASCII (32-126)
is_multibyte() this.s.is_multibyte() Contains multibyte chars
String Checks
Method Usage Description
not_blank() this.name.not_blank() Not empty/whitespace
has_whitespace() this.s.has_whitespace() Contains whitespace
Length & Range
Method Usage Description
len_between(min,max) this.s.len_between(min:3, max:20) String/slice/map length
min_len(n) this.s.min_len(n: 3) Minimum length
max_len(n) this.s.max_len(n: 100) Maximum length
str_len(min,max) this.s.str_len(min:2, max:10) Rune count range
between(min,max) this.age.between(min:0, max:150) Numeric range (inclusive)
Financial
Method Usage Description
luhn_valid() this.pan.luhn_valid() Luhn checksum (card numbers)
Date Functions
Function Usage Description
is_valid_date(d) is_valid_date(this.date) Parseable date string
is_past_date(d) is_past_date(this.birthday) Date is in the past
is_future_date(d) is_future_date(this.expiry) Date is in the future
Comparison Functions
Function Usage Description
in_list(value, candidates) in_list(this.status, ["active","pending"]) Returns true if value is in the list

API Validation

Pre-compile at startup, validate per request with zero compilation overhead:

var userSchema = schemix.MustNew(`{
    username: =~"^[a-zA-Z][a-zA-Z0-9_]{2,20}$"
    email:    string @blob(this.email.is_email())
    password: string @blob(this.password.len_between(min: 8, max: 64))
    age:      int    @blob(this.age.between(min: 13, max: 150))
    role:     "admin" | "user" | "guest"
}`, schemix.WithErrorFormatter(apiFormatter))

func CreateUser(w http.ResponseWriter, req *http.Request) {
    var body map[string]any
    json.NewDecoder(req.Body).Decode(&body)

    r := userSchema.ProcessWithMode(body, schemix.FailAll)
    if !r.Valid {
        status := http.StatusBadRequest
        if r.HasCode(schemix.CodeRequiredMissing) {
            status = http.StatusUnprocessableEntity
        }
        w.WriteHeader(status)
        json.NewEncoder(w).Encode(map[string]any{
            "error":   "validation_failed",
            "details": r.Errors,
        })
        return
    }
    // use r.Output ...
}

Schema Syntax

CUE Constraints
Syntax Meaning Example
string / int / float / bool Type constraint name: string
& >=N & <=M Range age: int & >=0 & <=150
=~"regex" Regex match pan: =~"^[0-9]{16}$"
"a" | "b" Enum currency: "156" | "840"
? Optional field memo?: string
null | type Nullable memo: null | string
{...} Nested struct address: { city: string }
[...{schema}] Array of schema items: [...{id: string}]
@blob() — Bloblang Expressions
Return Type Behavior Example
bool = true Validation passes @blob(this.amount > 0)
bool = false Validation fails (→ E2B01) @blob(this.age >= 18)
Non-bool Computed value → Output @blob(this.first + " " + this.last)
Comma-separated AND — each independent @blob(expr1, expr2)
@meta() — Field Behavior Control
Parameter Type Meaning
priority=N int Execution priority (lower = earlier)
optional flag No error if field missing
conditional flag Conditionally optional (with required_if)
skip_empty flag Skip validation when empty
fail_fast flag Skip remaining rules on failure
omit_if_skip flag Remove from Output when skipped
omit_empty flag Remove from Output when empty
required_if=expr bloblang Conditionally required
skip_if=expr bloblang Conditionally skip
Combined Example
{
    payment_type: "credit" | "debit"
    cvv: string @meta(conditional, required_if=this.payment_type == "credit")

    pan: =~"^[0-9]{16}$" @meta(priority=1)
    luhn_check: bool @blob(this.pan.luhn_valid()) @meta(priority=2)

    memo?: string @meta(optional, omit_empty)
    fee?: number @meta(optional, skip_if=this.payment_type == "debit", omit_if_skip)
}

Custom Functions & Methods

Register custom validation logic using the same API as Bloblang — isolated per Validator:

// Function style: my_func(args...)
v, _ := schemix.New(schema, schemix.WithFunction("check_blacklist",
    func(args ...any) (bloblang.Function, error) {
        pan := args[0].(string)
        return func() (any, error) {
            return !isBlocked(pan), nil
        }, nil
    },
))

// Method style: this.field.my_method()
v, _ := schemix.New(schema, schemix.WithMethod("is_valid_bin",
    func(v any) (any, error) {
        return checkBIN(v.(string)), nil
    },
))

// V2 style with typed parameters (PluginSpec + ParsedParams)
v, _ := schemix.New(schema, schemix.WithFunctionV2("calc_fee",
    bloblang.NewPluginSpec().
        Param(bloblang.NewInt64Param("amount")).
        Param(bloblang.NewFloat64Param("rate")),
    func(args *bloblang.ParsedParams) (bloblang.Function, error) {
        amount, _ := args.GetInt64("amount")
        rate, _ := args.GetFloat64("rate")
        return func() (any, error) { return float64(amount) * rate, nil }, nil
    },
))

// V2 method with params: this.field.method(param: value)
v, _ := schemix.New(schema, schemix.WithMethodV2("in_range",
    bloblang.NewPluginSpec().
        Param(bloblang.NewInt64Param("min")).
        Param(bloblang.NewInt64Param("max")),
    func(args *bloblang.ParsedParams) (bloblang.Method, error) {
        min, _ := args.GetInt64("min")
        max, _ := args.GetInt64("max")
        return func(v any) (any, error) {
            n := v.(int64)
            return n >= min && n <= max, nil
        }, nil
    },
))
FuncMap (Reusable Collections)

For multiple custom functions, use FuncMap to build once and share:

funcs := schemix.NewFuncMap(
    schemix.Func("check_blacklist", blacklistFn),
    schemix.Func("calc_fee", feeFn),
    schemix.Method("mask_pan", maskFn),
    schemix.MethodV2("in_range", rangeSpec, rangeCtor),
)

// Share across validators
v1, _ := schemix.New(schema1, schemix.WithFuncMap(funcs))
v2, _ := schemix.New(schema2, schemix.WithFuncMap(funcs))

Names are validated at construction time (must be snake_case: /^[a-z0-9]+(_[a-z0-9]+)*$/).

Overriding Built-in Validators

Built-in names are protected by default. Use WithOverrideMethod or WithOverrideFunc to explicitly replace them:

// Override a specific built-in method
v, _ := schemix.New(schema,
    schemix.WithOverrideMethod("is_email"),
    schemix.WithMethod("is_email", myStrictEmailFn),
)

// Override a specific built-in function
v, _ := schemix.New(schema,
    schemix.WithOverrideFunc("is_valid_date"),
    schemix.WithFunction("is_valid_date", myDateFn),
)

// Override all — disable conflict checks entirely
v, _ := schemix.New(schema, schemix.WithOverrideAll(), schemix.WithFuncMap(myFuncs))

Note: Function and Method are separate namespaces. Registering a Function named is_email does NOT conflict with the built-in Method is_email.

Error Handling

r := v.Process(data)

r.Valid                              // bool
r.Err()                              // combined error (nil if valid)
r.FirstError()                       // *ValidationError
r.ErrorsByPath("pan")                // []ValidationError
r.ErrorsByCode(schemix.CodeTypeMismatch) // []ValidationError
r.ErrorsByType("cue")                // []ValidationError — filter by layer
r.HasCode(schemix.CodeBizRuleFailed) // bool — quick category check
r.HasErrorsAt("email")              // bool — field-level check
r.ErrorMessages()                    // newline-joined string

Custom Error Messages

Provide a custom ErrorFormatter for i18n or user-facing messages:

v := schemix.MustNew(schema, schemix.WithErrorFormatter(
    func(code schemix.ErrorCode, path, detail string) string {
        return i18n.T("zh-CN", string(code), path)
    },
))

The formatter receives the error code, field path, and default detail message. Return your desired user-facing string. Default behavior (no formatter) passes the raw CUE/Bloblang error message through.

Schema Composition

Use NewFromValue to build validators from pre-compiled CUE values with shared definitions:

ctx := cuecontext.New()
schema := ctx.CompileString(`{
    #PAN:      =~"^[0-9]{16}$"
    #Amount:   int & >0
    #Currency: "CNY" | "USD" | "EUR"

    pan:      #PAN
    amount:   #Amount
    currency: #Currency
}`)

v, err := schemix.NewFromValue(schema)

Schema Introspection

Inspect schema structure at runtime for documentation or UI generation:

fields := v.Fields() // []FieldInfo

for _, f := range fields {
    fmt.Printf("%s: %s (optional=%v, blob=%v)\n", f.Path, f.Type, f.Optional, f.HasBlob)
    for _, child := range f.Children {
        fmt.Printf("  %s: %s\n", child.Path, child.Type)
    }
}

FailMode

Mode Best For Behavior
FailAll Form validation Collect all errors
FailFast API gateway Stop at first error
FailPriority Layered validation Collect CUE + Blob errors in the first failing priority group; skip higher groups
r := v.ProcessWithMode(data, schemix.FailFast)     // 1 error max
r := v.ProcessWithMode(data, schemix.FailAll)      // all errors
r := v.ProcessWithMode(data, schemix.FailPriority) // first failing group only

Processing contracts: CUE and Blob rules in the same FailPriority group are both evaluated. Once that group fails, higher-priority-number groups do not run. Any invalid result has Output == nil. A non-bool @blob() result must satisfy its field schema or validation fails with E2T01.

Error Codes

Format: E{layer}{category}{seq}

Constant Code Layer Meaning
CodeConfigError E0C01 Config Invalid configuration (e.g. undefined FailMode)
CodeFormatMismatch E1F01 CUE Regex format mismatch
CodeTypeMismatch E1T01 CUE Type error
CodeEnumInvalid E1E01 CUE Invalid enum value
CodeRangeViolation E1R01 CUE Range exceeded
CodeRequiredMissing E1M01 CUE Required field missing
CodeArrayElement E1A01 CUE Array element failed
CodeCUEOther E1X01 CUE Other CUE error
CodeBizRuleFailed E2B01 Blob Business rule false
CodeExprExecError E2X01 Blob Expression error
CodeBlobTypeMismatch E2T01 Blob @blob type contract violation
CodeCondRequired E3C01 Meta Conditional required
CodeMetaRuntimeError E3X01 Meta Meta expression runtime error

Bloblang Integration

reg := schemix.NewRegistry()
reg.Register("payment", cueSrc)
env := bloblang.NewEnvironment()
reg.RegisterAllTo(env) // scoped method + function forms

Method form — validates this:

let r = this.validate_schema(name: "payment", mode: "fast")
let r = this.process_schema(name: "payment", mode: "fast")

Function form — dynamic data source:

let r = validate_schema(data: this.payload, name: "payment")
let r = process_schema(data: this.payload, name: "payment")

validate_schema vs process_schema:

Plugin Returns Use When
validate_schema {valid, errors} You only need pass/fail + error details
process_schema {valid, errors, output} You also need computed field values from @blob()

Registry Management

reg := schemix.NewRegistry()       // shared CUE context internally
reg.Register("user", cueSrc)       // compile + store
reg.Has("user")                    // true
reg.List()                         // ["user"]
reg.Len()                          // 1
reg.Unregister("user")             // remove

// Scoped Bloblang registration (recommended)
env := bloblang.NewEnvironment()
reg.RegisterAllTo(env)             // register both method + function forms into env
reg.RegisterMethodsTo(env)         // method form only into env
reg.RegisterFunctionsTo(env)       // function form only into env

// Deprecated global registration (uses GlobalEnvironment; repeated registration returns an error)
reg.RegisterAll()                  // register both method + function forms
reg.RegisterMethods()              // method form only: this.validate_schema(...) / this.process_schema(...)
reg.RegisterFunctions()            // function form only: validate_schema(data: ...) / process_schema(data: ...)

Convenience API

// Construction
v := schemix.MustNew(cueSrc)                    // panic on error
v, _ := schemix.NewWithContext(ctx, src)         // shared CUE context
v, _ := schemix.NewFromValue(cueValue)           // from pre-compiled CUE value

// Options — custom functions
schemix.WithErrorFormatter(fn)                   // custom error messages
schemix.WithFunction(name, ctor)                 // custom function (V1)
schemix.WithFunctionV2(name, spec, ctor)         // custom function (V2)
schemix.WithMethod(name, fn)                     // custom method (V1)
schemix.WithMethodV2(name, spec, ctor)           // custom method (V2)
schemix.WithFuncMap(funcs)                       // inject reusable FuncMap

// Options — override built-in validators
schemix.WithOverrideMethod(names...)             // allow overriding specific built-in methods
schemix.WithOverrideFunc(names...)               // allow overriding specific built-in functions
schemix.WithOverrideAll()                        // disable all conflict checks

// FuncMap construction
funcs := schemix.NewFuncMap(opts...)             // build reusable collection
schemix.Func(name, ctor)                         // FuncMap entry: function (V1)
schemix.FuncV2(name, spec, ctor)                 // FuncMap entry: function (V2)
schemix.Method(name, fn)                         // FuncMap entry: method (V1)
schemix.MethodV2(name, spec, ctor)               // FuncMap entry: method (V2)
funcs.Err()                                      // first validation error (nil if valid)

// Validation (fast path — no Output allocation)
valid, errs := v.Validate(data)

// Processing (validation + computed fields)
r := v.Process(data)
r := v.ProcessWithMode(data, schemix.FailFast)

// Introspection
fields := v.Fields()                             // []FieldInfo

Benchmarks

Apple M4, Go 1.26.5 — 6 fields (3 CUE + 3 @blob):

Operation Time Memory Allocs
New (compile) 441 µs 791 KiB 22275
Process (valid) 7.07 µs 15.04 KiB 125
Process (invalid) 7.67 µs 15.91 KiB 141
Process (nested) 30.24 µs 45.43 KiB 491
Validate (no output) 6.48 µs 14.68 KiB 121
Process (parallel, 10 cores) 4.73 µs 15.04 KiB 125
ValidateFields (fast path) 146.8 ns 0 B 0
Registry.Get 6.05 ns 0 B 0

Simple scalar fields use a Go-native fast path that bypasses CUE entirely, achieving about 175x speedup over the CUE legacy path (146.8ns vs 25.62µs).

Pull requests also run base and head benchmarks on the same CI runner. A statistically significant regression above 5% fails the benchmark gate.

License

MIT

Documentation

Overview

Package schemix provides a schema-driven validation and transformation engine powered by CUE constraints and Bloblang dynamic expressions.

It combines CUE's declarative type system with Bloblang's scripting capability through three annotation layers:

  • CUE native constraints: types, regex, enums, ranges, nested structs, arrays
  • @blob() dynamic expressions: Bloblang syntax for validation (bool) and computed fields
  • @meta() field behavior control: priority, optional, conditional, skip/omit rules

Quick Start

v := schemix.MustNew(`{
    name:  string
    email: string @blob(this.email.is_email())
    age:   int    @blob(this.age.between(min: 0, max: 150))
    pan:   =~"^[0-9]{16}$"
    luhn:  bool   @blob(this.pan.luhn_valid())
}`)

r := v.Process(map[string]any{
    "name": "Alice", "email": "alice@test.com", "age": int64(30),
    "pan": "4111111111111111",
})
if r.Valid {
    // use r.Output
}

Built-in Validation Methods

Every Validator automatically includes 37+ built-in validation methods callable in @blob() expressions, covering common format checks:

  • String format: is_email, is_url, is_full_url, is_uuid/3/4/5, is_ip/v4/v6, is_cidr, is_mac, is_dns_name, is_cn_mobile, is_json, is_base64, is_hex, is_hex_color, is_rgb_color, is_data_uri, is_latitude, is_longitude, is_isbn10, is_isbn13
  • Character type: is_alpha, is_alpha_num, is_alpha_dash, is_numeric, is_number, is_ascii, is_printable_ascii, is_multibyte
  • String checks: not_blank, has_whitespace
  • Length: len_between(min,max), min_len(n), max_len(n), str_len(min,max)
  • Numeric: between(min,max)
  • Financial: luhn_valid
  • Date functions: is_valid_date, is_past_date, is_future_date

Usage in schema:

email: string @blob(this.email.is_email())
pan:   string @blob(this.pan.luhn_valid())
age:   int    @blob(this.age.between(min: 0, max: 150))
name:  string @blob(this.name.len_between(min: 2, max: 50))

Fail Modes

Three strategies control error collection behavior:

  • FailAll: collect all errors (default, best for form validation)
  • FailFast: stop at first error (best for API gateways)
  • FailPriority: priority-group isolation (p1 failure skips p2+)

Error Handling

Result provides multiple ways to inspect errors:

r := v.Process(data)
r.Valid                         // bool
r.Err()                         // combined error (nil if valid)
r.FirstError()                  // *ValidationError
r.ErrorsByPath("pan")           // []ValidationError
r.ErrorsByCode(CodeTypeMismatch)// []ValidationError
r.ErrorsByType("cue")           // []ValidationError — filter by layer
r.HasCode(CodeBizRuleFailed)    // bool — quick check
r.HasErrorsAt("email")          // bool — field-level check

Custom Error Messages (i18n)

Provide a custom ErrorFormatter to generate user-facing messages:

v := schemix.MustNew(schema, schemix.WithErrorFormatter(func(code ErrorCode, path, detail string) string {
    return myI18n.Translate("zh-CN", string(code), path)
}))

Custom Functions and Methods

Register custom validation logic using the same API as Bloblang:

// Function style (called as: my_func(args...))
v, _ := schemix.New(schema, schemix.WithFunction("check_blacklist",
    func(args ...any) (bloblang.Function, error) {
        pan := args[0].(string)
        return func() (any, error) { return !isBlocked(pan), nil }, nil
    },
))

// Method style (called as: this.field.my_method())
v, _ := schemix.New(schema, schemix.WithMethod("is_valid_bin",
    func(v any) (any, error) {
        return checkBIN(v.(string)), nil
    },
))

// V2 style with typed parameters (same as bloblang.RegisterFunctionV2)
v, _ := schemix.New(schema, schemix.WithFunctionV2("calc_fee",
    bloblang.NewPluginSpec().
        Param(bloblang.NewInt64Param("amount")).
        Param(bloblang.NewFloat64Param("rate")),
    func(args *bloblang.ParsedParams) (bloblang.Function, error) {
        amount, _ := args.GetInt64("amount")
        rate, _ := args.GetFloat64("rate")
        return func() (any, error) { return float64(amount) * rate, nil }, nil
    },
))

Custom functions are isolated per Validator — they do not leak to other instances.

Schema Composition

Use NewFromValue to build validators from pre-compiled CUE values, enabling schema reuse through CUE definitions:

ctx := cuecontext.New()
schema := ctx.CompileString(`{
    #PAN: =~"^[0-9]{16}$"
    pan:    #PAN
    amount: int & >0
}`)
v, _ := schemix.NewFromValue(schema)

Schema Introspection

Inspect schema structure at runtime for documentation or UI generation:

fields := v.Fields() // []FieldInfo{Name, Path, Type, Optional, HasBlob, Children}

Performance

Schemix uses a Go-native fast path for simple constraints (type, regex, range, enum), bypassing CUE evaluation entirely. Typical Process latency is 2-3µs for schemas with scalar fields.

Bloblang Pipeline Integration

Register schemas into a Registry for use within Benthos/Redpanda Connect pipelines:

reg := schemix.NewRegistry()
reg.Register("payment", cueSrc)
reg.RegisterAll() // registers both method and function forms

Then use in Bloblang mappings:

let r = this.process_schema(name: "payment", mode: "fast")
let r = validate_schema(data: this.payload, name: "payment")

Thread Safety

Validator is safe for concurrent use after construction. Registry uses sync.RWMutex for concurrent Register/Get/Unregister operations.

Package schemix provides a schema-driven validation and transformation engine powered by CUE constraints and Bloblang dynamic expressions.

It combines CUE's declarative type system (@blob() for dynamic rules, @meta() for field behavior control) with recursive multi-level validation, structured error codes, and configurable fail strategies.

Index

Constants

View Source
const (
	ModeAll      = "all"
	ModeFast     = "fast"
	ModePriority = "priority"
)

Mode string values for FailMode selection (user-facing).

View Source
const (
	TypeCUE      = "cue"
	TypeBloblang = "bloblang"
	TypeMeta     = "meta"
	TypeConfig   = "config"
)

Validation error type identifiers (user-facing, for filtering ValidationError.Type).

Variables

This section is empty.

Functions

func Has added in v0.1.4

func Has(name string) bool

Has reports whether a Validator with the given name is globally registered.

func Len added in v0.1.4

func Len() int

Len returns the number of globally registered Validators.

func List added in v0.1.4

func List() []string

List returns the names of all globally registered Validators.

func MustRegister added in v0.1.4

func MustRegister(name string, v *Validator)

MustRegister stores a pre-compiled Validator globally under name. Panics if name already exists (conflict).

func Register added in v0.1.4

func Register(name string, v *Validator)

Register stores a pre-compiled Validator globally under name. If name already exists, it is silently replaced (use MustRegister to reject duplicates).

func Unregister added in v0.1.4

func Unregister(name string) bool

Unregister removes a globally registered Validator by name. Returns true if it existed and was removed.

Types

type ErrorCode

type ErrorCode string

ErrorCode is a structured error identifier with format E{layer}{category}{seq}.

Layer 1: CUE structural/type validation
Layer 2: Bloblang business rules
Layer 3: Meta control violations
const (
	// Layer 1: CUE structural validation
	CodeFormatMismatch  ErrorCode = "E1F01" // regex format mismatch
	CodeTypeMismatch    ErrorCode = "E1T01" // type conflict
	CodeEnumInvalid     ErrorCode = "E1E01" // enum value not allowed
	CodeRangeViolation  ErrorCode = "E1R01" // numeric range exceeded
	CodeRequiredMissing ErrorCode = "E1M01" // required field missing
	CodeArrayElement    ErrorCode = "E1A01" // array element validation failed
	CodeCUEOther        ErrorCode = "E1X01" // other CUE error

	// Layer 2: Bloblang business rules
	CodeBizRuleFailed    ErrorCode = "E2B01" // business rule returned false
	CodeExprExecError    ErrorCode = "E2X01" // expression runtime error
	CodeBlobTypeMismatch ErrorCode = "E2T01" // @blob type contract violation (WU2)

	// Layer 3: Meta control
	CodeCondRequired     ErrorCode = "E3C01" // conditional required not met
	CodeMetaRuntimeError ErrorCode = "E3X01" // meta expression runtime error (required_if/skip_if Query failure)

	// Layer 0: Configuration / invocation errors
	CodeConfigError ErrorCode = "E0C01" // invalid configuration (e.g. undefined FailMode)
)

type ErrorFormatter added in v0.1.2

type ErrorFormatter func(code ErrorCode, path string, detail string) string

ErrorFormatter customizes the human-readable message in ValidationError. It receives the error code, field path, and the default detail message (which is the raw CUE error or expression text). Return the desired user-facing message string.

Example (i18n):

func myFormatter(code ErrorCode, path, detail string) string {
    return i18n.T("zh-CN", string(code), path)
}

type FailMode

type FailMode int

FailMode controls how errors are collected during validation.

const (
	// FailAll collects all errors before returning (default, good for forms).
	FailAll FailMode = iota
	// FailFast stops at the first error (good for gateways).
	FailFast
	// FailPriority stops when the current priority group has errors.
	FailPriority
)

type FieldInfo added in v0.1.2

type FieldInfo struct {
	Name     string      `json:"name"`               // field name
	Path     string      `json:"path"`               // full dot-path
	Type     string      `json:"type"`               // "string", "int", "float", "bool", "struct", "list", "number", "unknown"
	Optional bool        `json:"optional"`           // whether the field is optional
	HasBlob  bool        `json:"has_blob"`           // has @blob() annotation
	Children []FieldInfo `json:"children,omitempty"` // nested struct fields
}

FieldInfo describes a field in the schema. Returned by Validator.Fields(). This is useful for generating documentation, API specs, or UI forms.

type FuncMap added in v0.1.2

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

FuncMap is a reusable collection of custom functions and methods that can be shared across multiple Validators. Build it once, inject everywhere.

Example:

funcs := schemix.NewFuncMap(
    schemix.Func("check_blacklist", myBlacklistFn),
    schemix.Func("calc_fee", myFeeFn),
    schemix.Method("is_valid_bin", myBinFn),
    schemix.MethodV2("in_range", rangeSpec, rangeCtor),
)

v1, _ := schemix.New(schema1, schemix.WithFuncMap(funcs))
v2, _ := schemix.New(schema2, schemix.WithFuncMap(funcs))

func NewFuncMap added in v0.1.2

func NewFuncMap(opts ...FuncMapOption) *FuncMap

NewFuncMap creates a FuncMap from the given registration options. Returns nil error in FuncMap.Err() if all names are valid.

func (*FuncMap) Err added in v0.1.2

func (m *FuncMap) Err() error

Err returns the first validation error encountered during FuncMap construction (e.g. invalid function name). Returns nil if all registrations are valid.

type FuncMapOption added in v0.1.2

type FuncMapOption func(*FuncMap)

FuncMapOption defines a registration entry for NewFuncMap.

func Func added in v0.1.2

Func registers a custom function (V1 style). In schema: name(args...)

func FuncV2 added in v0.1.2

FuncV2 registers a custom function with typed parameters (V2 style). In schema: name(param1: value1, param2: value2)

func Method added in v0.1.2

func Method(name string, fn bloblang.Method) FuncMapOption

Method registers a custom method (V1 style). In schema: this.field.name()

func MethodV2 added in v0.1.2

MethodV2 registers a custom method with typed parameters (V2 style). In schema: this.field.name(param1: value1, param2: value2)

type Option added in v0.1.2

type Option func(*validatorConfig)

Option configures a Validator during construction.

func WithErrorFormatter added in v0.1.2

func WithErrorFormatter(f ErrorFormatter) Option

WithErrorFormatter sets a custom error message formatter. When set, all ValidationError.Message values will be generated by this function instead of the default English messages.

func WithFuncMap added in v0.1.2

func WithFuncMap(m *FuncMap) Option

WithFuncMap injects a pre-built FuncMap into the Validator. If the FuncMap has a validation error (e.g. invalid name), New() will return that error.

func WithFunction added in v0.1.2

func WithFunction(name string, fn bloblang.FunctionConstructor) Option

WithFunction registers a custom function using Bloblang's FunctionConstructor signature. This is the same signature as bloblang.RegisterFunction — a factory that receives arguments and returns a Function closure.

Example:

v, _ := schemix.New(schema, schemix.WithFunction("is_even", func(args ...any) (bloblang.Function, error) {
    n, ok := args[0].(int64)
    if !ok {
        return nil, fmt.Errorf("is_even requires int64")
    }
    return func() (any, error) {
        return n%2 == 0, nil
    }, nil
}))

In schema: check: bool @blob(is_even(this.amount))

func WithFunctionV2 added in v0.1.2

func WithFunctionV2(name string, spec *bloblang.PluginSpec, ctor bloblang.FunctionConstructorV2) Option

WithFunctionV2 registers a custom function using a PluginSpec for typed parameters. This matches Bloblang's RegisterFunctionV2 signature exactly.

Example:

v, _ := schemix.New(schema, schemix.WithFunctionV2("calculate_fee",
    bloblang.NewPluginSpec().
        Param(bloblang.NewInt64Param("amount")).
        Param(bloblang.NewFloat64Param("rate")),
    func(args *bloblang.ParsedParams) (bloblang.Function, error) {
        amount, _ := args.GetInt64("amount")
        rate, _ := args.GetFloat64("rate")
        return func() (any, error) {
            return float64(amount) * rate, nil
        }, nil
    },
))

func WithMethod added in v0.1.2

func WithMethod(name string, fn bloblang.Method) Option

WithMethod registers a custom method using the simple style. Methods are called on a target value: this.field.my_method()

Example:

v, _ := schemix.New(schema, schemix.WithMethod("is_valid_luhn", func(v any) (any, error) {
    s := v.(string)
    return luhnCheck(s), nil
}))

In schema: check: bool @blob(this.pan.is_valid_luhn())

func WithMethodV2 added in v0.1.2

func WithMethodV2(name string, spec *bloblang.PluginSpec, ctor bloblang.MethodConstructorV2) Option

WithMethodV2 registers a custom method using a PluginSpec for typed parameters. This matches Bloblang's RegisterMethodV2 signature exactly.

Example:

v, _ := schemix.New(schema, schemix.WithMethodV2("has_prefix_any",
    bloblang.NewPluginSpec().
        Param(bloblang.NewStringParam("prefixes").Description("comma-separated prefixes")),
    func(args *bloblang.ParsedParams) (bloblang.Method, error) {
        prefixes, _ := args.GetString("prefixes")
        parts := strings.Split(prefixes, ",")
        return func(v any) (any, error) {
            s := v.(string)
            for _, p := range parts {
                if strings.HasPrefix(s, p) { return true, nil }
            }
            return false, nil
        }, nil
    },
))

func WithOverrideAll added in v0.1.2

func WithOverrideAll() Option

WithOverrideAll disables all built-in conflict checks — any name can be registered regardless of whether it conflicts with a built-in.

func WithOverrideFunc added in v0.1.2

func WithOverrideFunc(names ...string) Option

WithOverrideFunc allows overriding specific built-in functions by name.

func WithOverrideMethod added in v0.1.2

func WithOverrideMethod(names ...string) Option

WithOverride explicitly allows overriding one or more built-in validators in their respective namespace. Use WithOverrideMethod / WithOverrideFunc for namespace-specific overrides, or WithOverrideAll to allow overriding everything.

Example:

// Override specific built-in methods
schemix.WithOverrideMethod("is_email", "luhn_valid")

// Override specific built-in functions
schemix.WithOverrideFunc("is_valid_date")

// Override everything — no conflict checks at all
schemix.WithOverrideAll()

type Processable added in v0.1.4

type Processable interface {
	ToMap() map[string]any
}

Processable is an interface that types can implement to provide custom map conversion logic for use with ProcessValue/ValidateValue. This gives callers full control over how their data is represented.

type Registry

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

Registry is a thread-safe validator registry for use with Bloblang methods.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty validator registry with a shared CUE context.

func (*Registry) Get

func (r *Registry) Get(name string) (*Validator, bool)

Get retrieves a validator by name.

func (*Registry) Has

func (r *Registry) Has(name string) bool

Has reports whether a validator with the given name is registered.

func (*Registry) Len

func (r *Registry) Len() int

Len returns the number of registered validators.

func (*Registry) List

func (r *Registry) List() []string

List returns the names of all registered validators.

func (*Registry) Register

func (r *Registry) Register(name, cueSrc string) error

Register compiles and stores a named validator from a CUE schema string. It uses the registry's shared CUE context for efficient memory usage.

func (*Registry) RegisterAll deprecated

func (r *Registry) RegisterAll() error

RegisterAll registers both method and function forms of validate_schema and process_schema into the global environment.

Deprecated: Use RegisterAllTo with an explicit environment.

func (*Registry) RegisterAllTo added in v0.1.4

func (r *Registry) RegisterAllTo(env *bloblang.Environment) error

RegisterAllTo registers both method and function forms into a specific environment. Ownership is enforced.

func (*Registry) RegisterFunctions deprecated

func (r *Registry) RegisterFunctions() error

RegisterFunctions registers "validate_schema" and "process_schema" as Bloblang functions into the global environment.

Deprecated: Use RegisterFunctionsTo with an explicit environment.

func (*Registry) RegisterFunctionsTo added in v0.1.4

func (r *Registry) RegisterFunctionsTo(env *bloblang.Environment) error

RegisterFunctionsTo registers "validate_schema" and "process_schema" as Bloblang functions into a specific environment. Ownership is enforced.

func (*Registry) RegisterMethods deprecated

func (r *Registry) RegisterMethods() error

RegisterMethods registers "validate_schema" and "process_schema" Bloblang methods into the global environment.

Deprecated: Use RegisterMethodsTo with an explicit environment.

func (*Registry) RegisterMethodsTo added in v0.1.4

func (r *Registry) RegisterMethodsTo(env *bloblang.Environment) error

RegisterMethodsTo registers "validate_schema" and "process_schema" as Bloblang methods into a specific environment. Ownership is enforced: the same env cannot be registered by a different Registry.

func (*Registry) Unregister

func (r *Registry) Unregister(name string) bool

Unregister removes a named validator from the registry. Returns true if the validator existed and was removed.

type Result

type Result struct {
	Valid  bool              `json:"valid"`
	Errors []ValidationError `json:"errors"`
	Output map[string]any    `json:"output"`
}

Result holds the output of a Process call.

func ProcessStruct added in v0.1.4

func ProcessStruct[T any](v *Validator, data T) Result

ProcessStruct validates and processes a struct value with compile-time type safety. The struct is converted to map[string]any via JSON serialization (respects json tags). For hot paths, implement Processable or pass map[string]any directly.

func ProcessStructWithMode added in v0.1.4

func ProcessStructWithMode[T any](v *Validator, data T, mode FailMode) Result

ProcessStructWithMode is like ProcessStruct but accepts a FailMode.

func ProcessWith added in v0.1.4

func ProcessWith(name string, data any) Result

ProcessWith validates and processes data using a globally registered Validator. Accepts any supported input type (map, struct, JSON bytes, Processable). Returns a config error if the name is not registered.

func ProcessWithMode added in v0.1.4

func ProcessWithMode(name string, data any, mode FailMode) Result

ProcessWithMode validates and processes data using a named global Validator with mode.

func (Result) Err

func (r Result) Err() error

Err returns nil if validation passed, or a combined error from all validation failures. This is convenient for Go-style error checking:

if err := v.Process(data).Err(); err != nil { ... }

func (Result) ErrorMessages

func (r Result) ErrorMessages() string

ErrorMessages returns all error messages joined by newline.

func (Result) ErrorsByCode added in v0.1.2

func (r Result) ErrorsByCode(code ErrorCode) []ValidationError

ErrorsByCode returns all errors matching the specified error code.

func (Result) ErrorsByPath

func (r Result) ErrorsByPath(path string) []ValidationError

ErrorsByPath returns all errors for a specific field path.

func (Result) ErrorsByType added in v0.1.2

func (r Result) ErrorsByType(typ string) []ValidationError

ErrorsByType returns all errors of the specified type ("cue", "bloblang", "meta").

func (Result) FirstError

func (r Result) FirstError() *ValidationError

FirstError returns the first validation error, or nil if validation passed.

func (Result) HasCode added in v0.1.2

func (r Result) HasCode(code ErrorCode) bool

HasCode reports whether any error has the specified error code.

func (Result) HasErrorsAt added in v0.1.2

func (r Result) HasErrorsAt(path string) bool

HasErrorsAt reports whether there are any errors at the specified field path.

type ValidationError

type ValidationError struct {
	Code    ErrorCode `json:"code"`    // structured error code
	Path    string    `json:"path"`    // field path (e.g. "merchant.country")
	Type    string    `json:"type"`    // "cue", "bloblang", or "meta"
	Message string    `json:"message"` // human-readable description
}

ValidationError represents a single validation failure.

func ValidateStruct added in v0.1.4

func ValidateStruct[T any](v *Validator, data T) (bool, []ValidationError)

ValidateStruct validates a struct value with compile-time type safety (no Output).

func ValidateWith added in v0.1.4

func ValidateWith(name string, data any) (bool, []ValidationError)

ValidateWith validates data using a globally registered Validator (no Output).

func (ValidationError) Error

func (e ValidationError) Error() string

Error implements the error interface for ValidationError.

type Validator

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

Validator is a schema-driven validation and transformation engine. It combines CUE static constraints with Bloblang dynamic expressions, supporting recursive multi-level validation, structured error codes, and configurable fail strategies.

Validator is safe for concurrent use after construction.

func Get added in v0.1.4

func Get(name string) (*Validator, bool)

Get retrieves a globally registered Validator by name. Returns (nil, false) if name is not registered.

func MustGet added in v0.1.4

func MustGet(name string) *Validator

MustGet retrieves a globally registered Validator by name. Panics if name is not registered.

func MustNew

func MustNew(cueSrc string, opts ...Option) *Validator

MustNew is like New but panics on error. Useful for package-level initialization with schema literals.

func New

func New(cueSrc string, opts ...Option) (*Validator, error)

New creates a Validator from a CUE schema string. The schema may use @blob() for dynamic expressions and @meta() for field controls.

func NewFromValue added in v0.1.2

func NewFromValue(schema cue.Value, opts ...Option) (*Validator, error)

NewFromValue creates a Validator from a pre-compiled CUE value. This enables schema composition by allowing users to build complex schemas using CUE's native import/definition mechanisms and pass the result directly.

Example:

ctx := cuecontext.New()
defs := ctx.CompileString(`#PAN: =~"^[0-9]{16}$"`)
schema := ctx.CompileString(`{ pan: #PAN, amount: int & >0 }`, cue.Scope(defs))
v, err := schemix.NewFromValue(schema)

func NewWithContext

func NewWithContext(ctx *cue.Context, cueSrc string, opts ...Option) (*Validator, error)

NewWithContext creates a Validator from a CUE schema string using a shared CUE context. This is more efficient when creating many validators, as they can share compilation state.

func (*Validator) Fields added in v0.1.2

func (v *Validator) Fields() []FieldInfo

Fields returns the schema's field descriptors for runtime introspection. This is useful for generating documentation, API specs, or UI forms.

func (*Validator) Process

func (v *Validator) Process(data map[string]any) Result

Process performs validation and value computation using the default FailAll mode.

func (*Validator) ProcessValue added in v0.1.4

func (v *Validator) ProcessValue(data any) Result

ProcessValue validates and processes any supported input type. Accepts: map[string]any, struct, *struct, []byte (JSON), or Processable. Returns a config error Result if the input type is unsupported or conversion fails.

func (*Validator) ProcessValueWithMode added in v0.1.4

func (v *Validator) ProcessValueWithMode(data any, mode FailMode) Result

ProcessValueWithMode validates and processes any supported input type with the given FailMode. Accepts: map[string]any, struct, *struct, []byte (JSON), or Processable.

func (*Validator) ProcessWithMode

func (v *Validator) ProcessWithMode(data map[string]any, mode FailMode) Result

ProcessWithMode performs validation and value computation with the specified FailMode.

func (*Validator) Validate

func (v *Validator) Validate(data map[string]any) (bool, []ValidationError)

Validate performs validation only and returns (valid, errors). Unlike Process, it skips deepCopy and Output construction for better performance.

func (*Validator) ValidateValue added in v0.1.4

func (v *Validator) ValidateValue(data any) (bool, []ValidationError)

ValidateValue performs validation (no Output) on any supported input type. Accepts: map[string]any, struct, *struct, []byte (JSON), or Processable.

Directories

Path Synopsis
internal
ci/benchgate
Package benchgate provides a fail-closed benchmark regression gate.
Package benchgate provides a fail-closed benchmark regression gate.
ci/benchgate/cmd command
Command benchgate fails CI when benchstat reports significant regressions.
Command benchgate fails CI when benchstat reports significant regressions.

Jump to

Keyboard shortcuts

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