queryfy

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Feb 22, 2026 License: Apache-2.0 Imports: 5 Imported by: 2

README

Queryfy

Validate, Query, and Transform Dynamic Data in Go

Queryfy is a Go library for working with map[string]interface{} data. It provides schema validation, path-based querying, and data transformation in a single package — designed for JSON APIs, configuration files, and any scenario where data arrives as untyped maps rather than structs.

Zero external dependencies. 499 tests. 80%+ coverage.

Features

Validation: schema definitions via fluent builder API, strict and loose modes, composite logic (AND/OR/NOT), custom validators, async validators with context cancellation, dependent field validation, DateTime with format/range/business-day constraints, clear error messages with exact field paths.

Querying: dot-notation path expressions, array indexing, wildcard expansion (items[*].price), iteration methods (Each, Collect, ValidateEach).

Transformation: 31 built-in transformers (string, number, date, phone), custom transformers, transform chaining, validate-and-transform in a single pass.

Schema tooling: introspection API (read constraints, walk fields, compare schemas), JSON Schema import and export with round-trip verification, schema compilation for 25–53% faster validation, custom format registry, type metadata.

Installation

go get github.com/ha1tch/queryfy

Requires Go 1.22 or later.

Quick Start

package main

import (
    "fmt"
    "log"

    qf "github.com/ha1tch/queryfy"
    "github.com/ha1tch/queryfy/builders"
)

func main() {
    // Define a schema
    schema := builders.Object().
        Field("customerId", builders.String().Required()).
        Field("amount", builders.Number().Min(0).Required()).
        Field("items", builders.Array().Of(
            builders.Object().
                Field("productId", builders.String().Required()).
                Field("quantity", builders.Number().Min(1)).
                Field("price", builders.Number().Min(0)),
        ).MinItems(1))

    // Data to validate
    order := map[string]interface{}{
        "customerId": "CUST-123",
        "amount":     150.50,
        "items": []interface{}{
            map[string]interface{}{
                "productId": "PROD-456",
                "quantity":  float64(2),
                "price":     75.25,
            },
        },
    }

    // Validate
    if err := qf.Validate(order, schema); err != nil {
        log.Fatal(err)
    }

    // Query
    price, _ := qf.Query(order, "items[0].price")
    fmt.Printf("First item price: $%.2f\n", price)
}

Error Messages

Queryfy produces clear, actionable errors with exact paths:

validation failed:
  customer.email: must be a valid email address, got "not-an-email"
  items[0].quantity: must be >= 1, got 0
  items[2].productId: field is required
  payment.method: must be one of: CARD, CASH, DIGITAL_WALLET, got "CHECK"

Documentation

Usage guide:

  • MANUAL.md — Full API reference with examples for every feature

Design and rationale:

Philosophy:

En Español:

Research:

Performance

Queryfy schemas are defined once and reused. Query paths are cached after first use. The library uses type-switch optimisation instead of reflection and has no external dependencies.

For hot paths, Compile() pre-processes a schema into an optimised form that eliminates per-validation overhead. The compiled schema is a drop-in replacement:

var orderSchema = qf.Compile(builders.Object().
    Field("id", builders.String().Required()).
    Field("amount", builders.Number().Min(0)))

Measured with go test -bench=. -benchmem -count=3 on Xeon Platinum 8581C:

Benchmark Raw Compiled Delta
Object (valid) 1,360 ns/op · 0 B · 0 allocs 1,020 ns/op · 0 B · 0 allocs -25% time
Object (invalid) 3,000 ns/op · 427 B · 24 allocs 2,500 ns/op · 395 B · 22 allocs -17% time · -8% allocs
Number 40 ns/op · 0 B · 0 allocs 22 ns/op · 0 B · 0 allocs -44% time
Enum (10 values) 47 ns/op · 0 B · 0 allocs 22 ns/op · 0 B · 0 allocs -53% time
Array (20 items) 2,080 ns/op · 96 B · 21 allocs 2,140 ns/op · 96 B · 21 allocs flat (element validation dominates)
String (email) 395 ns/op · 0 B · 0 allocs 411 ns/op · 0 B · 0 allocs flat (regex dominates)
Compile cost 4,520 ns/op · 2,840 B · 57 allocs one-time; amortised after ~4 validations

