Local Backend Configuration and Limits

Configuration options

base := &engine.Config{
    Schema: schema.Struct(schema.String("name")),
    Capabilities: capabilities.Standard(),
}
e, err := engine.New(
    engine.WithConfig(base),
    engine.WithSchema(serviceSchema),
)

Options apply left to right; the last assignment to a setting wins. WithConfig replaces the whole configuration at its position, including zero values. New validates the final configuration and retains no mutable Config pointer. A nil Config or a zero Option is a configuration error.

Select capabilities explicitly

features := capabilities.Standard().Without(capabilities.Regex)
e, err := engine.New(
    engine.WithSchema(serviceSchema),
    engine.WithCapabilities(features),
)

An omitted set is an error; None() is an explicitly empty set. Allow and Without return new sets. Has tests permission; IDs returns sorted, copied IDs. Standard contains the built-in language feature and method catalog, rather than promising that every backend implements every member. Common query compilation checks enabled capabilities; each backend builder separately checks support for the program operations it implements.

ID constant Meaning
Logical Boolean operators and implicit AND
Comparison Equality and ordering comparisons
Arithmetic Numeric arithmetic
Access Field access
Index List and object indexing
Guard Postfix path guards
TypeGuard Explicit type guards (value:[Type])
Collections List and object literals
Callbacks Callback expressions
RegexOperators Regex operators
Regex Group selecting RegexOperators and string.matches
ListEquality Structural equality of list values
ObjectEquality Structural equality of object values
StructuralEquality Group selecting ListEquality and ObjectEquality

Method("string.len") selects a built-in method by receiver family and name. For example:

features := capabilities.None().Allow(
    capabilities.Access,
    capabilities.Callbacks,
    capabilities.Method("list.any"),
)

The callback forms of list count and the list methods any, all, filter, map, flatmap, foldl, and foldr require Callbacks. Zero-argument list:count() does not require Callbacks. Missing dependencies are errors, not implicitly enabled features. The Regex group adds or removes both regex operators and string.matches. Unknown IDs are rejected, including IDs supplied only to Without. Removing a known but already-disabled ID is valid. For example, this typo is rejected by Check instead of silently leaving a similarly named method enabled:

features := capabilities.Standard().Without(
    capabilities.Method("string.maches"),
)
err := features.Check() // unknown capability "method:string.maches"

A declared extension uses capabilities.Method(declaration.ID()) and must also be included in engine.WithMethods. engine.New checks every referenced extension ID against those declarations, even if it was removed from the set. Call CheckWithMethods with the declaration ID suffixes when checking a set directly. See the local extension guide for the complete flow.

Structural equality permissions are checked during compilation for =/!=, in(), list:contains(), list:unique(), and list:distinct(). Scalar comparisons and explicit null tests do not need them. Required permissions follow the statically possible compared types, recursively through compatible nested containers; explicit type guards narrow those types. The StructuralEquality group enables both list and object equality. Removing the group removes both permissions; each permission can then be selected independently.

Limits

e, err := engine.New(
    engine.WithSchema(serviceSchema),
    engine.WithCapabilities(features),
    engine.WithLimits(hff.Limits{
        MaxInputDepth: 32,
        MaxInputValues: 20_000,
    }),
)

Zero selects the default for that item. Negative values are errors. WithUnlimitedLimits explicitly disables ceilings; a subsequent WithLimits restores finite defaults and applies its overrides. Config.UnlimitedLimits provides the same explicit choice when assembling configuration as data.

Limit Initial default Counting contract
MaxInputDepth 64 Root depth is zero; each value nested in a container adds one
MaxInputValues 100,000 Every value, including root and containers, counts once
MaxJSONDepth 64 JSON root is depth one; nested arrays and objects add one per level
MaxQueryBytes 65,536 UTF-8 source bytes per compilation
MaxSyntaxDepth 64 Syntax nesting per compilation
MaxCallbackDepth 64 Nested callback depth per compilation; the outermost callback is depth 1
MaxCompileSteps 1,000,000 Semantic analysis visits per compilation, including repeated callback inference
MaxSteps 1,000,000 Evaluated nodes, traversed data, lookup-key bytes, and declared struct fields checked per local match
MaxGeneratedValues 100,000 Generated collection containers and entries per local match

Compilation enforces query-byte, syntax-depth, callback-depth, and semantic-step limits; JSON parsing and JSON serialization enforce MaxJSONDepth; input validation enforces input-depth and input-value limits; the local backend enforces per-match step and generated-value limits. Runtime MaxSteps also counts values traversed by equality and type guards and the lengths processed by string and byte operations. MaxGeneratedValues counts collection containers and entries. SQL backends report the execution constraints they can enforce; configure database timeouts through the database integration. A requested constraint that the selected backend cannot enforce must be rejected. DefaultLimits returns a value copy. Limits returns the engine's resolved configuration; zero ceilings in an explicitly unlimited engine indicate that opt-out.

