Skip to content

Commit 7e79427

Browse files
committed
Add Support Polymorphic Data Elements
Closes: #1 See also: #7
1 parent 4dbe742 commit 7e79427

File tree

456 files changed

+4857
-1571
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

456 files changed

+4857
-1571
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@
187187
same "printed page" as the copyright notice for easier
188188
identification within third-party archives.
189189

190-
Copyright 2019 - 2021 The Samply Community
190+
Copyright 2019 - 2022 The Samply Community
191191

192192
Licensed under the Apache License, Version 2.0 (the "License");
193193
you may not use this file except in compliance with the License.

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ In your project, import `github.com/samply/golang-fhir-models/fhir-models/fhir`
1515

1616
## TODOs
1717

18-
* [Support Polymorphic Data Elements](https://github.com/samply/golang-fhir-models/issues/1)
1918
* [Support ValueSets Referring to Multiple CodeSystems](https://github.com/samply/golang-fhir-models/issues/2)
2019

2120
## Develop

fhir-models-gen/cmd/genResources.go

+119-79
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019 - 2021 The Samply Community
1+
// Copyright 2019 - 2022 The Samply Community
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ func UnmarshalResource(b []byte) (Resource, error) {
4747
type ResourceMap = map[string]map[string][]byte
4848

4949
var licenseComment = Split(Trim(`
50-
Copyright 2019 - 2021 The Samply Community
50+
Copyright 2019 - 2022 The Samply Community
5151
5252
Licensed under the Apache License, Version 2.0 (the "License");
5353
you may not use this file except in compliance with the License.
@@ -318,7 +318,9 @@ func appendGeneratorComment(file *jen.File) {
318318
file.Comment("// THIS FILE IS GENERATED BY https://github.com/samply/golang-fhir-models\n// PLEASE DO NOT EDIT BY HAND\n")
319319
}
320320

321-
func appendFields(resources ResourceMap, requiredTypes map[string]bool, requiredValueSetBindings map[string]bool, file *jen.File, fields *jen.Group, parentName string, elementDefinitions []fhir.ElementDefinition, start, level int) (int, error) {
321+
func appendFields(resources ResourceMap, requiredTypes map[string]bool, requiredValueSetBindings map[string]bool,
322+
file *jen.File, fields *jen.Group, parentName string, elementDefinitions []fhir.ElementDefinition, start,
323+
level int) (int, error) {
322324
//fmt.Printf("appendFields parentName=%s, start=%d, level=%d\n", parentName, start, level)
323325
for i := start; i < len(elementDefinitions); i++ {
324326
element := elementDefinitions[i]
@@ -346,88 +348,27 @@ func appendFields(resources ResourceMap, requiredTypes map[string]bool, required
346348
}
347349
statement.Id(typeIdentifier).Tag(map[string]string{"json": pathParts[level] + ",omitempty", "bson": pathParts[level] + ",omitempty"})
348350
}
349-
// support polymorphic elements later
350351
case 1:
351-
statement := fields.Id(name)
352+
var err error
353+
i, err = addFieldStatement(resources, requiredTypes, requiredValueSetBindings, file, fields,
354+
pathParts[level], parentName, elementDefinitions, i, level, element.Type[0])
352355

353-
switch element.Type[0].Code {
354-
case "code":
355-
if *element.Max == "*" {
356-
statement.Op("[]")
357-
} else if *element.Min == 0 {
358-
statement.Op("*")
359-
}
356+
if err != nil {
357+
return 0, err
358+
}
359+
default: //polymorphic type
360+
name = Replace(pathParts[level], "[x]", "", -1)
361+
for _, eleType := range element.Type {
362+
name := name + Title(eleType.Code)
360363

361-
if url := requiredValueSetBinding(element); url != nil {
362-
if bytes := resources["ValueSet"][*url]; bytes != nil {
363-
valueSet, err := fhir.UnmarshalValueSet(bytes)
364-
if err != nil {
365-
return 0, err
366-
}
367-
if name := valueSet.Name; name != nil {
368-
if !namePattern.MatchString(*name) {
369-
fmt.Printf("Skip generating an enum for a ValueSet binding to `%s` because the ValueSet has a non-conforming name.\n", *name)
370-
statement.Id("string")
371-
} else if len(valueSet.Compose.Include) > 1 {
372-
fmt.Printf("Skip generating an enum for a ValueSet binding to `%s` because the ValueSet includes more than one CodeSystem.\n", *valueSet.Name)
373-
statement.Id("string")
374-
} else if codeSystemUrl := canonical(valueSet.Compose.Include[0]); resources["CodeSystem"][codeSystemUrl] == nil {
375-
fmt.Printf("Skip generating an enum for a ValueSet binding to `%s` because the ValueSet includes the non-existing CodeSystem with canonical URL `%s`.\n", *valueSet.Name, codeSystemUrl)
376-
statement.Id("string")
377-
} else {
378-
requiredValueSetBindings[*url] = true
379-
statement.Id(*name)
380-
}
381-
} else {
382-
return 0, fmt.Errorf("missing name in ValueSet with canonical URL `%s`", *url)
383-
}
384-
} else {
385-
statement.Id("string")
386-
}
387-
} else {
388-
statement.Id("string")
389-
}
390-
case "Resource":
391-
statement.Qual("encoding/json", "RawMessage")
392-
default:
393-
if *element.Max == "*" {
394-
statement.Op("[]")
395-
} else if *element.Min == 0 {
396-
statement.Op("*")
397-
}
364+
var err error
365+
i, err = addFieldStatement(resources, requiredTypes, requiredValueSetBindings, file, fields,
366+
name, parentName, elementDefinitions, i, level, eleType)
398367

399-
var typeIdentifier string
400-
if parentName == "Element" && name == "Id" ||
401-
parentName == "Extension" && name == "Url" {
402-
typeIdentifier = "string"
403-
} else {
404-
typeIdentifier = typeCodeToTypeIdentifier(element.Type[0].Code)
405-
}
406-
if typeIdentifier == "Element" || typeIdentifier == "BackboneElement" {
407-
backboneElementName := parentName + name
408-
statement.Id(backboneElementName)
409-
var err error
410-
file.Type().Id(backboneElementName).StructFunc(func(childFields *jen.Group) {
411-
//var err error
412-
i, err = appendFields(resources, requiredTypes, requiredValueSetBindings, file, childFields, backboneElementName, elementDefinitions, i+1, level+1)
413-
})
414-
if err != nil {
415-
return 0, err
416-
}
417-
i--
418-
} else {
419-
if unicode.IsUpper(rune(typeIdentifier[0])) {
420-
requiredTypes[typeIdentifier] = true
421-
}
422-
statement.Id(typeIdentifier)
368+
if err != nil {
369+
return 0, err
423370
}
424371
}
425-
426-
if *element.Min == 0 {
427-
statement.Tag(map[string]string{"json": pathParts[level] + ",omitempty", "bson": pathParts[level] + ",omitempty"})
428-
} else {
429-
statement.Tag(map[string]string{"json": pathParts[level], "bson": pathParts[level]})
430-
}
431372
}
432373
}
433374
} else {
@@ -438,6 +379,105 @@ func appendFields(resources ResourceMap, requiredTypes map[string]bool, required
438379
return 0, nil
439380
}
440381

382+
func addFieldStatement(
383+
resources ResourceMap,
384+
requiredTypes map[string]bool,
385+
requiredValueSetBindings map[string]bool,
386+
file *jen.File,
387+
fields *jen.Group,
388+
name string,
389+
parentName string,
390+
elementDefinitions []fhir.ElementDefinition,
391+
elementIndex, level int,
392+
elementType fhir.ElementDefinitionType,
393+
) (idx int, err error) {
394+
fieldName := Title(name)
395+
element := elementDefinitions[elementIndex]
396+
statement := fields.Id(fieldName)
397+
398+
switch elementType.Code {
399+
case "code":
400+
if *element.Max == "*" {
401+
statement.Op("[]")
402+
} else if *element.Min == 0 {
403+
statement.Op("*")
404+
}
405+
406+
if url := requiredValueSetBinding(element); url != nil {
407+
if bytes := resources["ValueSet"][*url]; bytes != nil {
408+
valueSet, err := fhir.UnmarshalValueSet(bytes)
409+
if err != nil {
410+
return 0, err
411+
}
412+
if name := valueSet.Name; name != nil {
413+
if !namePattern.MatchString(*name) {
414+
fmt.Printf("Skip generating an enum for a ValueSet binding to `%s` because the ValueSet has a non-conforming name.\n", *name)
415+
statement.Id("string")
416+
} else if len(valueSet.Compose.Include) > 1 {
417+
fmt.Printf("Skip generating an enum for a ValueSet binding to `%s` because the ValueSet includes more than one CodeSystem.\n", *valueSet.Name)
418+
statement.Id("string")
419+
} else if codeSystemUrl := canonical(valueSet.Compose.Include[0]); resources["CodeSystem"][codeSystemUrl] == nil {
420+
fmt.Printf("Skip generating an enum for a ValueSet binding to `%s` because the ValueSet includes the non-existing CodeSystem with canonical URL `%s`.\n", *valueSet.Name, codeSystemUrl)
421+
statement.Id("string")
422+
} else {
423+
requiredValueSetBindings[*url] = true
424+
statement.Id(*name)
425+
}
426+
} else {
427+
return 0, fmt.Errorf("missing name in ValueSet with canonical URL `%s`", *url)
428+
}
429+
} else {
430+
statement.Id("string")
431+
}
432+
} else {
433+
statement.Id("string")
434+
}
435+
case "Resource":
436+
statement.Qual("encoding/json", "RawMessage")
437+
default:
438+
if *element.Max == "*" {
439+
statement.Op("[]")
440+
} else if *element.Min == 0 {
441+
statement.Op("*")
442+
}
443+
444+
var typeIdentifier string
445+
if parentName == "Element" && fieldName == "Id" ||
446+
parentName == "Extension" && fieldName == "Url" {
447+
typeIdentifier = "string"
448+
} else {
449+
typeIdentifier = typeCodeToTypeIdentifier(elementType.Code)
450+
}
451+
if typeIdentifier == "Element" || typeIdentifier == "BackboneElement" {
452+
backboneElementName := parentName + fieldName
453+
statement.Id(backboneElementName)
454+
var err error
455+
file.Type().Id(backboneElementName).StructFunc(func(childFields *jen.Group) {
456+
//var err error
457+
elementIndex, err = appendFields(resources, requiredTypes, requiredValueSetBindings, file, childFields,
458+
backboneElementName, elementDefinitions, elementIndex+1, level+1)
459+
})
460+
if err != nil {
461+
return 0, err
462+
}
463+
elementIndex--
464+
} else {
465+
if unicode.IsUpper(rune(typeIdentifier[0])) {
466+
requiredTypes[typeIdentifier] = true
467+
}
468+
statement.Id(typeIdentifier)
469+
}
470+
}
471+
472+
if *element.Min == 0 {
473+
statement.Tag(map[string]string{"json": name + ",omitempty", "bson": name + ",omitempty"})
474+
} else {
475+
statement.Tag(map[string]string{"json": name, "bson": name})
476+
}
477+
478+
return elementIndex, err
479+
}
480+
441481
func requiredValueSetBinding(elementDefinition fhir.ElementDefinition) *string {
442482
if elementDefinition.Binding != nil {
443483
binding := *elementDefinition.Binding

fhir-models-gen/cmd/root.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019 - 2021 The Samply Community
1+
// Copyright 2019 - 2022 The Samply Community
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ import (
2323
// rootCmd represents the base command when called without any subcommands
2424
var rootCmd = &cobra.Command{
2525
Use: "fhir-gen",
26-
Version: "0.2.1",
26+
Version: "0.3.0",
2727
}
2828

2929
// Execute adds all child commands to the root command and sets flags appropriately.

fhir-models-gen/fhir/address.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2019 - 2022 The Samply Community
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package fhir
16+
17+
// THIS FILE IS GENERATED BY https://github.com/samply/golang-fhir-models
18+
// PLEASE DO NOT EDIT BY HAND
19+
20+
// Address is documented here http://hl7.org/fhir/StructureDefinition/Address
21+
type Address struct {
22+
Id *string `bson:"id,omitempty" json:"id,omitempty"`
23+
Extension []Extension `bson:"extension,omitempty" json:"extension,omitempty"`
24+
Use *AddressUse `bson:"use,omitempty" json:"use,omitempty"`
25+
Type *AddressType `bson:"type,omitempty" json:"type,omitempty"`
26+
Text *string `bson:"text,omitempty" json:"text,omitempty"`
27+
Line []string `bson:"line,omitempty" json:"line,omitempty"`
28+
City *string `bson:"city,omitempty" json:"city,omitempty"`
29+
District *string `bson:"district,omitempty" json:"district,omitempty"`
30+
State *string `bson:"state,omitempty" json:"state,omitempty"`
31+
PostalCode *string `bson:"postalCode,omitempty" json:"postalCode,omitempty"`
32+
Country *string `bson:"country,omitempty" json:"country,omitempty"`
33+
Period *Period `bson:"period,omitempty" json:"period,omitempty"`
34+
}

fhir-models-gen/fhir/addressType.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2019 - 2022 The Samply Community
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package fhir
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
"strings"
21+
)
22+
23+
// THIS FILE IS GENERATED BY https://github.com/samply/golang-fhir-models
24+
// PLEASE DO NOT EDIT BY HAND
25+
26+
// AddressType is documented here http://hl7.org/fhir/ValueSet/address-type
27+
type AddressType int
28+
29+
const (
30+
AddressTypePostal AddressType = iota
31+
AddressTypePhysical
32+
AddressTypeBoth
33+
)
34+
35+
func (code AddressType) MarshalJSON() ([]byte, error) {
36+
return json.Marshal(code.Code())
37+
}
38+
func (code *AddressType) UnmarshalJSON(json []byte) error {
39+
s := strings.Trim(string(json), "\"")
40+
switch s {
41+
case "postal":
42+
*code = AddressTypePostal
43+
case "physical":
44+
*code = AddressTypePhysical
45+
case "both":
46+
*code = AddressTypeBoth
47+
default:
48+
return fmt.Errorf("unknown AddressType code `%s`", s)
49+
}
50+
return nil
51+
}
52+
func (code AddressType) String() string {
53+
return code.Code()
54+
}
55+
func (code AddressType) Code() string {
56+
switch code {
57+
case AddressTypePostal:
58+
return "postal"
59+
case AddressTypePhysical:
60+
return "physical"
61+
case AddressTypeBoth:
62+
return "both"
63+
}
64+
return "<unknown>"
65+
}
66+
func (code AddressType) Display() string {
67+
switch code {
68+
case AddressTypePostal:
69+
return "Postal"
70+
case AddressTypePhysical:
71+
return "Physical"
72+
case AddressTypeBoth:
73+
return "Postal & Physical"
74+
}
75+
return "<unknown>"
76+
}
77+
func (code AddressType) Definition() string {
78+
switch code {
79+
case AddressTypePostal:
80+
return "Mailing addresses - PO Boxes and care-of addresses."
81+
case AddressTypePhysical:
82+
return "A physical address that can be visited."
83+
case AddressTypeBoth:
84+
return "An address that is both physical and postal."
85+
}
86+
return "<unknown>"
87+
}

0 commit comments

Comments
 (0)