generated from friendly-fhir/go-template
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds an initial system package for the FHIRPath implementation. The types are from the N1 specification, and are beginning with an initial set of types: * Booleans * Integers * Strings This is the first step in the implementation of the FHIRPath. This format is not expected to be the final format, since the `fhir` package is still itself unstable.
- Loading branch information
1 parent
e048960
commit 97b2a25
Showing
13 changed files
with
1,004 additions
and
1 deletion.
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
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 |
---|---|---|
@@ -1,4 +1,10 @@ | ||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= | ||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= | ||
github.com/friendly-fhir/go-fhir v0.0.0-20240627035249-eacfb3386af5 h1:D6ephfRcx17SrIHrWz2HTDu19Y6Zy1J9YTRyal0ZUnw= | ||
github.com/friendly-fhir/go-fhir v0.0.0-20240627035249-eacfb3386af5/go.mod h1:dHmN8TwYULp9TAOI1D0cR1RpsIntTM75Y/aUq3v9fY0= | ||
github.com/friendly-fhir/go-fhir v0.0.0-20240627230005-9ef2174c1f29 h1:8PXLQ1rNBD7CRPlDjJYaDFu3ZQFSsSnV5bl+3QUSs0k= | ||
github.com/friendly-fhir/go-fhir v0.0.0-20240627230005-9ef2174c1f29/go.mod h1:dHmN8TwYULp9TAOI1D0cR1RpsIntTM75Y/aUq3v9fY0= | ||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= | ||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= |
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,38 @@ | ||
/* | ||
Package esc provides basic functionality for handling the ESC (escape) sequences | ||
in the FHIRPath grammar. | ||
*/ | ||
package esc | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
var escaper *strings.Replacer | ||
|
||
func init() { | ||
escaper = strings.NewReplacer( | ||
`\'`, `\u0027`, | ||
`\"`, `\u0022`, | ||
"\\`", `\u0060`, | ||
"\\r", `\u000d`, | ||
"\\n", `\u000a`, | ||
"\\t", `\u0009`, | ||
"\\f", `\u000c`, | ||
"\\\\", `\u005c`, | ||
) | ||
} | ||
|
||
func Parse(input string) (string, error) { | ||
result := input | ||
result = escaper.Replace(result) | ||
// Re-escape any remaining quotes, so that Unquote won't fail. | ||
result = strings.ReplaceAll(result, `"`, `\u0022`) | ||
result, err := strconv.Unquote(fmt.Sprintf(`"%v"`, result)) | ||
if err != nil { | ||
return "", err | ||
} | ||
return result, 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,17 @@ | ||
package system | ||
|
||
// Any is the top-level system type for FHIRPath. | ||
type Any interface { | ||
isAny() | ||
} | ||
|
||
// IsType checks whether a given type string is a valid FHIRPath System type | ||
// name value. This function is case-sensitive. | ||
func IsType(ty string) bool { | ||
switch ty { | ||
case "Boolean", "Integer", "Any", "Date", "DateTime", "Decimal", "Quantity", | ||
"String", "Time": | ||
return true | ||
} | ||
return false | ||
} |
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,121 @@ | ||
package system | ||
|
||
import ( | ||
"encoding" | ||
"encoding/json" | ||
"fmt" | ||
"strconv" | ||
|
||
fhir "github.com/friendly-fhir/go-fhir/r4/core" | ||
) | ||
|
||
// Boolean is the FHIRPath system-type representation of the "boolean" value. | ||
type Boolean bool | ||
|
||
// NewBoolean constructs a Boolean object with the underlying value. | ||
// | ||
// This function primarily exists for symmetry with the other constructor | ||
// functions. | ||
func NewBoolean(b bool) Boolean { | ||
return Boolean(b) | ||
} | ||
|
||
// ParseBoolean parses a string into the valid FHIRPath System.Boolean type. | ||
func ParseBoolean(str string) (Boolean, error) { | ||
switch str { | ||
case "true": | ||
return true, nil | ||
case "false": | ||
return false, nil | ||
} | ||
return false, newParseError[Boolean](str, nil) | ||
} | ||
|
||
// MustParseBoolean parses a boolean string, and panics if the value is invalid. | ||
func MustParseBoolean(str string) Boolean { | ||
got, err := ParseBoolean(str) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return got | ||
} | ||
|
||
func (Boolean) isAny() {} | ||
|
||
// Negate returns the inverse polarity of this boolean value. | ||
func (b Boolean) Negate() Boolean { | ||
return !b | ||
} | ||
|
||
// Bool returns the Go boolean representation of the System.Boolean. | ||
func (b Boolean) Bool() bool { | ||
return bool(b) | ||
} | ||
|
||
// Formatting | ||
|
||
// String returns the string representation of the System.Boolean. | ||
func (b Boolean) String() string { | ||
return strconv.FormatBool(bool(b)) | ||
} | ||
|
||
// Format implements the fmt.Formatter interface. | ||
func (b Boolean) Format(s fmt.State, verb rune) { | ||
fmt.Fprintf(s, "%"+string(verb), bool(b)) | ||
} | ||
|
||
var ( | ||
_ fmt.Stringer = (*Boolean)(nil) | ||
_ fmt.Formatter = (*Boolean)(nil) | ||
) | ||
|
||
// R4 conversions | ||
|
||
// FromR4 converts a FHIR Boolean type into a System.Boolean type. | ||
func (b *Boolean) FromR4(r *fhir.Boolean) { | ||
*b = Boolean(r.Value) | ||
} | ||
|
||
// R4 converts this System.Boolean into a FHIR Boolean type. | ||
func (b Boolean) R4() *fhir.Boolean { | ||
return &fhir.Boolean{Value: bool(b)} | ||
} | ||
|
||
// JSON conversions | ||
|
||
// MarshalJSON converts this Boolean object into a JSON object. | ||
func (b Boolean) MarshalJSON() ([]byte, error) { | ||
return json.Marshal(bool(b)) | ||
} | ||
|
||
// UnmarshalJSON converts a JSON object into a Boolean object. | ||
func (b *Boolean) UnmarshalJSON(data []byte) error { | ||
var value bool | ||
if err := json.Unmarshal(data, &value); err != nil { | ||
return err | ||
} | ||
*b = Boolean(value) | ||
return nil | ||
} | ||
|
||
var ( | ||
_ json.Marshaler = (*Boolean)(nil) | ||
_ json.Unmarshaler = (*Boolean)(nil) | ||
) | ||
|
||
// Text conversions | ||
|
||
// MarshalText converts this Boolean object into a text object. | ||
func (b Boolean) MarshalText() ([]byte, error) { | ||
return b.MarshalJSON() | ||
} | ||
|
||
// UnmarshalText converts a text object into a Boolean object. | ||
func (b *Boolean) UnmarshalText(text []byte) error { | ||
return b.UnmarshalJSON(text) | ||
} | ||
|
||
var ( | ||
_ encoding.TextMarshaler = (*Boolean)(nil) | ||
_ encoding.TextUnmarshaler = (*Boolean)(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,151 @@ | ||
package system_test | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
fhir "github.com/friendly-fhir/go-fhir/r4/core" | ||
"github.com/friendly-fhir/go-fhirpath/system" | ||
"github.com/google/go-cmp/cmp" | ||
) | ||
|
||
func TestParseBoolean(t *testing.T) { | ||
testCases := []struct { | ||
input string | ||
want system.Boolean | ||
}{ | ||
{"false", false}, | ||
{"true", true}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.input, func(t *testing.T) { | ||
got, err := system.ParseBoolean(tc.input) | ||
if err != nil { | ||
t.Fatalf("ParseBoolean() = %v; want nil", err) | ||
} | ||
|
||
if got, want := got, tc.want; got != want { | ||
t.Errorf("ParseBoolean() = %v; want %v", got, want) | ||
} | ||
|
||
}) | ||
} | ||
} | ||
|
||
func TestParseBoolean_InvalidString_ReturnsParseError(t *testing.T) { | ||
testCases := []struct { | ||
input string | ||
}{ | ||
{"bad value"}, | ||
{"b"}, | ||
{"TRUE"}, | ||
{"1"}, | ||
{"FALSE"}, | ||
{"0"}, | ||
{"False"}, | ||
{"True"}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.input, func(t *testing.T) { | ||
_, err := system.ParseBoolean(tc.input) | ||
|
||
var parseErr *system.ParseError | ||
ok := errors.As(err, &parseErr) | ||
|
||
if got, want := ok, true; got != want { | ||
t.Errorf("ParseBoolean() = %v; want %v", got, want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestMustParseBoolean(t *testing.T) { | ||
testCases := []struct { | ||
input string | ||
want system.Boolean | ||
}{ | ||
{"false", false}, | ||
{"true", true}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.input, func(t *testing.T) { | ||
got := system.MustParseBoolean(tc.input) | ||
|
||
if got, want := got, tc.want; got != want { | ||
t.Errorf("ParseBoolean() = %v; want %v", got, want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestMustParseBoolean_InvalidString_Panics(t *testing.T) { | ||
testCases := []struct { | ||
input string | ||
}{ | ||
{"bad value"}, | ||
{"b"}, | ||
{"TRUE"}, | ||
{"1"}, | ||
{"FALSE"}, | ||
{"0"}, | ||
{"False"}, | ||
{"True"}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.input, func(t *testing.T) { | ||
defer func() { _ = recover() }() | ||
|
||
system.MustParseBoolean(tc.input) | ||
|
||
t.Errorf("MustParseBoolean() = want panic") | ||
}) | ||
} | ||
} | ||
|
||
func TestBooleanBool(t *testing.T) { | ||
testCases := []struct { | ||
input string | ||
want bool | ||
}{ | ||
{"false", false}, | ||
{"true", true}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.input, func(t *testing.T) { | ||
v := system.MustParseBoolean(tc.input) | ||
|
||
got := v.Bool() | ||
|
||
if got, want := got, tc.want; got != want { | ||
t.Errorf("Boolean.Bool() = %v; want %v", got, want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestBooleanR4(t *testing.T) { | ||
testCases := []struct { | ||
input string | ||
want *fhir.Boolean | ||
}{ | ||
{"false", &fhir.Boolean{Value: false}}, | ||
{"true", &fhir.Boolean{Value: true}}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.input, func(t *testing.T) { | ||
v := system.MustParseBoolean(tc.input) | ||
|
||
got := v.R4() | ||
|
||
if diff := cmp.Diff(got, tc.want); diff != "" { | ||
t.Errorf("Boolean.ToBoolean() mismatch (-got +want):\n%s", diff) | ||
} | ||
}) | ||
} | ||
} |
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,41 @@ | ||
package system | ||
|
||
import ( | ||
"fmt" | ||
|
||
fhir "github.com/friendly-fhir/go-fhir/r4/core" | ||
profile "github.com/friendly-fhir/go-fhir/r4/core/profiles" | ||
) | ||
|
||
// FromR4 return a system type from a (valid) FHIR R4 element. | ||
func FromR4(element fhir.Element) (Any, error) { | ||
switch e := element.(type) { | ||
case *fhir.Boolean: | ||
var b Boolean | ||
b.FromR4(e) | ||
return b, nil | ||
case profile.Integer: | ||
var i Integer | ||
i.FromR4(e) | ||
return i, nil | ||
case profile.String: | ||
var s String | ||
s.FromR4(e) | ||
return s, nil | ||
} | ||
return nil, fmt.Errorf("%w: %T is not a valid R4 type", ErrNotConvertible, element) | ||
} | ||
|
||
// Normalizes a FHIR R4 type into a system type, if able -- or just returns | ||
// the input value if it's not a FHIR R4 type. | ||
func Normalize(v any) any { | ||
element, ok := v.(fhir.Element) | ||
if !ok { | ||
return v | ||
} | ||
got, err := FromR4(element) | ||
if err != nil { | ||
return element | ||
} | ||
return got | ||
} |
Oops, something went wrong.