Skip to content

Commit

Permalink
Merge branch 'copybara' of github.com:verily-src/fhirpath-go into cop…
Browse files Browse the repository at this point in the history
…ybara
  • Loading branch information
alexlaurinmath committed May 6, 2024
2 parents 327f44b + e29f6e5 commit 3700055
Show file tree
Hide file tree
Showing 188 changed files with 42,964 additions and 0 deletions.
4 changes: 4 additions & 0 deletions fhirpath/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*antlr*.jar
*.interp
*.tokens
*fhirpath_base_visitor.go
55 changes: 55 additions & 0 deletions fhirpath/compopts/compopts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
Package compopts provides CompileOption values for FHIRPath.
This package exists to isolate the options away from the core FHIRPath logic,
since this will simplify discovery of compile-specific options.
*/
package compopts

import (
"errors"

"github.com/verily-src/fhirpath-go/fhirpath/internal/opts"
"github.com/verily-src/fhirpath-go/fhirpath/internal/parser"
)

var ErrMultipleTransforms = errors.New("multiple transforms provided")

// AddFunction creates a CompileOption that will register a custom FHIRPath
// function that can be called during evaluation with the given name.
//
// If the function already exists, then compilation will return an error.
func AddFunction(name string, fn any) opts.CompileOption {
return opts.Transform(func(cfg *opts.CompileConfig) error {
return cfg.Table.Register(name, fn)
})
}

// Transform creates a CompileOption that will set a transform
// to be called on each expression returned by the Visitor.
//
// If there is already a Transform set, then compilation will return an error.
func Transform(v parser.VisitorTransform) opts.CompileOption {
return opts.Transform(func(cfg *opts.CompileConfig) error {
if cfg.Transform != nil {
return ErrMultipleTransforms
}
cfg.Transform = v
return nil
})
}

// Permissive is an option that enables deprecated behavior in FHIRPath field
// navigation. This can be used as a temporary fix for FHIRpaths that have never
// been valid FHIRPaths, but have worked up until this point.
//
// This option is marked Deprecated so that it nags users until the paths can
// be resolved.
//
// Deprecated: Please update FHIRPaths whenever possible.
func Permissive() opts.CompileOption {
return opts.Transform(func(cfg *opts.CompileConfig) error {
cfg.Permissive = true
return nil
})
}
9 changes: 9 additions & 0 deletions fhirpath/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
Package fhirpath implements the FHIRPath specification.
More documentation about the FHIRPath specification can be found on HL7
websites:
* https://www.hl7.org/fhir/fhirpath.html
* http://hl7.org/fhirpath/N1/
*/
package fhirpath
74 changes: 74 additions & 0 deletions fhirpath/evalopts/evalopts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
Package evalopts provides EvaluateOption values for FHIRPath.
This package exists to isolate the options away from the core FHIRPath logic,
since this will simplify discovery of evaluation-specific options.
*/
package evalopts

import (
"errors"
"fmt"
"time"

"github.com/verily-src/fhirpath-go/internal/fhir"
"github.com/verily-src/fhirpath-go/fhirpath/internal/opts"
"github.com/verily-src/fhirpath-go/fhirpath/system"
)

var (
ErrUnsupportedType = errors.New("external constant type not supported")
ErrExistingConstant = errors.New("constant already exists")
)

// OverrideTime returns an EvaluateOption that can be used to override the time
// that will be used in FHIRPath expressions.
func OverrideTime(t time.Time) opts.EvaluateOption {
return opts.Transform(func(cfg *opts.EvaluateConfig) error {
cfg.Context.Now = t
return nil
})
}

// EnvVariable returns an EvaluateOption that sets FHIRPath environment variables
// (e.g. %action).
//
// The input must be one of:
// - A FHIRPath System type,
// - A FHIR Element or Resource type, or
// - A FHIRPath Collection, containing the above types.
//
// If an EnvVariable is specified that already exists in the expression, then
// evaluation will yield an ErrExistingConstant error. If an EnvVariable is
// contains a type that is not one of the above valid types, then evaluation
// will yield an ErrUnsupportedType error.
func EnvVariable(name string, value any) opts.EvaluateOption {
return opts.Transform(func(cfg *opts.EvaluateConfig) error {
if err := validateType(value); err != nil {
return err
}
if _, ok := cfg.Context.ExternalConstants[name]; !ok {
cfg.Context.ExternalConstants[name] = value
return nil
}
return fmt.Errorf("%w: %s", ErrExistingConstant, name)
})
}