Subprojects

Superjsonic — a fast JSON pre-validation parser that will be merged into queryfy in a future release.

Roadmap

v0.1.0 (Released)
  • [✓] Schema validation with builder API
  • [✓] Basic path queries (dot notation, array indexing)
  • [✓] Composite schemas (AND/OR/NOT)
  • [✓] Strict and loose validation modes
  • [✓] Custom validators
  • [✓] Clear error messages with paths
v0.2.0 (Released)
  • [✓] Data transformation pipeline with builder pattern
  • [✓] DateTime validation with comprehensive format support
  • [✓] Dependent field validation for conditional requirements
  • [✓] Phone normalization for multiple countries
  • [✓] Built-in transformers (string, number, date operations)
  • [✓] Transform chaining with .Add() method
v0.3.0 (Current Release)

v0.3.0 diverged from the original plan in response to real-world usage. Development of a downstream application revealed that queryfy's schemas were opaque at runtime: there was no way to inspect constraints, compare schemas, traverse fields programmatically, or convert between queryfy and external schema formats. These capabilities were prerequisites for building tooling on top of queryfy — schema-driven form generation, API contract validation, migration tooling, or integration with systems that already use JSON Schema.

The introspection API was built first, then JSON Schema import/export on top of it, validated with round-trip tests. Once the introspection layer was in place, the originally planned query features and schema compilation were straightforward additions.

Schema introspection and tooling:

  • [✓] Schema introspection API (GetField, RequiredFieldNames, RangeConstraints, etc.)
  • [✓] Schema equality and hashing (structural comparison, canonical hashing)
  • [✓] Schema diff (field-level change detection between schema versions)
  • [✓] Field walker (recursive traversal with visitor callbacks)
  • [✓] Custom format registry (RegisterFormat, LookupFormat)
  • [✓] Custom type metadata (Meta, GetMeta, AllMeta on all schema types)

Interoperability:

  • [✓] JSON Schema import (builders/jsonschema.FromJSON) — Draft 2020-12 / Draft 7 subset
  • [✓] JSON Schema export (builders/jsonschema.ToJSON) with round-trip verification
  • [✓] Controllable additional properties (AllowAdditional decoupled from validation mode)

Query and iteration:

  • [✓] Wildcard queries (items[*].price, nested customers[*].orders[*].total)
  • [✓] Iteration methods (Each, Collect, ValidateEach)
  • [✓] Enhanced transform API (.Transform() convenience methods on leaf schemas)

Async validation:

  • [✓] Context-aware async validators (AsyncCustom, ValidateAndTransformAsync)
  • [✓] Cancellation propagation through field and object-level async validators

Infrastructure:

  • [✓] Schema compilation (Compile flattens constraint checks into a single function-call chain)
  • [✓] GitHub Actions CI (test matrix, lint, example builds)
  • [✓] golangci-lint configuration
  • [✓] 80%+ test coverage across all packages
v0.4.0 (Planned)

The items deferred to v0.4.0 require semantic changes to how queryfy processes data, not just how it reads or describes schemas.

Query layer enhancements:

  • Filter expressions (items[?price > 100]) — predicate evaluation inside brackets
  • Aggregation functions (sum(), avg(), count()) — operates on wildcard results

Data transformation:

  • Data transformation in loose mode (modify actual data, not just validate type compatibility) — requires careful design around coercion policy and failure reporting
v0.5.0 (Future)
  • Struct conversion (ToStruct, ValidateToStruct)
  • Pre-validation of raw JSON bytes before unmarshalling
  • Schema Explain() method for human-readable rule descriptions

