Skip to content

Evaluation Options

The options API lets you customize how FHIRPath expressions are evaluated. Options are applied using Go’s functional options pattern, giving you fine-grained control over timeouts, recursion limits, external variables, and reference resolution.

EvalOptions

The EvalOptions struct holds all configuration for an evaluation run. You rarely construct this directly; instead, use the functional option functions to build options incrementally.

type EvalOptions struct {
    // Ctx is the context for cancellation and timeout.
    Ctx context.Context

    // Timeout for evaluation (0 means no timeout).
    Timeout time.Duration

    // MaxDepth limits recursion depth for descendants() (0 means default of 100).
    MaxDepth int

    // MaxCollectionSize limits output collection size (0 means no limit).
    MaxCollectionSize int

    // Variables are external variables accessible via %name in expressions.
    Variables map[string]types.Collection

    // Resolver handles reference resolution for the resolve() function.
    Resolver ReferenceResolver

    // Model provides FHIR version-specific type metadata.
    Model Model
}

DefaultOptions

Returns a new EvalOptions pre-configured with sensible production defaults.

func DefaultOptions() *EvalOptions

Default values:

FieldDefault Value
Ctxcontext.Background()
Timeout5 * time.Second
MaxDepth100
MaxCollectionSize10000
VariablesEmpty map
Resolvernil

When you call Expression.EvaluateWithOptions without any options, these defaults are used.


Functional Options

Each functional option is a function of type EvalOption that modifies EvalOptions:

type EvalOption func(*EvalOptions)

WithContext

Sets the context.Context for cancellation support. If the context is canceled during evaluation, the evaluation returns immediately with the context’s error.

func WithContext(ctx context.Context) EvalOption

Example:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

result, err := expr.EvaluateWithOptions(resource,
    fhirpath.WithContext(ctx),
)

WithTimeout

Sets the maximum time allowed for a single evaluation. If the evaluation takes longer than the specified duration, it is canceled and an error is returned. A zero value disables the timeout.

func WithTimeout(d time.Duration) EvalOption

Example:

// Allow at most 2 seconds for evaluation
result, err := expr.EvaluateWithOptions(resource,
    fhirpath.WithTimeout(2*time.Second),
)
When both WithContext and WithTimeout are provided, the effective deadline is the earlier of the two. WithTimeout internally creates a child context with the given timeout.

WithMaxDepth

Sets the maximum recursion depth for functions like descendants() that traverse the resource tree. This prevents stack overflows on deeply nested or circular structures. The default is 100.

func WithMaxDepth(depth int) EvalOption

Example:

// Limit recursion to 50 levels
result, err := expr.EvaluateWithOptions(resource,
    fhirpath.WithMaxDepth(50),
)

WithMaxCollectionSize

Sets the maximum number of values allowed in a result collection. This prevents excessive memory usage when expressions produce very large result sets. The default is 10000.

func WithMaxCollectionSize(size int) EvalOption

Example:

// Limit results to 1000 values
result, err := expr.EvaluateWithOptions(resource,
    fhirpath.WithMaxCollectionSize(1000),
)

WithVariable

Defines an external variable that can be referenced in the FHIRPath expression using the %name syntax. Multiple WithVariable calls can be chained.

func WithVariable(name string, value types.Collection) EvalOption

Parameters:

NameTypeDescription
namestringVariable name (referenced as %name in expressions)
valuetypes.CollectionThe variable’s value as a Collection

Example:

import "github.com/gofhir/fhirpath/types"

// Define a variable %maxAge that can be used in the expression
expr := fhirpath.MustCompile("Patient.birthDate < today() - %maxAge")

result, err := expr.EvaluateWithOptions(resource,
    fhirpath.WithVariable("maxAge", types.Collection{types.NewString("65 years")}),
)

WithResolver

Provides a ReferenceResolver implementation for the FHIRPath resolve() function. When an expression calls resolve() on a FHIR® reference, the resolver fetches the referenced resource.

func WithResolver(r ReferenceResolver) EvalOption

Example:

result, err := expr.EvaluateWithOptions(resource,
    fhirpath.WithResolver(myResolver),
)

See ReferenceResolver below for details.


WithModel

