Use a Filter in a Service

A service should compile a saved or user-authored filter when it is created or updated, then reuse the resulting local filter for requests. This example also validates records once at ingestion and calls MatchInput when searching them.

Compile when the filter changes

Keep the schema under application control. In this example, each ticket has a team and active flag. Compile and build the query once:

e, err := engine.New(
    engine.WithSchema(schema.Struct(
        schema.String("team"),
        schema.Bool("active"),
    )),
    engine.WithCapabilities(capabilities.Standard()),
)
if err != nil {
    return err
}

program, err := e.Compile(ctx, `team="support" active=true`)
if err != nil {
    return err
}
filter, err := local.Build(ctx, program)
if err != nil {
    return err
}

Treat a compile or build error as a rejected filter configuration. Do not store or run a filter that failed either step. HFF queries select values from the service schema; they do not add fields or execute Go functions.

Validate records at ingestion

Convert trusted application data to explicit HFF values and validate it before storing it for later searches:

record := hff.Object(map[string]hff.Value{
    "team":   hff.String("support"),
    "active": hff.Bool(true),
})
input, err := e.Validate(ctx, record)
if err != nil {
    return err
}

Input is immutable. Keep the validated input alongside the service record or in a cache whose invalidation follows the record's lifecycle.

Reuse validated inputs

Each request can now evaluate the same filter without repeating input validation:

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

MatchInput still checks that the input's schema contract matches the filter. If records were not validated at ingestion, use Match with the original HFF value instead. Both operations use fresh per-call state and return errors for cancellation and exceeded evaluation limits. See Build and run a local filter for limit configuration and error codes.

Reject invalid query changes

Compilation errors are structured HFF errors. An application can report a source location to the filter author while keeping the active filter unchanged:

program, err := e.Compile(ctx, source)
if err != nil {
    var detail *hff.Error
    if errors.As(err, &detail) {
        return fmt.Errorf("filter rejected at %d:%d: %w",
            detail.Line, detail.Column, err)
    }
    return err
}

Only replace the active filter after both compilation and local.Build succeed. Use stable error codes for application behavior rather than matching diagnostic text; see Compile a query.

Run the complete example

From the repository root:

go run ./examples/tutorial-service

It prints:

ticket-17: true
ticket-18: false
Explore HFF
Search documentation

Search the language specification and guides.