License

Copyright 2025-2026 h@ual.fi

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Documentation

Overview

Package queryfy provides schema validation and querying for dynamic data in Go.

Queryfy is designed to work with map[string]interface{} data structures commonly found in JSON APIs, configuration files, and other dynamic contexts.

Basic usage:

schema := queryfy.Object().
	Field("id", queryfy.String().Required()).
	Field("amount", queryfy.Number().Min(0))

data := map[string]interface{}{
	"id": "order-123",
	"amount": 99.99,
}

if err := queryfy.Validate(data, schema); err != nil {
	// Handle validation error
}

// Query the data
amount, _ := queryfy.Query(data, "amount")

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Collect added in v0.3.0

func Collect(data interface{}, queryStr string, fn func(value interface{}) (interface{}, error)) ([]interface{}, error)

Collect executes a query path and applies a transform function to each matched element, returning the collected results. The path may include wildcards.

func ConvertStringToNumber

func ConvertStringToNumber(str string) (float64, bool)

ConvertStringToNumber attempts to convert a string to a number. Returns the number and true if successful, 0 and false otherwise.

func ConvertToString

func ConvertToString(value interface{}) (string, bool)

ConvertToString attempts to convert a value to string. Returns the string and true if successful, empty string and false otherwise.

func Each added in v0.3.0

func Each(data interface{}, queryStr string, fn func(index int, value interface{}) error) error

Each executes a callback for each element matched by a query path. The path may include wildcards (e.g., "items[*]"). If no wildcard is present, the callback is called once with the single matched value.

The callback receives the index and value of each element. Return a non-nil error from the callback to stop iteration early.

func MustValidate

func MustValidate(data interface{}, schema Schema)

MustValidate validates data against a schema and panics on error. This is useful in initialization code where validation errors are fatal.

func Query

func Query(data interface{}, queryStr string) (interface{}, error)

Query executes a query against the data and returns the result. Supports dot notation and array indexing:

  • "name" - returns the value of field "name"
  • "user.email" - returns nested field value
  • "items[0]" - returns first element of array
  • "items[0].price" - returns field from array element

func Validate

func Validate(data interface{}, schema Schema) error

Validate validates data against a schema. Returns a ValidationError containing all validation failures, or nil if valid.

func ValidateAndTransform added in v0.3.0

func ValidateAndTransform(data interface{}, schema Schema, mode ValidationMode) (interface{}, error)

ValidateAndTransform validates data against a schema and returns the transformed result. If the schema does not support transformation, it falls back to plain validation and returns the original data.

func ValidateAndTransformAsync added in v0.3.0

func ValidateAndTransformAsync(goCtx context.Context, data interface{}, schema Schema, mode ValidationMode) (interface{}, error)

ValidateAndTransformAsync validates data with async validators and returns the transformed result. If the schema has no async validators, it falls back to synchronous ValidateAndTransform.

func ValidateEach added in v0.3.0

func ValidateEach(data interface{}, queryStr string, schema Schema, mode ValidationMode) error

ValidateEach executes a query path, then validates each matched element against the given schema. Returns a ValidationError with paths that include the element index (e.g., "[0]: field is required").

The mode parameter controls strict/loose validation.

func ValidateValue

func ValidateValue(value interface{}, expectedType SchemaType, ctx *ValidationContext) bool

ValidateValue is a helper function that validates a single value against its expected type. It handles type checking and conversion based on the validation mode.

func ValidateWithMode

func ValidateWithMode(data interface{}, schema Schema, mode ValidationMode) error

ValidateWithMode validates data against a schema with a specific validation mode.

func WrapError

func WrapError(err error, path string) error

WrapError wraps an error with field path information. If the error is already a ValidationError, it prepends the path to all field errors. Otherwise, it creates a new ValidationError with a single field error.

Types

type AsyncTransformableSchema added in v0.3.0

