Skip to content

Commit f1f3c59

Browse files
authored
unmarshal HealthcareServices once (#388)
1 parent 2592537 commit f1f3c59

File tree

4 files changed

+86
-116
lines changed

4 files changed

+86
-116
lines changed

component/mcsd/component.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,15 +358,15 @@ func (c *Component) updateFromDirectory(ctx context.Context, fhirBaseURLRaw stri
358358
deduplicatedEntries := deduplicateHistoryEntries(entries)
359359

360360
// Filter to only include HealthcareService resources
361-
var allHealthcareServices []fhir.BundleEntry
361+
var allHealthcareServices []fhir.HealthcareService
362362
for _, entry := range entries {
363363
if entry.Resource == nil {
364364
continue
365365
}
366366
var healthcareService fhir.HealthcareService
367367
if err := json.Unmarshal(entry.Resource, &healthcareService); err == nil {
368368
// Successfully unmarshaled as HealthcareService
369-
allHealthcareServices = append(allHealthcareServices, entry)
369+
allHealthcareServices = append(allHealthcareServices, healthcareService)
370370
}
371371
}
372372

component/mcsd/updater.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
//
2626
// Resources are only synced to the query directory if they come from non-discoverable directories.
2727
// Discoverable directories are for discovery only and their resources should not be synced.
28-
func buildUpdateTransaction(ctx context.Context, tx *fhir.Bundle, entry fhir.BundleEntry, validationRules ValidationRules, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.BundleEntry, isDiscoverableDirectory bool, sourceBaseURL string) (string, error) {
28+
func buildUpdateTransaction(ctx context.Context, tx *fhir.Bundle, entry fhir.BundleEntry, validationRules ValidationRules, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.HealthcareService, isDiscoverableDirectory bool, sourceBaseURL string) (string, error) {
2929
if entry.FullUrl == nil {
3030
return "", errors.New("missing 'fullUrl' field")
3131
}

component/mcsd/validator.go

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func ValidateParentOrganizations(parentOrganizationMap map[*fhir.Organization][]
3030

3131
// ValidateUpdate validates a FHIR resource create/update from a mCSD Administration Directory,
3232
// according to the rules specified by https://nuts-foundation.github.io/nl-generic-functions-ig/care-services.html#update-client
33-
func ValidateUpdate(ctx context.Context, rules ValidationRules, resourceJSON []byte, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.BundleEntry) error {
33+
func ValidateUpdate(ctx context.Context, rules ValidationRules, resourceJSON []byte, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.HealthcareService) error {
3434
resourceAsMap := map[string]any{}
3535
if err := json.Unmarshal(resourceJSON, &resourceAsMap); err != nil {
3636
return fmt.Errorf("failed to unmarshal resource JSON: %w", err)
@@ -60,7 +60,7 @@ func ValidateUpdate(ctx context.Context, rules ValidationRules, resourceJSON []b
6060
return nil
6161
}
6262

63-
func unmarshalAndVisitResource[ResType any](ctx context.Context, resourceJSON []byte, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.BundleEntry, visitor func(ctx context.Context, resource *ResType, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.BundleEntry) error) error {
63+
func unmarshalAndVisitResource[ResType any](ctx context.Context, resourceJSON []byte, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.HealthcareService, visitor func(ctx context.Context, resource *ResType, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.HealthcareService) error) error {
6464
resource := new(ResType)
6565
if err := json.Unmarshal(resourceJSON, resource); err != nil {
6666
return fmt.Errorf("failed to unmarshal resource JSON: %w", err)
@@ -188,7 +188,7 @@ func validatePartOfChain(partOfRef *fhir.Reference, parentOrganizationMap map[*f
188188
return fmt.Errorf("organization's partOf reference could not be validated (organization %s not found within authoritative organizations)", refID)
189189
}
190190

191-
func validateHealthcareServiceResource(ctx context.Context, resource *fhir.HealthcareService, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.BundleEntry) error {
191+
func validateHealthcareServiceResource(ctx context.Context, resource *fhir.HealthcareService, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.HealthcareService) error {
192192
if resource.ProvidedBy == nil {
193193
slog.WarnContext(ctx, "Healthcare service missing providedBy reference")
194194
return fmt.Errorf("healthcare service must have a 'providedBy' referencing an Organization")
@@ -197,7 +197,7 @@ func validateHealthcareServiceResource(ctx context.Context, resource *fhir.Healt
197197
return assertReferencePointsToValidOrganization(resource.ProvidedBy, parentOrganizationMap, "healthcareService.providedBy")
198198
}
199199

200-
func validatePractitionerRoleResource(ctx context.Context, resource *fhir.PractitionerRole, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.BundleEntry) error {
200+
func validatePractitionerRoleResource(ctx context.Context, resource *fhir.PractitionerRole, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.HealthcareService) error {
201201
if resource.Organization == nil {
202202
slog.WarnContext(ctx, "Practitioner role missing organization reference")
203203
return fmt.Errorf("practitioner role must have an organization reference")
@@ -206,7 +206,7 @@ func validatePractitionerRoleResource(ctx context.Context, resource *fhir.Practi
206206
return assertReferencePointsToValidOrganization(resource.Organization, parentOrganizationMap, "practitionerRole.organization")
207207
}
208208

209-
func validateEndpointResource(ctx context.Context, resource *fhir.Endpoint, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.BundleEntry) error {
209+
func validateEndpointResource(ctx context.Context, resource *fhir.Endpoint, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.HealthcareService) error {
210210
if resource.Id == nil {
211211
return fmt.Errorf("endpoint must have an ID")
212212
}
@@ -215,7 +215,7 @@ func validateEndpointResource(ctx context.Context, resource *fhir.Endpoint, pare
215215
return assertOrganizationOrHealthcareServiceHasEndpointReference(resource.Id, parentOrganizationMap, allHealthcareServices)
216216
}
217217

218-
func validateLocationResource(ctx context.Context, resource *fhir.Location, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.BundleEntry) error {
218+
func validateLocationResource(ctx context.Context, resource *fhir.Location, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.HealthcareService) error {
219219
if resource.ManagingOrganization == nil {
220220
slog.WarnContext(ctx, "Location missing managingOrganization reference")
221221
return fmt.Errorf("location must have a 'managingOrganization' referencing an Organization")
@@ -226,7 +226,7 @@ func validateLocationResource(ctx context.Context, resource *fhir.Location, pare
226226

227227
// assertOrganizationOrHealthcareServiceHasEndpointReference validates that at least one of the organizations (parent or in allOrganizations)
228228
// or healthcare services has this endpoint ID in their endpoint references.
229-
func assertOrganizationOrHealthcareServiceHasEndpointReference(endpointID *string, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.BundleEntry) error {
229+
func assertOrganizationOrHealthcareServiceHasEndpointReference(endpointID *string, parentOrganizationMap map[*fhir.Organization][]*fhir.Organization, allHealthcareServices []fhir.HealthcareService) error {
230230
if endpointID == nil {
231231
return fmt.Errorf("endpoint ID is nil")
232232
}
@@ -249,20 +249,14 @@ func assertOrganizationOrHealthcareServiceHasEndpointReference(endpointID *strin
249249
}
250250

251251
// Check healthcare services
252-
for _, entry := range allHealthcareServices {
253-
if entry.Resource == nil {
254-
continue
255-
}
256-
var healthcareService fhir.HealthcareService
257-
if err := json.Unmarshal(entry.Resource, &healthcareService); err == nil {
258-
if healthcareServiceHasEndpointReference(&healthcareService, endpointID) {
259-
// If the healthcare service references this endpoint, validate that the healthcare service itself is valid
260-
if err := validateHealthcareServiceResource(context.Background(), &healthcareService, parentOrganizationMap, allHealthcareServices); err == nil {
261-
// Found a valid healthcare service that references this endpoint
262-
return nil
263-
}
264-
// Otherwise, continue checking other healthcare services or organizations
252+
for _, healthcareService := range allHealthcareServices {
253+
if healthcareServiceHasEndpointReference(&healthcareService, endpointID) {
254+
// If the healthcare service references this endpoint, validate that the healthcare service itself is valid
255+
if err := validateHealthcareServiceResource(context.Background(), &healthcareService, parentOrganizationMap, allHealthcareServices); err == nil {
256+
// Found a valid healthcare service that references this endpoint
257+
return nil
265258
}
259+
// Otherwise, continue checking other healthcare services or organizations
266260
}
267261
}
268262

component/mcsd/validator_test.go

Lines changed: 69 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,7 +1702,7 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
17021702
name string
17031703
endpoint *fhir.Endpoint
17041704
parentOrgMap map[*fhir.Organization][]*fhir.Organization
1705-
allHealthcareServices []fhir.BundleEntry
1705+
allHealthcareServices []fhir.HealthcareService
17061706
shouldSucceed bool
17071707
description string
17081708
}{
@@ -1719,15 +1719,13 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
17191719
},
17201720
}: {},
17211721
},
1722-
allHealthcareServices: []fhir.BundleEntry{
1722+
allHealthcareServices: []fhir.HealthcareService{
17231723
{
1724-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1725-
Id: to.Ptr("hcs-1"),
1726-
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1727-
Endpoint: []fhir.Reference{
1728-
{Reference: to.Ptr("Endpoint/endpoint-hcs-1")},
1729-
},
1730-
}),
1724+
Id: to.Ptr("hcs-1"),
1725+
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1726+
Endpoint: []fhir.Reference{
1727+
{Reference: to.Ptr("Endpoint/endpoint-hcs-1")},
1728+
},
17311729
},
17321730
},
17331731
shouldSucceed: true,
@@ -1746,15 +1744,13 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
17461744
},
17471745
}: {},
17481746
},
1749-
allHealthcareServices: []fhir.BundleEntry{
1747+
allHealthcareServices: []fhir.HealthcareService{
17501748
{
1751-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1752-
Id: to.Ptr("hcs-2"),
1753-
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1754-
Endpoint: []fhir.Reference{
1755-
{Reference: to.Ptr("http://example.org/fhir/Endpoint/endpoint-hcs-2")},
1756-
},
1757-
}),
1749+
Id: to.Ptr("hcs-2"),
1750+
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1751+
Endpoint: []fhir.Reference{
1752+
{Reference: to.Ptr("http://example.org/fhir/Endpoint/endpoint-hcs-2")},
1753+
},
17581754
},
17591755
},
17601756
shouldSucceed: true,
@@ -1773,17 +1769,15 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
17731769
},
17741770
}: {},
17751771
},
1776-
allHealthcareServices: []fhir.BundleEntry{
1772+
allHealthcareServices: []fhir.HealthcareService{
17771773
{
1778-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1779-
Id: to.Ptr("hcs-multi"),
1780-
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1781-
Endpoint: []fhir.Reference{
1782-
{Reference: to.Ptr("Endpoint/endpoint-hcs-1")},
1783-
{Reference: to.Ptr("Endpoint/endpoint-hcs-2")},
1784-
{Reference: to.Ptr("Endpoint/endpoint-hcs-3")},
1785-
},
1786-
}),
1774+
Id: to.Ptr("hcs-multi"),
1775+
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1776+
Endpoint: []fhir.Reference{
1777+
{Reference: to.Ptr("Endpoint/endpoint-hcs-1")},
1778+
{Reference: to.Ptr("Endpoint/endpoint-hcs-2")},
1779+
{Reference: to.Ptr("Endpoint/endpoint-hcs-3")},
1780+
},
17871781
},
17881782
},
17891783
shouldSucceed: true,
@@ -1802,24 +1796,20 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
18021796
},
18031797
}: {},
18041798
},
1805-
allHealthcareServices: []fhir.BundleEntry{
1799+
allHealthcareServices: []fhir.HealthcareService{
18061800
{
1807-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1808-
Id: to.Ptr("hcs-cardiology"),
1809-
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1810-
Endpoint: []fhir.Reference{
1811-
{Reference: to.Ptr("Endpoint/endpoint-cardio")},
1812-
},
1813-
}),
1801+
Id: to.Ptr("hcs-cardiology"),
1802+
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1803+
Endpoint: []fhir.Reference{
1804+
{Reference: to.Ptr("Endpoint/endpoint-cardio")},
1805+
},
18141806
},
18151807
{
1816-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1817-
Id: to.Ptr("hcs-emergency"),
1818-
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1819-
Endpoint: []fhir.Reference{
1820-
{Reference: to.Ptr("Endpoint/endpoint-hcs-4")},
1821-
},
1822-
}),
1808+
Id: to.Ptr("hcs-emergency"),
1809+
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1810+
Endpoint: []fhir.Reference{
1811+
{Reference: to.Ptr("Endpoint/endpoint-hcs-4")},
1812+
},
18231813
},
18241814
},
18251815
shouldSucceed: true,
@@ -1841,14 +1831,12 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
18411831
},
18421832
}: {},
18431833
},
1844-
allHealthcareServices: []fhir.BundleEntry{
1834+
allHealthcareServices: []fhir.HealthcareService{
18451835
{
1846-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1847-
Id: to.Ptr("hcs-1"),
1848-
Endpoint: []fhir.Reference{
1849-
{Reference: to.Ptr("Endpoint/endpoint-different")},
1850-
},
1851-
}),
1836+
Id: to.Ptr("hcs-1"),
1837+
Endpoint: []fhir.Reference{
1838+
{Reference: to.Ptr("Endpoint/endpoint-different")},
1839+
},
18521840
},
18531841
},
18541842
shouldSucceed: false,
@@ -1870,14 +1858,12 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
18701858
},
18711859
}: {},
18721860
},
1873-
allHealthcareServices: []fhir.BundleEntry{
1861+
allHealthcareServices: []fhir.HealthcareService{
18741862
{
1875-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1876-
Id: to.Ptr("hcs-1"),
1877-
Endpoint: []fhir.Reference{
1878-
{Reference: to.Ptr("Endpoint/endpoint-shared")},
1879-
},
1880-
}),
1863+
Id: to.Ptr("hcs-1"),
1864+
Endpoint: []fhir.Reference{
1865+
{Reference: to.Ptr("Endpoint/endpoint-shared")},
1866+
},
18811867
},
18821868
},
18831869
shouldSucceed: true,
@@ -1897,15 +1883,13 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
18971883
// No endpoint references in organization
18981884
}: {},
18991885
},
1900-
allHealthcareServices: []fhir.BundleEntry{
1886+
allHealthcareServices: []fhir.HealthcareService{
19011887
{
1902-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1903-
Id: to.Ptr("hcs-1"),
1904-
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1905-
Endpoint: []fhir.Reference{
1906-
{Reference: to.Ptr("Endpoint/endpoint-hcs-only")},
1907-
},
1908-
}),
1888+
Id: to.Ptr("hcs-1"),
1889+
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1890+
Endpoint: []fhir.Reference{
1891+
{Reference: to.Ptr("Endpoint/endpoint-hcs-only")},
1892+
},
19091893
},
19101894
},
19111895
shouldSucceed: true,
@@ -1924,7 +1908,7 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
19241908
},
19251909
}: {},
19261910
},
1927-
allHealthcareServices: []fhir.BundleEntry{},
1911+
allHealthcareServices: []fhir.HealthcareService{},
19281912
shouldSucceed: false,
19291913
description: "should fail when healthcare services list is empty and endpoint not in org",
19301914
},
@@ -1941,12 +1925,10 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
19411925
},
19421926
}: {},
19431927
},
1944-
allHealthcareServices: []fhir.BundleEntry{
1928+
allHealthcareServices: []fhir.HealthcareService{
19451929
{
1946-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1947-
Id: to.Ptr("hcs-1"),
1948-
Endpoint: nil, // No endpoints
1949-
}),
1930+
Id: to.Ptr("hcs-1"),
1931+
Endpoint: nil, // No endpoints
19501932
},
19511933
},
19521934
shouldSucceed: false,
@@ -1965,15 +1947,13 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
19651947
},
19661948
}: {},
19671949
},
1968-
allHealthcareServices: []fhir.BundleEntry{
1950+
allHealthcareServices: []fhir.HealthcareService{
19691951
{
1970-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1971-
Id: to.Ptr("hcs-invalid"),
1972-
// Missing ProvidedBy - healthcare service is invalid
1973-
Endpoint: []fhir.Reference{
1974-
{Reference: to.Ptr("Endpoint/endpoint-invalid")},
1975-
},
1976-
}),
1952+
Id: to.Ptr("hcs-invalid"),
1953+
// Missing ProvidedBy - healthcare service is invalid
1954+
Endpoint: []fhir.Reference{
1955+
{Reference: to.Ptr("Endpoint/endpoint-invalid")},
1956+
},
19771957
},
19781958
},
19791959
shouldSucceed: false,
@@ -1992,24 +1972,20 @@ func TestValidateEndpointResourceReferencedByHealthcareService(t *testing.T) {
19921972
},
19931973
}: {},
19941974
},
1995-
allHealthcareServices: []fhir.BundleEntry{
1975+
allHealthcareServices: []fhir.HealthcareService{
19961976
{
1997-
Resource: mustMarshalJSON(&fhir.HealthcareService{
1998-
Id: to.Ptr("hcs-invalid"),
1999-
// Missing ProvidedBy - invalid
2000-
Endpoint: []fhir.Reference{
2001-
{Reference: to.Ptr("Endpoint/endpoint-mixed")},
2002-
},
2003-
}),
1977+
Id: to.Ptr("hcs-invalid"),
1978+
// Missing ProvidedBy - invalid
1979+
Endpoint: []fhir.Reference{
1980+
{Reference: to.Ptr("Endpoint/endpoint-mixed")},
1981+
},
20041982
},
20051983
{
2006-
Resource: mustMarshalJSON(&fhir.HealthcareService{
2007-
Id: to.Ptr("hcs-valid"),
2008-
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
2009-
Endpoint: []fhir.Reference{
2010-
{Reference: to.Ptr("Endpoint/endpoint-mixed")},
2011-
},
2012-
}),
1984+
Id: to.Ptr("hcs-valid"),
1985+
ProvidedBy: &fhir.Reference{Reference: to.Ptr("Organization/hospital-main")},
1986+
Endpoint: []fhir.Reference{
1987+
{Reference: to.Ptr("Endpoint/endpoint-mixed")},
1988+
},
20131989
},
20141990
},
20151991
shouldSucceed: true,

0 commit comments

Comments
 (0)