// validateType validates that the input type is a supported
// fhir proto or System type. If a system.Collection is passed in,
// recursively checks each element.
func validateType(input any) error {
var err error
switch v := input.(type) {
case fhir.Base, system.Any:
break
case system.Collection:
for _, elem := range v {
err = errors.Join(err, validateType(elem))
}
default:
err = fmt.Errorf("%w: %T", ErrUnsupportedType, input)
}
return err
}
118 changes: 118 additions & 0 deletions fhirpath/fhirpath.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package fhirpath

import (
"errors"

"github.com/verily-src/fhirpath-go/internal/slices"
"github.com/verily-src/fhirpath-go/internal/fhir"
"github.com/verily-src/fhirpath-go/fhirpath/evalopts"
"github.com/verily-src/fhirpath-go/fhirpath/internal/compile"
"github.com/verily-src/fhirpath-go/fhirpath/internal/expr"
"github.com/verily-src/fhirpath-go/fhirpath/internal/opts"
"github.com/verily-src/fhirpath-go/fhirpath/internal/parser"
"github.com/verily-src/fhirpath-go/fhirpath/system"
)

var (
ErrInvalidField = expr.ErrInvalidField
ErrUnsupportedType = evalopts.ErrUnsupportedType
ErrExistingConstant = evalopts.ErrExistingConstant
)

// Expression is the FHIRPath expression that will be compiled from a FHIRPath string
type Expression struct {
expression expr.Expression
path string
}

// Compile parses and compiles the FHIRPath expression down to a single
// Expression object.
//
// If there are any syntax or semantic errors, this will return an
// error indicating the compilation failure reason.
func Compile(expr string, options ...CompileOption) (*Expression, error) {
config, err := compile.PopulateConfig(options...)
if err != nil {
return nil, err
}

tree, err := compile.Tree(expr)
if err != nil {
return nil, err
}

visitor := &parser.FHIRPathVisitor{
Functions: config.Table,
Permissive: config.Permissive,
}
vr, ok := visitor.Visit(tree).(*parser.VisitResult)
if !ok {
return nil, errors.New("input expression currently unsupported")
}

if vr.Error != nil {
return nil, vr.Error
}
return &Expression{
expression: vr.Result,
path: expr,
}, nil
}

// String returns the string representation of this FHIRPath expression.
// This is just the input that initially produced the FHIRPath value.
func (e *Expression) String() string {
return e.path
}

// MustCompile compiles the FHIRpath expression input, and returns the
// compiled expression. If any compilation error occurs, this function
// will panic.
func MustCompile(expr string, opts ...CompileOption) *Expression {
result, err := Compile(expr, opts...)
if err != nil {
panic(err)
}
return result
}

// Evaluate the expression, returning either a collection of elements, or error
func (e *Expression) Evaluate(input []fhir.Resource, options ...EvaluateOption) (system.Collection, error) {
config := &opts.EvaluateConfig{
Context: expr.InitializeContext(slices.MustConvert[any](input)),
}
config, err := opts.ApplyOptions(config, options...)
if err != nil {
return nil, err
}

collection := slices.MustConvert[any](input)
return e.expression.Evaluate(config.Context, collection)
}

// EvaluateAsString evaluates the expression, returning a string or error
func (e *Expression) EvaluateAsString(input []fhir.Resource, options ...EvaluateOption) (string, error) {
got, err := e.Evaluate(input, options...)
if err != nil {
return "", err
}
return got.ToString()
}

// EvaluateAsBool evaluates the expression, returning either a boolean or error
func (e *Expression) EvaluateAsBool(input []fhir.Resource, options ...EvaluateOption) (bool, error) {
got, err := e.Evaluate(input, options...)
if err != nil {
return false, err
}
return got.ToBool()
}

// EvaluateAsInt32 evaluates the expression, returning either an int32 or error
func (e *Expression) EvaluateAsInt32(input []fhir.Resource, options ...EvaluateOption) (int32, error) {
got, err := e.Evaluate(input, options...)
if err != nil {
return 0, err
}
return got.ToInt32()
}
Loading

0 comments on commit 3700055

Please sign in to comment.