type AsyncTransformableSchema interface {
	TransformableSchema
	// HasAsyncValidators reports whether this schema has any async validators.
	HasAsyncValidators() bool
	// ValidateAndTransformAsync runs sync validation and transformations first.
	// If sync validation passes, it then runs async validators sequentially.
	ValidateAndTransformAsync(goCtx context.Context, value interface{}, ctx *ValidationContext) (interface{}, error)
}

AsyncTransformableSchema represents a schema that supports async validation with transformations. Async validators run only after sync validation passes.

type AsyncValidatorFunc added in v0.3.0

type AsyncValidatorFunc func(ctx context.Context, value interface{}) error

AsyncValidatorFunc is a function that validates a value asynchronously. It receives a context.Context for cancellation and timeout support, and should return an error if validation fails, nil otherwise. Async validators are only invoked by the async validation methods; sync validation silently ignores them.

type BaseSchema

type BaseSchema struct {
	SchemaType SchemaType // Changed from schemaType to SchemaType to make it accessible
	// contains filtered or unexported fields
}

BaseSchema provides common functionality for all schema types. It should be embedded in concrete schema implementations.

func (*BaseSchema) AllMeta added in v0.3.0

func (s *BaseSchema) AllMeta() map[string]interface{}

AllMeta returns all metadata, or nil if none is set.

func (*BaseSchema) CheckRequired

func (s *BaseSchema) CheckRequired(value interface{}, ctx *ValidationContext) bool

CheckRequired checks if a required field is present and not nil. Returns true if validation should continue, false if it should stop.

func (*BaseSchema) GetMeta added in v0.3.0

func (s *BaseSchema) GetMeta(key string) (interface{}, bool)

GetMeta retrieves metadata by key.

func (*BaseSchema) IsNullable

func (s *BaseSchema) IsNullable() bool

IsNullable returns true if the field can be null.

func (*BaseSchema) IsRequired

func (s *BaseSchema) IsRequired() bool

IsRequired returns true if the field is required.

func (*BaseSchema) SetMeta added in v0.3.0

func (s *BaseSchema) SetMeta(key string, value interface{})

SetMeta stores a key-value metadata pair. This is the unexported implementation; each builder type exposes a typed Meta() method that calls this and returns itself for chaining.

func (*BaseSchema) SetNullable

func (s *BaseSchema) SetNullable(nullable bool)

SetNullable sets whether the field can be null.

func (*BaseSchema) SetRequired

func (s *BaseSchema) SetRequired(required bool)

SetRequired sets whether the field is required.

func (*BaseSchema) Type

func (s *BaseSchema) Type() SchemaType

Type returns the schema type.

type CompiledSchema added in v0.3.0

type CompiledSchema struct {
	BaseSchema
	// contains filtered or unexported fields
}

CompiledSchema wraps a Schema with a pre-built flat slice of check functions. All constraint checks (length, range, pattern, enum, custom validators, etc.) are resolved once at compile time and stored as function pointers. At validation time, the compiled schema executes one loop with no nil-checks or conditional branching on which constraints are configured.

func (*CompiledSchema) Inner added in v0.3.0

func (cs *CompiledSchema) Inner() Schema

Inner returns the original uncompiled schema.

func (*CompiledSchema) Type added in v0.3.0

func (cs *CompiledSchema) Type() SchemaType

Type returns the underlying schema type.

func (*CompiledSchema) Validate added in v0.3.0

func (cs *CompiledSchema) Validate(value interface{}, ctx *ValidationContext) error

Validate runs the pre-compiled check chain.

type FieldError

type FieldError struct {
	// Path is the field path where the error occurred (e.g., "user.email" or "items[0].price")
	Path string
	// Message describes what validation failed
	Message string
	// Value is the actual value that failed validation (optional)
	Value interface{}
}

FieldError represents a validation error for a specific field.

func NewFieldError

func NewFieldError(path, message string, value interface{}) FieldError

NewFieldError creates a new FieldError.

func (FieldError) Error

