Saltar al contenido
Opciones de Evaluación

Opciones de Evaluación

Descripción General de EvalOptions

Cuando llama a Evaluate() o EvaluateCached(), la biblioteca utiliza valores predeterminados sensatos para cada límite de seguridad. Para un control detallado, compile la expresión primero y luego llame a EvaluateWithOptions() con una o más opciones funcionales.

result, err := expr.EvaluateWithOptions(resource,
    fhirpath.WithTimeout(2 * time.Second),
    fhirpath.WithMaxDepth(50),
)

La estructura subyacente EvalOptions contiene estos campos:

CampoTipoValor por DefectoDescripción
Ctxcontext.Contextcontext.Background()Contexto para cancelación y propagación de plazos
Timeouttime.Duration5 sTiempo máximo de reloj de pared para una evaluación
MaxDepthint100Límite de recursión para descendants() y rutas anidadas
MaxCollectionSizeint10 000Número máximo de elementos en cualquier resultado intermedio
Variablesmap[string]types.Collectionmapa vacíoVariables externas accesibles via %name
ResolverReferenceResolvernilManejador para la función resolve()

Todas las opciones se aplican sobre los valores predeterminados devueltos por DefaultOptions(), por lo que solo necesita especificar los valores que desea sobrescribir.

Protección por Tiempo de Espera

La opción WithTimeout envuelve la evaluación en un context.WithTimeout. Si la expresión tarda más que la duración especificada, la evaluación se cancela y devuelve un error.

Esto es esencial al evaluar expresiones proporcionadas por el usuario, porque una expresión patológica como Patient.descendants().descendants() podría de otro modo ejecutarse durante mucho tiempo.

package main

import (
    "fmt"
    "time"

    "github.com/gofhir/fhirpath"
)

func main() {
    patient := []byte(`{
        "resourceType": "Patient",
        "name": [{"family": "Doe", "given": ["John", "James"]}]
    }`)

    expr := fhirpath.MustCompile("Patient.name.given")

    // Allow at most 500 ms for this evaluation.
    result, err := expr.EvaluateWithOptions(patient,
        fhirpath.WithTimeout(500 * time.Millisecond),
    )
    if err != nil {
        fmt.Println("evaluation timed out:", err)
        return
    }
    fmt.Println(result) // [John, James]
}

Uso de un Contexto Existente

Si su aplicación ya lleva un contexto con alcance de solicitud (por ejemplo, desde un manejador HTTP), páselo con WithContext para que la evaluación respete la señal de cancelación del llamador:

func handleRequest(ctx context.Context, resource []byte) (fhirpath.Collection, error) {
    expr := fhirpath.MustGetCached("Patient.name.family")
    return expr.EvaluateWithOptions(resource,
        fhirpath.WithContext(ctx),
        fhirpath.WithTimeout(2 * time.Second),
    )
}

Cuando se especifican tanto WithContext como WithTimeout, el tiempo de espera se aplica como hijo del contexto proporcionado. Si el contexto padre se cancela primero, la evaluación se detiene inmediatamente.

Límites de Recursión

La opción WithMaxDepth limita cuán profundamente recursará el evaluador al recorrer estructuras anidadas. Esto protege contra desbordamientos de pila causados por recursos profundamente anidados o expresiones que usan descendants().

package main

import (
    "fmt"
    "github.com/gofhir/fhirpath"
)

func main() {
    // A deeply nested Questionnaire with items inside items.
    questionnaire := []byte(`{
        "resourceType": "Questionnaire",
        "item": [{
            "linkId": "1",
            "item": [{
                "linkId": "1.1",
                "item": [{
                    "linkId": "1.1.1"
                }]
            }]
        }]
    }`)

    expr := fhirpath.MustCompile("Questionnaire.descendants().ofType(Questionnaire.item)")

    // Restrict recursion to 50 levels instead of the default 100.
    result, err := expr.EvaluateWithOptions(questionnaire,
        fhirpath.WithMaxDepth(50),
    )
    if err != nil {
        fmt.Println("depth exceeded:", err)
        return
    }
    fmt.Println("items found:", len(result))
}

Establezca MaxDepth en 0 para usar el valor predeterminado de 100.

Límites de Tamaño de Colección

La opción WithMaxCollectionSize limita el número de elementos en cualquier colección intermedia. Si una expresión produce más elementos que el límite, la evaluación devuelve un error en lugar de consumir memoria sin límite.

package main

import (
    "fmt"
    "github.com/gofhir/fhirpath"
)

