-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Project import generated by Copybara.
GitOrigin-RevId: b8bebf065c6b2212c17d7d4dbfb8e5427b9e96d7
- Loading branch information
0 parents
commit e29f6e5
Showing
192 changed files
with
43,115 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Note: For syntax, see <https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax> | ||
|
||
* @verily-src/fhirpathgo-eng-reviewers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: Build and Test | ||
on: | ||
pull_request: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
build-test: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Setup Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
# Use the central Go version defined in go.mod to make it easier | ||
# to perform upgrades. | ||
go-version-file: go.mod | ||
- name: Vet | ||
run: go vet -v -unreachable=false ./... | ||
- name: Build | ||
run: go build -v ./... | ||
- name: Test | ||
run: go test ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# FHIRPath | ||
|
||
This package contains a Go implementation of the [FHIRPath][fhirpath] specification, implemented directly with | ||
the [google/fhir][google-fhir] proto definitions. | ||
|
||
This package aims to be compliant with both: | ||
|
||
- the [N1 Normative Release](http://hl7.org/fhirpath/N1/) specification, and | ||
- the [R4 specifications](http://hl7.org/fhir/R4/fhirpath.html). | ||
|
||
## Import | ||
|
||
```go | ||
import "github.com/verily-src/fhirpath-go/fhirpath" | ||
``` | ||
|
||
## Usage | ||
|
||
A FHIRPath must be compiled before running it against a resource using the `Compile` method like so: | ||
|
||
```go | ||
expression, err := fhirpath.Compile("Patient.name.given") | ||
if err != nil { | ||
panic("error while compiling FHIRPath") | ||
} | ||
``` | ||
|
||
The compilation result can then be run against a resource: | ||
|
||
```go | ||
inputResources := []fhir.Resource{somePatient, someMedication} | ||
|
||
result, err := expression.Evaluate(inputResources) | ||
if err != nil { | ||
panic("error while running FHIRPath against resource") | ||
} | ||
``` | ||
|
||
As defined in the FHIRPath specification, the output of evaluation is a **Collection**. So, the | ||
result of Evaluate is of type `[]any`. As such, the result must be unpacked and cast to the desired | ||
type for further processing. | ||
|
||
### CompileOptions and EvaluateOptions | ||
|
||
Options are provided for optional modification of compilation and evaluation. There is currently | ||
support for: | ||
|
||
- adding custom functions during Compile time | ||
- adding custom external constant variables | ||
|
||
#### To add a custom function | ||
|
||
The constraints on the custom function are as follows: | ||
|
||
- First argument must be `system.Collection` | ||
- Arguments that follow must be either a fhir proto type or primitive system type | ||
|
||
```go | ||
customFn := func (input system.Collection, args ...any) (system.Collection error) { | ||
fmt.Print("called custom fn") | ||
return input, nil | ||
} | ||
expression, err := fhirpath.Compile("print()", WithFunction("print", customFn)) | ||
``` | ||
|
||
#### To add external constants | ||
|
||
The constraints on external constants are as follows: | ||
|
||
- Must be a fhir proto type, primitive system type, or `system.Collection` | ||
- If you pass in a collection, contained elements must be fhir proto or system type. | ||
|
||
```go | ||
customVar := system.String("custom variable") | ||
result, err := expression.Evaluate([]fhir.Resource{someResource}, WithConstant("var", customVar)) | ||
``` | ||
|
||
### System Types | ||
|
||
The FHIRPath [spec](http://hl7.org/fhirpath/N1/#literals) defines the following custom System types: | ||
|
||
- Boolean | ||
- String | ||
- Integer | ||
- Decimal | ||
- Quantity | ||
- Date | ||
- Time | ||
- DateTime | ||
|
||
FHIR Protos get implicitly converted to the above types according to this | ||
[chart](http://hl7.org/fhir/R4/fhirpath.html#types), when used in some FHIRPath expressions. | ||
|
||
### Things to be aware of | ||
|
||
FHIRPath is not the most intuitive language, and there are some quirks. See [gotchas](gotchas.md). | ||
|
||
[fhirpath]: http://hl7.org/fhirpath/ | ||
[google-fhir]: https://github.com/google/fhir |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*antlr*.jar | ||
*.interp | ||
*.tokens | ||
*fhirpath_base_visitor.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
Oops, something went wrong.