func (e FieldError) Error() string

Error implements the error interface for FieldError.

func (FieldError) String

func (e FieldError) String() string

String returns a string representation of the field error.

type Option

type Option func(interface{})

Option represents a configuration option for validators.

type Schema

type Schema interface {
	// Validate validates a value against this schema.
	// It should add any validation errors to the context.
	// Returns an error only for unexpected failures (not validation failures).
	Validate(value interface{}, ctx *ValidationContext) error

	// Type returns the schema type.
	Type() SchemaType
}

Schema represents a validation schema. All schema types must implement this interface.

func Compile

func Compile(schema Schema) Schema

Compile pre-compiles a schema into a flat function-call chain. The returned CompiledSchema validates with a single loop over pre-resolved check functions, eliminating per-call nil checks and constraint branching.

If the schema is already compiled, it is returned as-is.

type SchemaType

type SchemaType string

SchemaType represents the type of a schema.

const (
	// TypeString represents a string schema
	TypeString SchemaType = "string"
	// TypeNumber represents a number schema
	TypeNumber SchemaType = "number"
	// TypeBool represents a boolean schema
	TypeBool SchemaType = "boolean"
	// TypeObject represents an object schema
	TypeObject SchemaType = "object"
	// TypeArray represents an array schema
	TypeArray SchemaType = "array"
	// TypeAny represents a schema that accepts any type
	TypeAny SchemaType = "any"
	// TypeCustom represents a custom validator
	TypeCustom SchemaType = "custom"
	// TypeComposite represents a composite schema (AND/OR/NOT)
	TypeComposite SchemaType = "composite"
	// TypeDateTime represents a date/time schema
	TypeDateTime SchemaType = "datetime"
	// TypeDependent represents a dependent field schema
	TypeDependent SchemaType = "dependent"
	// TypeTransform represents a transformation schema
	TypeTransform SchemaType = "transform"
)

func (SchemaType) String

func (t SchemaType) String() string

String returns the string representation of a SchemaType.

type TransformableSchema added in v0.2.0

type TransformableSchema interface {
	Schema
	// ValidateAndTransform returns the transformed value and any validation error
	ValidateAndTransform(value interface{}, ctx *ValidationContext) (interface{}, error)
}

TransformableSchema represents a schema that can apply transformations.

type TransformationRecord added in v0.2.0

type TransformationRecord struct {
	Path     string
	Original interface{}
	Result   interface{}
	Type     string
}

TransformationRecord records a transformation that was applied.

type Transformer added in v0.2.0

type Transformer interface {
	// Transform applies the transformation to a value
	Transform(value interface{}) (interface{}, error)
}

Transformer represents a function that can transform values. This is defined here to avoid circular dependencies.

type ValidationContext

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

ValidationContext maintains state during validation. It tracks the current path and accumulates errors.

func NewValidationContext

func NewValidationContext(mode ValidationMode) *ValidationContext

NewValidationContext creates a new validation context.

func (*ValidationContext) AddError

func (c *ValidationContext) AddError(message string, value interface{})

AddError adds an error at the current path.

func (*ValidationContext) AddFieldError

func (c *ValidationContext) AddFieldError(err FieldError)

AddFieldError adds a pre-constructed field error.

func (*ValidationContext) CurrentPath

func (c *ValidationContext) CurrentPath() string

CurrentPath returns the current field path as a string.

func (*ValidationContext) Error

func (c *ValidationContext) Error() error

Error returns a ValidationError if there are any errors, nil otherwise.

func (*ValidationContext) Errors

func (c *ValidationContext) Errors() []FieldError

Errors returns all accumulated errors.

func (*ValidationContext) HasErrors

func (c *ValidationContext) HasErrors() bool

HasErrors returns true if any errors have been added.

func (*ValidationContext) HasTransformations added in v0.2.0

func (c *ValidationContext) HasTransformations() bool

HasTransformations returns true if any transformations were applied.