func main() {
    // A Bundle with many entries.
    bundle := []byte(`{
        "resourceType": "Bundle",
        "entry": [
            {"resource": {"resourceType": "Patient", "id": "1"}},
            {"resource": {"resourceType": "Patient", "id": "2"}},
            {"resource": {"resourceType": "Patient", "id": "3"}}
        ]
    }`)

    expr := fhirpath.MustCompile("Bundle.entry.resource")

    // Limit intermediate collections to 500 elements.
    result, err := expr.EvaluateWithOptions(bundle,
        fhirpath.WithMaxCollectionSize(500),
    )
    if err != nil {
        fmt.Println("collection too large:", err)
        return
    }
    fmt.Println("resources:", len(result))
}

El límite predeterminado es 10 000, lo cual es generoso para la mayoría de cargas de trabajo. Redúzcalo cuando evalúe expresiones no confiables para prevenir denegación de servicio por agotamiento de memoria.

Variables Personalizadas

FHIRPath soporta variables externas referenciadas con el prefijo %. La biblioteca establece automáticamente %resource y %context al recurso raíz, pero puede inyectar sus propias variables con WithVariable.

package main

import (
    "fmt"
    "github.com/gofhir/fhirpath"
    "github.com/gofhir/fhirpath/types"
)

func main() {
    patient := []byte(`{
        "resourceType": "Patient",
        "identifier": [
            {"system": "http://hospital.example.org/mrn", "value": "MRN-12345"},
            {"system": "http://hl7.org/fhir/sid/us-ssn",  "value": "123-45-6789"}
        ]
    }`)

    // Find identifiers matching a system provided at runtime.
    expr := fhirpath.MustCompile("Patient.identifier.where(system = %targetSystem).value")

    targetSystem := types.Collection{types.NewString("http://hl7.org/fhir/sid/us-ssn")}

    result, err := expr.EvaluateWithOptions(patient,
        fhirpath.WithVariable("targetSystem", targetSystem),
    )
    if err != nil {
        panic(err)
    }
    fmt.Println(result) // [123-45-6789]
}

Múltiples Variables

Puede pasar tantas opciones WithVariable como necesite:

result, err := expr.EvaluateWithOptions(patient,
    fhirpath.WithVariable("minAge", types.Collection{types.NewInteger(18)}),
    fhirpath.WithVariable("system", types.Collection{types.NewString("http://loinc.org")}),
    fhirpath.WithVariable("today", types.Collection{todayDate}),
)

Variables Incorporadas

La biblioteca proporciona automáticamente estas variables para cada evaluación:

VariableValor
%resourceEl recurso raíz que se está evaluando
%contextIgual que %resource para evaluación de nivel superior

Estas son requeridas por las expresiones de restricción FHIR® (como bdl-3 y bdl-4) y no deben sobrescribirse a menos que tenga una razón específica para hacerlo.

Combinación de Opciones

En código de producción frecuentemente combinará varias opciones. El patrón de opciones funcionales hace esto limpio y legible:

package main

import (
    "context"
    "fmt"
    "time"

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

func evaluateExpression(
    ctx context.Context,
    resource []byte,
    expression string,
    targetSystem string,
) (fhirpath.Collection, error) {
    expr, err := fhirpath.GetCached(expression)
    if err != nil {
        return nil, fmt.Errorf("compile: %w", err)
    }

    return expr.EvaluateWithOptions(resource,
        // Propagate the caller's context for cancellation.
        fhirpath.WithContext(ctx),

        // Hard timeout for this single evaluation.
        fhirpath.WithTimeout(2 * time.Second),

        // Safety limits.
        fhirpath.WithMaxDepth(50),
        fhirpath.WithMaxCollectionSize(5000),

        // Runtime variable.
        fhirpath.WithVariable("targetSystem",
            types.Collection{types.NewString(targetSystem)},
        ),
    )
}

func main() {
    patient := []byte(`{
        "resourceType": "Patient",
        "identifier": [
            {"system": "http://hospital.example.org/mrn", "value": "MRN-001"}
        ]
    }`)

    result, err := evaluateExpression(
        context.Background(),
        patient,
        "Patient.identifier.where(system = %targetSystem).value",
        "http://hospital.example.org/mrn",
    )
    if err != nil {
        panic(err)
    }
    fmt.Println(result) // [MRN-001]
}

Referencia Rápida

FunciónDescripción
WithContext(ctx)Establecer el context.Context padre
WithTimeout(d)Establecer el tiempo de espera de evaluación
WithMaxDepth(n)Establecer la profundidad máxima de recursión
WithMaxCollectionSize(n)Establecer el tamaño máximo de colección intermedia
WithVariable(name, value)Inyectar una variable externa accesible via %name
WithResolver(r)Establecer un ReferenceResolver (ver Resolvedores Personalizados)
DefaultOptions()Devuelve un nuevo EvalOptions con todos los valores por defecto aplicados
Última actualización