Local Backend

Compile a query once, build a reusable local filter, and match records against the schema used by the engine. A filter is immutable and safe for concurrent calls. Each match uses fresh evaluation state.

import (
    "context"
    "errors"
    "fmt"

    "github.com/ornew/hff"
    "github.com/ornew/hff/backends/local"
    "github.com/ornew/hff/capabilities"
    "github.com/ornew/hff/engine"
    "github.com/ornew/hff/schema"
)

Compile, build, and match

ctx := context.Background()
e, err := engine.New(
    engine.WithSchema(schema.Struct(
        schema.String("name"),
        schema.Int("age"),
    )),
    engine.WithCapabilities(capabilities.Standard()),
)
if err != nil {
    return err
}

program, err := e.Compile(ctx, `name="Alice" age>=18`)
if err != nil {
    return err
}

filter, err := local.Build(ctx, program)
if err != nil {
    return err
}

record := hff.Object(map[string]hff.Value{
    "name": hff.String("Alice"),
    "age":  hff.Int(20),
})
matched, err := filter.Match(ctx, record)
if err != nil {
    return err
}
fmt.Println(matched) // true

Match validates the supplied value against the program's input schema on each call, traversing the whole schema-shaped record even if the query reads only one scalar field, then evaluates it. false, nil means evaluation completed and the record did not match. Validation failures, cancellation, and exceeded runtime budgets return errors instead of a match result. Ordinary null and internal no-value follow the language's boolean-conversion rules.

Reuse validated input

For repeated matches, validate the record once and use MatchInput:

input, err := e.Validate(ctx, record)
if err != nil {
    return err
}

matched, err := filter.MatchInput(ctx, input)
if err != nil {
    return err
}

Input is immutable. MatchInput checks that its schema contract exactly matches the program and skips record validation. The contract compares field types, required/optional presence, nullability, numeric subtypes, and missing value behavior; it does not require the same schema object or capability set. An invalid or incompatible input returns an input error without a completed match. For repeated searches, validate once at ingestion and retain the immutable Input; this avoids traversing fields that an individual query does not read.

Inspect the local profile

local.Profile() reports the local backend's static support and compatibility facts. After building, filter.Profile() reports the program's effective capability policy, compile limits, local input/runtime limits, registered extension declarations, and the selected time-zone snapshot. The profile is an immutable value; it does not load zone data or grant service permissions.

Float behavior is described by the float.* profile details. The local backend uses Go float64 parsing and arithmetic; subnormal values and underflow to signed zero are accepted, while overflow and non-finite results produce null. Float rounding uses Go's math.Round, Ceil, and Floor, and Float text uses Go's shortest round-trip format with a subtype marker. Float aggregate methods are not part of the current local method set.

profile := filter.Profile()
if limits, known := profile.CompileLimits(); known {
	fmt.Println(limits.MaxCompileSteps)
}
if zoneVersion, known := profile.Version("tz"); known {
	fmt.Println(zoneVersion)
}

The compile profile reports query-byte, syntax-depth, callback-depth, and compile-step ceilings. Input reports input depth/value ceilings; runtime reports step, generated-value, and JSON-depth ceilings. A known zero ceiling means unlimited. A missing limit stage means unknown or outside that backend's responsibility. The static profile has unknown compile policy; use the built filter profile for effective configured limits. Support levels describe what the backend can evaluate, while Capabilities() describes service permission policy; these are separate. A missing feature is unknown, not supported. Extension declarations appear in Methods() only when registered, and their presence does not enable them in the capability policy.

The runnable profile example prints the configured runtime ceiling and whether regex is permitted.

Configure evaluation limits

local.Build accepts backend-specific functional options. Individual limits can override the defaults:

filter, err := local.Build(ctx, program,
    local.WithLimits(hff.Limits{
        MaxSteps:           200_000,
        MaxGeneratedValues: 20_000,
    }),
)

WithConfig, WithLimits, and WithUnlimitedLimits apply in order; a later option replaces or overrides earlier settings. WithConfig takes a *local.Config with Limits and UnlimitedLimits fields. Config values are copied during build. For ordinary limits, zero selects the documented finite default and a negative value is invalid. Unlimited mode must be selected explicitly.

MaxSteps counts evaluated nodes, values traversed during equality and type guards, structural unique() fingerprints, and string/byte operation lengths. MaxGeneratedValues counts generated collection containers and entries. Exhausting either budget returns a limit error; HFF does not return a partial match. Context cancellation is checked during evaluation. These budgets apply to each match call independently.

Local unique() processes values in order and stops when it finds the first duplicate. Its fingerprints narrow candidate comparisons; structural HFF equality confirms each candidate. Fingerprint traversal and equality checks are charged to MaxSteps, and the boolean result does not charge generated values. See Local structural uniqueness for deterministic work-count evidence and collision limits.

Object lookup also charges the UTF-8 byte length of each evaluated key before checking it, whether the key is present or missing. Null/no-value propagation and logical short-circuiting do not charge lookups that are never evaluated.

Supported operations

The local evaluator supports core boolean and arithmetic operators, equality and ordering, regex operators, truthiness, null and no-value propagation, postfix guards and explicit type guards, indexing, collection literals, and logical short-circuiting. It supports every built-in method family in the method catalog:

Receiver family Methods
Any type(), json(), string(), in(any...), exists()
Bool and(bool), or(bool), not()
Number round(), ceil(), floor()
String and bytes Text, JSON/base64, timestamp/duration conversions, and byte length
List Size, count and sum, filtering, index presence, membership, uniqueness, sort, concat, callbacks, and folds
Object Size, key presence, get, keys, values, and items
Timestamp and duration Parsing, comparison, arithmetic, components, and formatting

For exact parameter and result types, consult the method reference. The backend supports the scalar and string examples in the scalar/string guide and list/object methods in the collections guide and callback guide. JSON conversions are covered in the JSON guide. Local custom methods are supported; see Local extension methods for declaration, capability, evaluator, and metering requirements. Base64 conversions are covered in the scalar/string guide. A program containing a method or operation not supported by the local backend fails during Build with hff.BackendError. Build never silently drops an operation. Use the normative method specification for query semantics; backend support is reported separately from compile validity.

Local regex matching follows HFF's RE2 reference syntax except that \C is excluded and is treated as an invalid pattern, producing null. Other backends may provide a smaller regex subset; check their support matrix or compatibility profile for documented restrictions.

Distinguish errors from non-matches

Use errors.As to inspect structured HFF errors:

var detail *hff.Error
if errors.As(err, &detail) {
    switch detail.Code {
    case hff.BackendError:
        // The local backend cannot execute an operation in this program.
    case hff.InputError:
        // The record or validated input does not satisfy the input contract.
    case hff.LimitError:
        // A configured per-match budget was exceeded.
    case hff.CancelledError:
        // errors.Is matches Canceled/DeadlineExceeded and any host cause
        // supplied with context.WithCancelCause/WithDeadlineCause.
    }
}

BackendError is raised while building an unsupported program. Runtime errors mean that matching did not complete; a completed false result is not an error.

Run the complete example from the repository root:

go run ./examples/local

It prints Match: true and MatchInput: true.

Explore HFF
Search documentation

Search the language specification and guides.