diff --git a/namespace/namespace.go b/namespace/namespace.go index 0f434ef..a3aff18 100644 --- a/namespace/namespace.go +++ b/namespace/namespace.go @@ -2,9 +2,10 @@ package namespace import ( "fmt" - "reflect" + stdreflect "reflect" fhir "github.com/friendly-fhir/go-fhir/r4/core" + "github.com/friendly-fhir/go-fhirpath/reflect" "github.com/friendly-fhir/go-fhirpath/system" ) @@ -13,12 +14,15 @@ var ( R4 = New("FHIR", fhirNamer, r4Element, r4Resource, r4Domain, r4Backbone) // System is the system namespace that contains all system types. - System = New("System", systemNamer, systemAny) + System = New("System", basicNamer, systemAny) + + // Reflect is the namespace that contains all reflection types. + Reflect = New("Reflect", basicNamer, reflectInfo, reflectElement) ) // Select returns the namespace that contains the specified type. If no // namespace contains the type, then nil is returned. -func Select(t reflect.Type, namespaces ...*Namespace) *Namespace { +func Select(t stdreflect.Type, namespaces ...*Namespace) *Namespace { for _, ns := range namespaces { if ns.Contains(t) { return ns @@ -30,15 +34,15 @@ func Select(t reflect.Type, namespaces ...*Namespace) *Namespace { // Namer is an interface that can be used to generate names for types. type Namer interface { // Name returns the name of the type. - Name(reflect.Type) string + Name(stdreflect.Type) reflect.TypeSpecifier } // NamerFunc is a convenience type that implements the Namer interface. This // simplifies constructing a function that acts as a name function. -type NamerFunc func(reflect.Type) string +type NamerFunc func(stdreflect.Type) reflect.TypeSpecifier // Name calls the underlying function to generate the name. -func (f NamerFunc) Name(t reflect.Type) string { +func (f NamerFunc) Name(t stdreflect.Type) reflect.TypeSpecifier { return f(t) } @@ -50,11 +54,11 @@ var _ Namer = (*NamerFunc)(nil) type Namespace struct { namer Namer name string - interfaces []reflect.Type + interfaces []stdreflect.Type } // New constructs a new namespace with the specified name, namer, and interfaces. -func New(name string, namer Namer, ifaces ...reflect.Type) *Namespace { +func New(name string, namer Namer, ifaces ...stdreflect.Type) *Namespace { return &Namespace{ name: name, namer: namer, @@ -69,18 +73,18 @@ func (n *Namespace) String() string { // QualifiedName returns the qualified name of the type in this namespace. // This is a namespace-prefixed type-name. -func (n *Namespace) QualifiedName(t reflect.Type) string { - return fmt.Sprintf("%s.%s", n.name, n.namer.Name(t)) +func (n *Namespace) QualifiedName(t stdreflect.Type) reflect.TypeSpecifier { + return reflect.TypeSpecifier(fmt.Sprintf("%s.%s", n.name, n.namer.Name(t))) } // Name returns the name of the type as it appears in the namespace. This will // not include the namespace prefix. -func (n *Namespace) Name(t reflect.Type) string { +func (n *Namespace) Name(t stdreflect.Type) reflect.TypeSpecifier { return n.namer.Name(t) } // Contains returns true if the namespace contains the specified type. -func (n *Namespace) Contains(t reflect.Type) bool { +func (n *Namespace) Contains(t stdreflect.Type) bool { for _, iface := range n.interfaces { if t.Implements(iface) { return true @@ -90,21 +94,26 @@ func (n *Namespace) Contains(t reflect.Type) bool { } var ( - systemAny = reflect.TypeOf((*system.Any)(nil)).Elem() - r4Element = reflect.TypeOf((*fhir.Element)(nil)).Elem() - r4Backbone = reflect.TypeOf((*fhir.BackboneElement)(nil)).Elem() - r4Resource = reflect.TypeOf((*fhir.Resource)(nil)).Elem() - r4Domain = reflect.TypeOf((*fhir.DomainResource)(nil)).Elem() - - fhirNamer = NamerFunc(func(t reflect.Type) string { - if t.Kind() == reflect.Ptr { + systemAny = stdreflect.TypeOf((*system.Any)(nil)).Elem() + r4Element = stdreflect.TypeOf((*fhir.Element)(nil)).Elem() + r4Backbone = stdreflect.TypeOf((*fhir.BackboneElement)(nil)).Elem() + r4Resource = stdreflect.TypeOf((*fhir.Resource)(nil)).Elem() + r4Domain = stdreflect.TypeOf((*fhir.DomainResource)(nil)).Elem() + reflectInfo = stdreflect.TypeOf((*reflect.Info)(nil)).Elem() + reflectElement = stdreflect.TypeOf((*reflect.InfoElement)(nil)).Elem() + + fhirNamer = NamerFunc(func(t stdreflect.Type) reflect.TypeSpecifier { + if t.Kind() == stdreflect.Ptr { t = t.Elem() } name := t.Name() // TODO: primitives should be in camelCase instead of PascalCase. - return name + return reflect.TypeSpecifier(name) }) - systemNamer = NamerFunc(func(t reflect.Type) string { - return t.Name() + basicNamer = NamerFunc(func(t stdreflect.Type) reflect.TypeSpecifier { + if t.Kind() == stdreflect.Ptr { + t = t.Elem() + } + return reflect.TypeSpecifier(t.Name()) }) ) diff --git a/namespace/namespace_test.go b/namespace/namespace_test.go index 43b5009..152162e 100644 --- a/namespace/namespace_test.go +++ b/namespace/namespace_test.go @@ -7,6 +7,7 @@ import ( fhir "github.com/friendly-fhir/go-fhir/r4/core" "github.com/friendly-fhir/go-fhir/r4/core/resources/patient" "github.com/friendly-fhir/go-fhirpath/namespace" + fpreflect "github.com/friendly-fhir/go-fhirpath/reflect" "github.com/friendly-fhir/go-fhirpath/system" ) @@ -36,12 +37,16 @@ func TestSelect(t *testing.T) { name: "Unknown type", input: reflect.TypeOf((*testing.T)(nil)), want: nil, + }, { + name: "Reflect type", + input: reflect.TypeOf((*fpreflect.ClassInfo)(nil)), + want: namespace.Reflect, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - got := namespace.Select(tc.input, namespace.R4, namespace.System) + got := namespace.Select(tc.input, namespace.R4, namespace.System, namespace.Reflect) if got != tc.want { t.Errorf("namespace.Select() = %v; want %v", got, tc.want) @@ -55,7 +60,7 @@ func TestNamespaceName(t *testing.T) { name string input reflect.Type namespace *namespace.Namespace - want string + want fpreflect.TypeSpecifier }{ { name: "FHIR resource", @@ -77,6 +82,11 @@ func TestNamespaceName(t *testing.T) { input: reflect.TypeOf((*system.String)(nil)).Elem(), namespace: namespace.System, want: "String", + }, { + name: "Reflect type", + input: reflect.TypeOf((*fpreflect.ClassInfo)(nil)), + namespace: namespace.Reflect, + want: "ClassInfo", }, } @@ -105,6 +115,10 @@ func TestNamespaceString(t *testing.T) { name: "System namespace", namespace: namespace.System, want: "System", + }, { + name: "Reflect namespace", + namespace: namespace.Reflect, + want: "Reflect", }, } @@ -124,7 +138,7 @@ func TestNamespaceQualifiedName(t *testing.T) { name string input reflect.Type namespace *namespace.Namespace - want string + want fpreflect.TypeSpecifier }{ { name: "FHIR resource", @@ -146,6 +160,11 @@ func TestNamespaceQualifiedName(t *testing.T) { input: reflect.TypeOf((*system.String)(nil)).Elem(), namespace: namespace.System, want: "System.String", + }, { + name: "Reflect type", + input: reflect.TypeOf((*fpreflect.ClassInfo)(nil)), + namespace: namespace.Reflect, + want: "Reflect.ClassInfo", }, } @@ -192,6 +211,16 @@ func TestNamespaceContains(t *testing.T) { input: reflect.TypeOf((*system.String)(nil)).Elem(), namespace: namespace.R4, want: false, + }, { + name: "Reflect type in Reflect namespace", + input: reflect.TypeOf((*fpreflect.ClassInfo)(nil)), + namespace: namespace.Reflect, + want: true, + }, { + name: "FHIR resource in Reflect namespace", + input: reflect.TypeOf((*patient.Patient)(nil)), + namespace: namespace.Reflect, + want: false, }, } diff --git a/reflect/reflect.go b/reflect/reflect.go new file mode 100644 index 0000000..3b4e561 --- /dev/null +++ b/reflect/reflect.go @@ -0,0 +1,114 @@ +/* +Package reflect implements §10.2. Reflection from the FHIRPath specification. + +The types defined in this package are a subset of the types defined in the +Clinical Query Language (CQL) specification, but are limited solely to the usage +within FHIRPath. + +See https://hl7.org/fhirpath/N1/#reflection for more information. +*/ +package reflect + +// TypeSpecifier represents a reflection of a FHIRPath type specifier. +// +// Type specifier may name either FHIR or System-namespaced types, and may be +// either scalar or list values. In most FHIRPath applications, these will only +// ever be seen as scalar type values corresponding to the underlying resource or +// element. Lists are a byproduct that only present itself during reflection +// operations, but otherwise should never be seen in expressions. +type TypeSpecifier string + +// Info represents a reflection of a FHIRPath type. +type Info interface { + isInfo() +} + +// InfoElement represents an element from a reflection of a FHIRPath type. +type InfoElement interface { + isInfoElement() +} + +// SimpleTypeInfo is a Go implementation of §10.2.1 "Primitive Types" from +// the FHIRPath specification. +// +// See: https://hl7.org/fhirpath/N1/#primitive-types +type SimpleTypeInfo struct { + Namespace string `json:"namespace" yaml:"namespace" fhirpath:"namespace"` + Name string `json:"name" yaml:"name" fhirpath:"name"` + BaseType TypeSpecifier `json:"baseType" yaml:"baseType" fhirpath:"baseType"` +} + +func (SimpleTypeInfo) isInfo() {} + +var _ Info = (*SimpleTypeInfo)(nil) + +// ClassInfoElement is a Go implementation of §10.2.2 "Class Types" from +// the FHIRPath specification. +// +// This is a compound type that is built into larger structures in the +// [ClassInfo] type. +// +// See https://hl7.org/fhirpath/N1/#class-types +type ClassInfoElement struct { + Name string `json:"name" yaml:"name" fhirpath:"name"` + Type TypeSpecifier `json:"type" yaml:"type" fhirpath:"type"` + IsOneBased bool `json:"isOneBased" yaml:"isOneBased" fhirpath:"isOneBased"` +} + +func (ClassInfoElement) isInfoElement() {} + +// ClassInfo is a Go implementation of §10.2.2 "Class Types" from +// the FHIRPath specification. +// +// See https://hl7.org/fhirpath/N1/#class-types +type ClassInfo struct { + Namespace string `json:"namespace" yaml:"namespace" fhirpath:"namespace"` + Name string `json:"name" yaml:"name" fhirpath:"name"` + BaseType TypeSpecifier `json:"baseType" yaml:"baseType" fhirpath:"baseType"` + Element []ClassInfoElement `json:"element" yaml:"element" fhirpath:"element"` +} + +func (ClassInfo) isInfo() {} + +var _ Info = (*ClassInfo)(nil) + +// ListTypeInfo is a Go implementation of §10.2.3 "Collection Types" from +// the FHIRPath specification. +// +// See https://hl7.org/fhirpath/N1/#collection-types +type ListTypeInfo struct { + ElementType TypeSpecifier `json:"elementType" yaml:"elementType" fhirpath:"elementType"` +} + +func (ListTypeInfo) isInfo() {} + +var _ Info = (*ListTypeInfo)(nil) + +// TupleTypeInfoElement is a Go implementation of §10.2.4 "Anonymous Types" from +// the FHIRPath specification. +// +// This is a compound type that is built into larger structures in the +// [TupleTypeInfo] type. +// +// See https://hl7.org/fhirpath/N1/#anonymous-types +type TupleTypeInfoElement struct { + Name string `json:"name" yaml:"name" fhirpath:"name"` + Type TypeSpecifier `json:"type" yaml:"type" fhirpath:"type"` + IsOneBased bool `json:"isOneBased" yaml:"isOneBased" fhirpath:"isOneBased"` +} + +func (TupleTypeInfoElement) isInfoElement() {} + +var _ InfoElement = (*TupleTypeInfoElement)(nil) + +// TupleTypeInfo is a Go implementation of §10.2.4 "Anonymous Types" from +// the FHIRPath specification. +// +// See https://hl7.org/fhirpath/N1/#anonymous-types +type TupleTypeInfo struct { + Element []TupleTypeInfoElement `json:"element" yaml:"element" fhirpath:"element"` +} + +func (TupleTypeInfo) isInfo() {} + +var _ Info = (*TupleTypeInfo)(nil)