func (*ValidationContext) Mode

Mode returns the validation mode.

func (*ValidationContext) PopPath

func (c *ValidationContext) PopPath()

PopPath removes the last path segment.

func (*ValidationContext) PushIndex

func (c *ValidationContext) PushIndex(index int)

PushIndex adds an array index to the current path.

func (*ValidationContext) PushPath

func (c *ValidationContext) PushPath(segment string)

PushPath adds a path segment to the current path.

func (*ValidationContext) RecordTransformation added in v0.2.0

func (c *ValidationContext) RecordTransformation(original, result interface{}, transformType string)

RecordTransformation records that a transformation was applied.

func (*ValidationContext) Reset added in v0.3.0

func (c *ValidationContext) Reset()

Reset clears accumulated errors, path state, and transformation records, allowing the context to be reused across multiple validations without reallocating. The validation mode is preserved.

func (*ValidationContext) Transformations added in v0.2.0

func (c *ValidationContext) Transformations() []TransformationRecord

Transformations returns all recorded transformations.

func (*ValidationContext) WithIndex

func (c *ValidationContext) WithIndex(index int, fn func())

WithIndex executes a function with an array index pushed onto the context. The index is automatically popped when the function returns.

func (*ValidationContext) WithPath

func (c *ValidationContext) WithPath(segment string, fn func())

WithPath executes a function with a path segment pushed onto the context. The path is automatically popped when the function returns.

type ValidationError

type ValidationError struct {
	Errors []FieldError
}

ValidationError represents one or more validation failures. It contains a slice of FieldError that provides detailed information about each validation failure.

func NewValidationError

func NewValidationError(errors ...FieldError) *ValidationError

NewValidationError creates a new ValidationError with the given field errors.

func (*ValidationError) Add

func (e *ValidationError) Add(path, message string, value interface{})

Add adds a field error to the validation error.

func (*ValidationError) AddError

func (e *ValidationError) AddError(err FieldError)

AddError adds an existing FieldError to the validation error.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error returns a string representation of all validation errors.

func (*ValidationError) HasErrors

func (e *ValidationError) HasErrors() bool

HasErrors returns true if there are any validation errors.

type ValidationMode

type ValidationMode int

ValidationMode determines how strict the validation is.

const (
	// Strict mode requires exact schema compliance.
	// Extra fields in objects will cause validation to fail.
	Strict ValidationMode = iota

	// Loose mode allows extra fields and safe type coercion.
	// Extra fields in objects are ignored.
	// Safe type coercions are applied (e.g., "123" -> 123).
	Loose
)

func (ValidationMode) String

func (m ValidationMode) String() string

String returns the string representation of a ValidationMode.

type Validator

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

Validator wraps a schema with configuration options.

func NewValidator

func NewValidator(schema Schema) *Validator

NewValidator creates a new validator with a schema. The validator can be configured with different modes and options.

func (*Validator) Loose

func (v *Validator) Loose() *Validator

Loose sets the validator to loose mode.

func (*Validator) Strict

func (v *Validator) Strict() *Validator

Strict sets the validator to strict mode.

func (*Validator) Validate

func (v *Validator) Validate(data interface{}) error

Validate validates data against the schema.

type ValidatorFunc

type ValidatorFunc func(value interface{}) error

ValidatorFunc is a function that validates a value. It should return an error if validation fails, nil otherwise.

Directories

Path Synopsis
datetime.go - Date/Time validation builder for Queryfy
datetime.go - Date/Time validation builder for Queryfy
jsonschema
Package jsonschema converts JSON Schema documents into queryfy schemas.
Package jsonschema converts JSON Schema documents into queryfy schemas.
transformers
common.go - Common transformation functions
common.go - Common transformation functions
examples
basic command
datetime command
jsonschema command
Package query provides a simple query language for navigating data structures.
Package query provides a simple query language for navigating data structures.
superjsonic

Jump to

Keyboard shortcuts

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