Skip to content

Commit

Permalink
Parse authoritativeSource URLs correctly (#489)
Browse files Browse the repository at this point in the history
* Parse authoritativeSource URLs correctly

* spotless

* can't use List.of

* no isBlank

* update stubs

---------

Co-authored-by: taha.attari@smilecdr.com <taha.attari@smilecdr.com>
  • Loading branch information
TahaAttari and taha.attari@smilecdr.com authored Jul 4, 2024
1 parent 657819c commit 92fdbfb
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseEnumFactory;
import org.opencds.cqf.fhir.utility.Canonicals;

public class TerminologyServerClient {
Expand Down Expand Up @@ -108,13 +111,54 @@ public org.hl7.fhir.r5.model.ValueSet expand(

// Strips resource and id from the authoritative source URL, these are not needed as the client constructs the URL.
// Converts http URLs to https
private String getAuthoritativeSourceBase(String authoritativeSource) {
authoritativeSource = authoritativeSource.substring(
0,
authoritativeSource.indexOf(Objects.requireNonNull(Canonicals.getResourceType(authoritativeSource))));
public String getAuthoritativeSourceBase(String authoritativeSource) {
Objects.requireNonNull(authoritativeSource, "authoritativeSource must not be null");
if (authoritativeSource.startsWith("http://")) {
authoritativeSource = authoritativeSource.replaceFirst("http://", "https://");
}
// remove trailing slashes
if (authoritativeSource.endsWith("/")) {
authoritativeSource = authoritativeSource.substring(0, authoritativeSource.length() - 1);
}
// check if URL is in the format [base URL]/[resource type]/[id]
var maybeFhirType = Canonicals.getResourceType(authoritativeSource);
if (maybeFhirType != null && StringUtils.isNotBlank(maybeFhirType)) {
IBaseEnumFactory<?> factory = getEnumFactory();
try {
factory.fromCode(maybeFhirType);
} catch (IllegalArgumentException e) {
// check if URL is in the format [base URL]/[resource type]
var lastSlashIndex = authoritativeSource.lastIndexOf("/");
if (lastSlashIndex > 0) {
maybeFhirType = authoritativeSource.substring(lastSlashIndex + 1, authoritativeSource.length());
try {
factory.fromCode(maybeFhirType);
} catch (IllegalArgumentException e2) {
return authoritativeSource;
}
} else {
return authoritativeSource;
}
}
authoritativeSource = authoritativeSource.substring(0, authoritativeSource.indexOf(maybeFhirType) - 1);
}
return authoritativeSource;
}

private IBaseEnumFactory<?> getEnumFactory() {
switch (this.ctx.getVersion().getVersion()) {
case DSTU3:
return new org.hl7.fhir.dstu3.model.Enumerations.ResourceTypeEnumFactory();

case R4:
return new org.hl7.fhir.r4.model.Enumerations.ResourceTypeEnumFactory();

case R5:
return new org.hl7.fhir.r5.model.Enumerations.ResourceTypeEnumEnumFactory();

default:
throw new UnprocessableEntityException("unsupported FHIR version: "
+ this.ctx.getVersion().getVersion().toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import static org.mockito.Mockito.when;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.client.impl.GenericClient;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
Expand All @@ -30,8 +32,10 @@ void testR4UrlAndVersion() {
vs.setUrl(url);
var capt = ArgumentCaptor.forClass(org.hl7.fhir.r4.model.Parameters.class);
var clientMock = mock(GenericClient.class, new ReturnsDeepStubs());
var contextMock = mock(FhirContext.class);
var contextMock = mock(FhirContext.class, new ReturnsDeepStubs());
when(contextMock.newRestfulGenericClient(any())).thenReturn(clientMock);
when(contextMock.getVersion().getVersion()).thenReturn(FhirVersionEnum.R4);

when(clientMock
.operation()
.onType(anyString())
Expand Down Expand Up @@ -69,8 +73,9 @@ void testR5UrlAndVersion() {
vs.setUrl(url);
var capt = ArgumentCaptor.forClass(org.hl7.fhir.r5.model.Parameters.class);
var clientMock = mock(GenericClient.class, new ReturnsDeepStubs());
var contextMock = mock(FhirContext.class);
var contextMock = mock(FhirContext.class, new ReturnsDeepStubs());
when(contextMock.newRestfulGenericClient(any())).thenReturn(clientMock);
when(contextMock.getVersion().getVersion()).thenReturn(FhirVersionEnum.R5);
when(clientMock
.operation()
.onType(anyString())
Expand Down Expand Up @@ -108,8 +113,9 @@ void testDstu3UrlAndVersion() {
vs.setUrl(url);
var capt = ArgumentCaptor.forClass(org.hl7.fhir.dstu3.model.Parameters.class);
var clientMock = mock(GenericClient.class, new ReturnsDeepStubs());
var contextMock = mock(FhirContext.class);
var contextMock = mock(FhirContext.class, new ReturnsDeepStubs());
when(contextMock.newRestfulGenericClient(any())).thenReturn(clientMock);
when(contextMock.getVersion().getVersion()).thenReturn(FhirVersionEnum.DSTU3);
when(clientMock
.operation()
.onType(anyString())
Expand Down Expand Up @@ -146,4 +152,26 @@ void testDstu3UrlAndVersion() {
.getValue())
.getValue());
}

@Test
void authoritativeSourceUrlParsing() {
var supportedVersions = Arrays.asList(FhirVersionEnum.DSTU3, FhirVersionEnum.R4, FhirVersionEnum.R5);
for (final var version : supportedVersions) {
var ts = new TerminologyServerClient(new FhirContext(version));
var theCorrectBaseServerUrl = "https://cts.nlm.nih.gov/fhir";
// remove the FHIR type and the ID if included
assertEquals(
theCorrectBaseServerUrl, ts.getAuthoritativeSourceBase(theCorrectBaseServerUrl + "/ValueSet/1"));
// remove a FHIR type if one was included
assertEquals(theCorrectBaseServerUrl, ts.getAuthoritativeSourceBase(theCorrectBaseServerUrl + "/ValueSet"));
// don't break on the actual base url
assertEquals(theCorrectBaseServerUrl, ts.getAuthoritativeSourceBase(theCorrectBaseServerUrl));
// ensure it's forcing https
assertEquals(
theCorrectBaseServerUrl,
ts.getAuthoritativeSourceBase(theCorrectBaseServerUrl.replace("https", "http")));
// remove trailing slashes
assertEquals(theCorrectBaseServerUrl, ts.getAuthoritativeSourceBase(theCorrectBaseServerUrl + "/"));
}
}
}

0 comments on commit 92fdbfb

Please sign in to comment.