Provides a Model implementation for FHIR version-specific type metadata. When a model is present, the engine uses it for precise polymorphic resolution, type hierarchy checking, and path-based inference. The model is authoritative — its answers override built-in heuristics.

func WithModel(m Model) EvalOption

Example:

import "github.com/gofhir/models/r4"

result, err := expr.EvaluateWithOptions(resource,
    fhirpath.WithModel(r4.FHIRPathModel()),
)

See FHIR Version-Specific Models for details on the Model interface and how to implement custom models.


ReferenceResolver Interface

The ReferenceResolver interface is implemented by your application to provide reference resolution for the FHIRPath resolve() function. When an expression evaluates Reference.resolve(), the library calls your resolver with the reference string.

type ReferenceResolver interface {
    Resolve(ctx context.Context, reference string) ([]byte, error)
}

Parameters passed to Resolve:

NameTypeDescription
ctxcontext.ContextThe evaluation context (respects cancellation/timeout)
referencestringThe FHIR® reference string (e.g., "Patient/123", "http://example.com/fhir/Patient/123")

Returns:

TypeDescription
[]byteRaw JSON bytes of the referenced resource
errorNon-nil if the reference cannot be resolved

Example Implementation: HTTP Resolver

type HTTPResolver struct {
    BaseURL    string
    HTTPClient *http.Client
}

func (r *HTTPResolver) Resolve(ctx context.Context, reference string) ([]byte, error) {
    url := r.BaseURL + "/" + reference

    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return nil, fmt.Errorf("creating request: %w", err)
    }
    req.Header.Set("Accept", "application/fhir+json")

    resp, err := r.HTTPClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("fetching %s: %w", reference, err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("unexpected status %d for %s", resp.StatusCode, reference)
    }

    return io.ReadAll(resp.Body)
}

Example Implementation: In-Memory Bundle Resolver

type BundleResolver struct {
    resources map[string][]byte
}

func NewBundleResolver(bundle []byte) (*BundleResolver, error) {
    resolver := &BundleResolver{
        resources: make(map[string][]byte),
    }

    // Parse bundle and index resources by their reference key
    // (e.g., "Patient/123")
    var b struct {
        Entry []struct {
            Resource json.RawMessage `json:"resource"`
        } `json:"entry"`
    }
    if err := json.Unmarshal(bundle, &b); err != nil {
        return nil, err
    }

    for _, entry := range b.Entry {
        var meta struct {
            ResourceType string `json:"resourceType"`
            ID           string `json:"id"`
        }
        if err := json.Unmarshal(entry.Resource, &meta); err == nil {
            key := meta.ResourceType + "/" + meta.ID
            resolver.resources[key] = entry.Resource
        }
    }

    return resolver, nil
}

func (r *BundleResolver) Resolve(ctx context.Context, reference string) ([]byte, error) {
    if data, ok := r.resources[reference]; ok {
        return data, nil
    }
    return nil, fmt.Errorf("resource not found: %s", reference)
}

Combining Multiple Options

Options are variadic, so you can pass as many as needed. They are applied in order, with later options overriding earlier ones for the same field.

expr := fhirpath.MustCompile(
    "Observation.subject.resolve().name.family",
)

resolver := &HTTPResolver{
    BaseURL:    "https://fhir.example.com",
    HTTPClient: &http.Client{Timeout: 10 * time.Second},
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

result, err := expr.EvaluateWithOptions(observationJSON,
    fhirpath.WithContext(ctx),
    fhirpath.WithTimeout(10*time.Second),
    fhirpath.WithMaxDepth(50),
    fhirpath.WithMaxCollectionSize(500),
    fhirpath.WithVariable("today", types.Collection{types.NewString("2025-01-15")}),
    fhirpath.WithResolver(resolver),
)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result)

Options Summary

OptionDefaultDescription
WithContextcontext.Background()Sets the cancellation context
WithTimeout5sMaximum evaluation time
WithMaxDepth100Maximum recursion depth for descendants()
WithMaxCollectionSize10000Maximum result collection size
WithVariableNoneDefines external variables accessible via %name
WithResolvernilProvides reference resolution for resolve()
WithModelnilProvides FHIR version-specific type metadata
Last updated on