Add a Service-Specific Method

This tutorial adds a pure hasprefix method for string fields, selects its capability, compiles a human-authored query, and registers a local evaluator. The full declaration and metering reference is in the local extension guide.

These snippets belong in a Go program using the following imports:

import (
    "context"
    "fmt"
    "strings"

    "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"
)

At the start of main, create the operation context:

ctx := context.Background()

Declare the method

The method takes one string receiver and one required string argument and returns a boolean. Its stable ID is separate from the name people type in a query:

declaration, err := hff.NewMethodDeclaration(hff.MethodConfig{
    ID:         "service.string.hasprefix",
    Name:       "hasprefix",
    Receiver:   schema.StringType,
    Parameters: []hff.Type{schema.StringType},
    Result:     schema.BoolType,
    BaseCost:   1,
})
if err != nil {
    return err
}

The declaration describes the language contract. It contains no Go function; the engine and local backend receive their own configuration.

Enable and compile it

features := capabilities.Standard().Allow(capabilities.Method(declaration.ID()))
e, err := engine.New(
    engine.WithSchema(schema.Struct(schema.String("name"))),
    engine.WithCapabilities(features),
    engine.WithMethods(declaration),
)
if err != nil {
    return err
}

program, err := e.Compile(ctx, `name:hasprefix("Al")`)
if err != nil {
    return err
}

Capability permission is explicit. Compilation checks the call's types and rejects a query if the extension signature is not selected.

Implement it for local matching

The evaluator uses HFF values, meters its input-dependent work, and returns a boolean:

filter, err := local.Build(ctx, program, local.WithMethods(local.Implement(
    declaration,
    func(call *local.Call, receiver hff.Value, args []hff.Value) (hff.Value, error) {
        name, _ := receiver.AsString()
        prefix, _ := args[0].AsString()
        if err := call.ChargeSteps(len(name)); err != nil {
            return hff.Null(), err
        }
        return hff.Bool(strings.HasPrefix(name, prefix)), nil
    },
)))
if err != nil {
    return err
}

The declaration's fixed base cost is charged automatically. The evaluator charges extra work through Call; it must be pure and safe for concurrent matches. Missing implementations and invalid result values fail explicitly. Null propagates by default, and internal no-value never reaches the evaluator.

Match records

for _, name := range []string{"Alice", "Bob"} {
    matched, err := filter.Match(ctx, hff.Object(map[string]hff.Value{
        "name": hff.String(name),
    }))
    if err != nil {
        return err
    }
    fmt.Printf("%s: %t\n", name, matched)
}

The output is Alice: true and Bob: false. The complete runnable program is in examples/extensions/main.go:

go run ./examples/extensions

For overloads, optional/variadic parameters, explicit null invocation, and metering details, continue with the local extension guide.

Explore HFF
Search documentation

Search the language specification and guides.