For list:unique(), fingerprint traversal and any collision-confirming equality checks charge MaxSteps; cancellation is checked during both. Since unique() returns only a boolean, it adds no MaxGeneratedValues charge. A budget or cancellation error aborts the match without a partial result.

For local object lookups, MaxSteps also charges the UTF-8 byte length of each evaluated key immediately before lookup. This includes root variable names, field access, string-key indexing, and the key argument to object has() and get(). The charge applies whether the key is present or missing. A lookup skipped because its receiver propagates null or no-value, or because an earlier logical operand short-circuits evaluation, does not charge an unevaluated key.

Local structural type checks charge one step for each declared struct field, including optional fields absent from the object, and check context while visiting those fields. Recursive checking of present values has its own normal work charge. A wide schema therefore cannot bypass MaxSteps merely because the record omits its optional fields; cancellation returns an error rather than a successful partial type check.

The initial input benchmarks exercise records containing 1, 1,000, and 10,000 integer list elements. Run them on your target hardware:

go test ./engine -run '^$' -bench BenchmarkValidateRecord -benchmem

On an Apple M3 Max with Go 1.27.1, a local 100 ms benchmark sample measured approximately 0.18 µs, 11.6 µs, and 114 µs respectively, with zero allocations per successful validation in these list benchmarks. Constructor work is outside the measured loop. This sample does not predict another schema, hardware, extension, or backend's performance.

These are configurable starting ceilings, not performance guarantees or limits on the storage already allocated by the caller. Validation checks context and counts all nested values, including Any contents. It checks UTF-8 in host strings and object keys in chunks of at most 4,096 bytes, adjusted to avoid splitting a rune, and checks context between chunks. Cancellation during a large scan returns a cancellation error with the context status and any host-supplied cause preserved; validation never returns a completed partial input. Map keys are still visited in byte-lexical order, matching Go's sort.Strings; the cancellable ordering checks context while collecting keys, merging runs, and comparing long common prefixes. Cancellation returns no partially ordered key list. Local evaluation uses the same ordering for object key enumeration such as keys(), structural type checks, and JSON object output. Its existing key-work budget charge is unchanged; cancellation during ordering returns no partial key list or encoded output.

Local JSON parsing also checks context while scanning whitespace, quoted strings and object keys, and unquoted tokens, with context checks at approximately 4 KiB intervals. Scanning a two-byte JSON escape may cross a checkpoint by one byte. The original JSON input bytes are charged once; checkpoints do not charge them again. Cancellation returns an error with the context cause preserved and no partial value. Before token scanning, JSON input UTF-8 validity uses the same chunked validator as host strings and object keys, so cancellation can interrupt that scan without copying the input or charging its bytes again. The standard-library JSON string decoder remains a single call that cannot be interrupted internally; HFF checks context after it returns. This is cooperative cancellation, not immediate preemption during every decoding step.

When local JSON serialization scans a long string to calculate its escaped length or quote its output, it checks context at approximately 4 KiB intervals and again before returning the encoded value. Cancellation returns no partial output. These checkpoints do not change input or output byte-budget accounting.

Regex evaluation checks context immediately after regexp.Compile, before it turns an invalid pattern into ordinary null or starts matching, and again after MatchString before applying negation or returning a result. Cancellation at these boundaries returns CancelledError with the context status and cause; step charges are unchanged. The standard-library compile and match calls themselves cannot be interrupted, so a long call is not promptly preempted.

Handle errors

var detail *hff.Error
if errors.As(err, &detail) {
    switch detail.Code {
    case hff.InputError:
        // detail.Path identifies the rejected value.
    case hff.BackendError:
        // The selected backend does not support an operation in the program.
    case hff.LimitError:
        // Validation exceeded its configured ceiling.
    case hff.CancelledError:
        // The context was cancelled or its deadline expired.
    case hff.ConfigurationError:
        // Correct the service configuration.
    }
}

Cancellation returns a CancelledError while preserving both the context status and its cause in the error chain. errors.Is matches context.Canceled or context.DeadlineExceeded; for contexts created with context.WithCancelCause or context.WithDeadlineCause, it also matches the host-supplied cause. Input paths contain literal names; list positions appear as decimal indices. Values are not included in diagnostics. Completed evaluation results and ordinary null are separate from these errors.

Explore HFF
Search documentation

Search the language specification and guides.