Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #896 - Add checking for MIME-type parameter fhirVersion #2434

Merged
merged 3 commits into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/src/pages/Conformance.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
layout: post
title: Conformance
description: Notes on the Conformance of the IBM FHIR Server
date: 2021-05-19
date: 2021-05-26
permalink: /conformance/
---

Expand All @@ -12,7 +12,7 @@ The IBM FHIR Server aims to be a conformant implementation of the HL7 FHIR speci
## Capability statement
The HL7 FHIR specification defines [an interaction](https://www.hl7.org/fhir/R4/http.html#capabilities) for retrieving a machine-readable description of the server's capabilities via the `[base]/metadata` endpoint. The IBM FHIR Server implements this interaction and generates a `CapabilityStatement` resource based on the current server configuration. While the `CapabilityStatement` resource is ideal for certain uses, this markdown document provides a human-readable summary of important details, with a special focus on limitations of the current implementation and deviations from the specification.

The IBM FHIR Server supports only version 4.0.1 of the specification and ignores the optional MIME-type parameter `fhirVersion`.
The IBM FHIR Server supports only version 4.0.1 of the specification.

## FHIR HTTP API
The HL7 FHIR specification is more than just a data format. It defines an [HTTP API](https://www.hl7.org/fhir/R4/http.html) for creating, reading, updating, deleting, and searching over FHIR resources. The IBM FHIR Server implements the full API for every resource defined in the specification, with the following exceptions:
Expand Down
16 changes: 14 additions & 2 deletions fhir-core/src/main/java/com/ibm/fhir/core/FHIRMediaType.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
/*
* (C) Copyright IBM Corp. 2019
* (C) Copyright IBM Corp. 2019, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.core;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.core.MediaType;

/**
* This class contains definitions of some non-standard media types.
*/
public class FHIRMediaType extends MediaType {

public final static String SUBTYPE_FHIR_JSON = "fhir+json";
public final static String APPLICATION_FHIR_JSON = "application/" + SUBTYPE_FHIR_JSON;
public final static MediaType APPLICATION_FHIR_JSON_TYPE = new MediaType("application", SUBTYPE_FHIR_JSON);
Expand All @@ -29,6 +35,12 @@ public class FHIRMediaType extends MediaType {
public final static MediaType APPLICATION_FHIR_NDJSON_TYPE = new MediaType("application", SUBTYPE_FHIR_NDJSON);

public final static String SUBTYPE_FHIR_PARQUET = "fhir+parquet";
public static final String APPLICATION_PARQUET = "application/" + SUBTYPE_FHIR_PARQUET;
public static final String APPLICATION_PARQUET = "application/" + SUBTYPE_FHIR_PARQUET;
public final static MediaType APPLICATION_FHIR_PARQUET_TYPE = new MediaType("application", SUBTYPE_FHIR_PARQUET);

// Supported values for the MIME-type parameter fhirVersion.
// https://www.hl7.org/fhir/http.html#version-parameter
public static final String FHIR_VERSION_PARAMETER = "fhirVersion";
public static final Set<String> SUPPORTED_FHIR_VERSIONS =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("4.0","4.0.1")));
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import jakarta.json.JsonObject;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.testng.annotations.Test;
Expand Down Expand Up @@ -49,6 +50,8 @@
import com.ibm.fhir.path.evaluator.FHIRPathEvaluator.EvaluationContext;
import com.ibm.fhir.path.exception.FHIRPathException;

import jakarta.json.JsonObject;

/**
* Basic sniff test of the FHIR Server.
*/
Expand Down Expand Up @@ -125,6 +128,38 @@ public void testMetadataAPI_XML() {
assertNotNull(conf.getName());
}

/**
* Verify the 'metadata' API with valid fhirVersion in Accept header.
*/
@Test(groups = { "server-basic" })
public void testMetadataAPI_validFhirVersion() {
WebTarget target = getWebTarget();
MediaType mediaType = new MediaType("application", "fhir+json",
Collections.singletonMap(FHIRMediaType.FHIR_VERSION_PARAMETER, "4.0"));
Response response = target.path("metadata").request(mediaType).get();
assertResponse(response, Response.Status.OK.getStatusCode());
assertEquals(mediaType, response.getMediaType());

CapabilityStatement conf = response.readEntity(CapabilityStatement.class);
assertNotNull(conf);
assertNotNull(conf.getFormat());
assertEquals(6, conf.getFormat().size());
assertNotNull(conf.getVersion());
assertNotNull(conf.getName());
}

/**
* Verify the 'metadata' API with invalid fhirVersion in Accept header.
*/
@Test(groups = { "server-basic" })
public void testMetadataAPI_invalidFhirVersion() {
WebTarget target = getWebTarget();
MediaType mediaType = new MediaType("application", "fhir+json",
Collections.singletonMap(FHIRMediaType.FHIR_VERSION_PARAMETER, "3.0"));
Response response = target.path("metadata").request(mediaType).get();
assertResponse(response, Response.Status.NOT_ACCEPTABLE.getStatusCode());
}

/**
* Create a Patient, then make sure we can retrieve it.
*/
Expand Down Expand Up @@ -175,6 +210,50 @@ public void testCreatePatient_minimal() throws Exception {
TestUtil.assertResourceEquals(patient, responsePatient);
}

/**
* Create a minimal Patient with valid fhirVersion in Content-Type header, then make sure we can retrieve it.
*/
@Test(groups = { "server-basic" })
public void testCreatePatient_minimal_validFhirVersion() throws Exception {
WebTarget target = getWebTarget();

// Build a new Patient and then call the 'create' API.
Patient patient = TestUtil.readLocalResource("Patient_DavidOrtiz.json");

MediaType mediaType = new MediaType("application", "fhir+json",
Collections.singletonMap(FHIRMediaType.FHIR_VERSION_PARAMETER, "4.0"));
Entity<Patient> entity = Entity.entity(patient, mediaType);
Response response = target.path("Patient").request().post(entity, Response.class);
assertResponse(response, Response.Status.CREATED.getStatusCode());

// Get the patient's logical id value.
String patientId = getLocationLogicalId(response);

// Next, call the 'read' API to retrieve the new patient and verify it.
response = target.path("Patient/" + patientId).request(mediaType).get();
assertResponse(response, Response.Status.OK.getStatusCode());
Patient responsePatient = response.readEntity(Patient.class);

TestUtil.assertResourceEquals(patient, responsePatient);
}

/**
* Attempt to create a minimal Patient with invalid fhirVersion in Content-Type header.
*/
@Test( groups = { "server-basic" })
public void testCreatePatient_minimal_invalidFhirVersion() throws Exception {
WebTarget target = getWebTarget();

// Build a new Patient and then call the 'create' API.
Patient patient = TestUtil.readLocalResource("Patient_DavidOrtiz.json");

MediaType mediaType = new MediaType("application", "fhir+json",
Collections.singletonMap(FHIRMediaType.FHIR_VERSION_PARAMETER, "3.0"));
Entity<Patient> entity = Entity.entity(patient, mediaType);
Response response = target.path("Patient").request().post(entity, Response.class);
assertResponse(response, Response.Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode());
}

/**
* Create a minimal Patient, then make sure we can retrieve it with varying format
*/
Expand All @@ -193,13 +272,60 @@ public void testCreatePatientMinimalWithFormat() throws Exception {
String patientId = getLocationLogicalId(response);

// Next, call the 'read' API to retrieve the new patient and verify it.
response = target.path("Patient/" + patientId).request(FHIRMediaType.APPLICATION_FHIR_JSON).header("_format", "application/fhir+json").get();
response = target.path("Patient/" + patientId).queryParam("_format", "application/fhir+json").request(FHIRMediaType.APPLICATION_FHIR_JSON).get();
assertResponse(response, Response.Status.OK.getStatusCode());
Patient responsePatient = response.readEntity(Patient.class);

TestUtil.assertResourceEquals(patient, responsePatient);
}

/**
* Create a minimal Patient, then make sure we can retrieve it with varying format with valid FHIR version
*/
@Test(groups = { "server-basic" })
public void testCreatePatientMinimalWithFormat_validFhirVersion() throws Exception {
WebTarget target = getWebTarget();

// Build a new Patient and then call the 'create' API.
Patient patient = TestUtil.readLocalResource("Patient_DavidOrtiz.json");

Entity<Patient> entity = Entity.entity(patient, FHIRMediaType.APPLICATION_FHIR_JSON);
Response response = target.path("Patient").request().post(entity, Response.class);
assertResponse(response, Response.Status.CREATED.getStatusCode());

// Get the patient's logical id value.
String patientId = getLocationLogicalId(response);

// Next, call the 'read' API to retrieve the new patient and verify it.
response = target.path("Patient/" + patientId).queryParam("_format", "application/fhir+json;fhirVersion=4.0").request(FHIRMediaType.APPLICATION_FHIR_JSON).get();
assertResponse(response, Response.Status.OK.getStatusCode());
Patient responsePatient = response.readEntity(Patient.class);

TestUtil.assertResourceEquals(patient, responsePatient);
}

/**
* Create a minimal Patient, then attempt to retrieve it with varying format with invalid FHIR version
*/
@Test(groups = { "server-basic" })
public void testCreatePatientMinimalWithFormat_invalidFhirVersion() throws Exception {
WebTarget target = getWebTarget();

// Build a new Patient and then call the 'create' API.
Patient patient = TestUtil.readLocalResource("Patient_DavidOrtiz.json");

Entity<Patient> entity = Entity.entity(patient, FHIRMediaType.APPLICATION_FHIR_JSON);
Response response = target.path("Patient").request().post(entity, Response.class);
assertResponse(response, Response.Status.CREATED.getStatusCode());

// Get the patient's logical id value.
String patientId = getLocationLogicalId(response);

// Next, call the 'read' API to attempt to retrieve the new patient
response = target.path("Patient/" + patientId).queryParam("_format", "application/fhir+json;fhirVersion=3.0").request(FHIRMediaType.APPLICATION_FHIR_JSON).get();
assertResponse(response, Response.Status.NOT_ACCEPTABLE.getStatusCode());
}

/**
* Create a minimal Patient, then make sure we can retrieve it.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright IBM Corp. 2017,2019
* (C) Copyright IBM Corp. 2017, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -45,18 +45,18 @@ public void testPrettyFormatting() throws Exception {

// Next, call the 'read' API to retrieve the new patient and verify it.
response =
target.queryParam("_pretty", "true").path("Patient/" + patientId)
.request(FHIRMediaType.APPLICATION_FHIR_JSON).header("_format", "application/fhir+json").get();
target.queryParam("_pretty", "true").queryParam("_format", "application/fhir+json").path("Patient/" + patientId)
.request(FHIRMediaType.APPLICATION_FHIR_JSON).get();
assertResponse(response, Response.Status.OK.getStatusCode());

String prettyOutput = response.readEntity(String.class);

response =
target.queryParam("_pretty", "false").path("Patient/" + patientId)
.request(FHIRMediaType.APPLICATION_FHIR_JSON).header("_format", "application/fhir+json").get();
target.queryParam("_pretty", "false").queryParam("_format", "application/fhir+json").path("Patient/" + patientId)
.request(FHIRMediaType.APPLICATION_FHIR_JSON).get();

String notPrettyOutput = response.readEntity(String.class);

assertNotEquals(prettyOutput, notPrettyOutput);
assertFalse(notPrettyOutput.contains("\n"));
assertTrue(prettyOutput.contains("\n"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* (C) Copyright IBM Corp. 2021
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.server.exception;

import javax.servlet.http.HttpServletResponse;

import com.ibm.fhir.exception.FHIROperationException;

public class FHIRRestServletRequestException extends FHIROperationException {
private static final long serialVersionUID = 1L;
private int httpStatusCode = HttpServletResponse.SC_BAD_REQUEST;

public FHIRRestServletRequestException(String message) {
super(message);
}

public FHIRRestServletRequestException(String message, Throwable cause) {
super(message, cause);
}

public FHIRRestServletRequestException(String message, int httpStatusCode) {
this(message, httpStatusCode, null);
}

public FHIRRestServletRequestException(String message, int httpStatusCode, Throwable t) {
super(message, t);
this.httpStatusCode = httpStatusCode;
}

public int getHttpStatusCode() {
return httpStatusCode;
}
}
Loading