* Update fhir version constants from 4.0.0 to 4.0.1 Yes, this means the test fails. The root cause of the failure is possibly the incorrect fhir version in org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Constants.java * fixes hapifhir/hapi-fhir#3466 Includes a POST version for every GET endpoint. Excludes non-primitive parameters from GET endpoints. For an idempotent (affectsState=false) endpoint with at least one required non-primitive parameter, only support POST Also include */_search endpoints for the Search operation.
This commit is contained in:
parent
23b593aa17
commit
ee06f900fe
|
@ -77,6 +77,9 @@ import org.hl7.fhir.r4.model.Reference;
|
||||||
import org.hl7.fhir.r4.model.Resource;
|
import org.hl7.fhir.r4.model.Resource;
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
import org.hl7.fhir.r4.model.Type;
|
import org.hl7.fhir.r4.model.Type;
|
||||||
|
import org.hl7.fhir.r4.model.OperationDefinition.OperationDefinitionParameterComponent;
|
||||||
|
import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
|
||||||
|
import org.hl7.fhir.r4.model.codesystems.DataTypes;
|
||||||
import org.thymeleaf.IEngineConfiguration;
|
import org.thymeleaf.IEngineConfiguration;
|
||||||
import org.thymeleaf.TemplateEngine;
|
import org.thymeleaf.TemplateEngine;
|
||||||
import org.thymeleaf.cache.AlwaysValidCacheEntryValidity;
|
import org.thymeleaf.cache.AlwaysValidCacheEntryValidity;
|
||||||
|
@ -98,6 +101,7 @@ import java.io.InputStream;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -569,21 +573,8 @@ public class OpenApiInterceptor {
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.SEARCHTYPE)) {
|
if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.SEARCHTYPE)) {
|
||||||
Operation operation = getPathItem(paths, "/" + resourceType, PathItem.HttpMethod.GET);
|
addSearchOperation(openApi, getPathItem(paths, "/" + resourceType, PathItem.HttpMethod.GET), ctx, resourceType, nextResource);
|
||||||
operation.addTagsItem(resourceType);
|
addSearchOperation(openApi, getPathItem(paths, "/" + resourceType + "/_search", PathItem.HttpMethod.GET), ctx, resourceType, nextResource);
|
||||||
operation.setDescription("This is a search type");
|
|
||||||
operation.setSummary("search-type: Search for " + resourceType + " instances");
|
|
||||||
addFhirResourceResponse(ctx, openApi, operation, null);
|
|
||||||
|
|
||||||
for (CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent nextSearchParam : nextResource.getSearchParam()) {
|
|
||||||
Parameter parametersItem = new Parameter();
|
|
||||||
operation.addParametersItem(parametersItem);
|
|
||||||
|
|
||||||
parametersItem.setName(nextSearchParam.getName());
|
|
||||||
parametersItem.setIn("query");
|
|
||||||
parametersItem.setDescription(nextSearchParam.getDocumentation());
|
|
||||||
parametersItem.setStyle(Parameter.StyleEnum.SIMPLE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resource-level Operations
|
// Resource-level Operations
|
||||||
|
@ -596,6 +587,24 @@ public class OpenApiInterceptor {
|
||||||
return openApi;
|
return openApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void addSearchOperation(final OpenAPI openApi, final Operation operation, final FhirContext ctx,
|
||||||
|
final String resourceType, final CapabilityStatement.CapabilityStatementRestResourceComponent nextResource) {
|
||||||
|
operation.addTagsItem(resourceType);
|
||||||
|
operation.setDescription("This is a search type");
|
||||||
|
operation.setSummary("search-type: Search for " + resourceType + " instances");
|
||||||
|
addFhirResourceResponse(ctx, openApi, operation, null);
|
||||||
|
|
||||||
|
for (final CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent nextSearchParam : nextResource.getSearchParam()) {
|
||||||
|
final Parameter parametersItem = new Parameter();
|
||||||
|
operation.addParametersItem(parametersItem);
|
||||||
|
|
||||||
|
parametersItem.setName(nextSearchParam.getName());
|
||||||
|
parametersItem.setIn("query");
|
||||||
|
parametersItem.setDescription(nextSearchParam.getDocumentation());
|
||||||
|
parametersItem.setStyle(Parameter.StyleEnum.SIMPLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Supplier<IBaseResource> patchExampleSupplier() {
|
private Supplier<IBaseResource> patchExampleSupplier() {
|
||||||
return () -> {
|
return () -> {
|
||||||
Parameters example = new Parameters();
|
Parameters example = new Parameters();
|
||||||
|
@ -650,8 +659,15 @@ public class OpenApiInterceptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
OperationDefinition operationDefinition = toCanonicalVersion(operationDefinitionNonCanonical);
|
OperationDefinition operationDefinition = toCanonicalVersion(operationDefinitionNonCanonical);
|
||||||
|
final boolean postOnly = operationDefinition.getAffectsState()
|
||||||
|
|| operationDefinition.getParameter().stream()
|
||||||
|
.filter(p -> p.getUse().equals(OperationParameterUse.IN))
|
||||||
|
.anyMatch(p -> {
|
||||||
|
final boolean required = p.getMin() > 0;
|
||||||
|
return required && !isPrimitive(p);
|
||||||
|
});
|
||||||
|
|
||||||
if (!operationDefinition.getAffectsState()) {
|
if (!postOnly) {
|
||||||
|
|
||||||
// GET form for non-state-affecting operations
|
// GET form for non-state-affecting operations
|
||||||
if (theResourceType != null) {
|
if (theResourceType != null) {
|
||||||
|
@ -671,30 +687,57 @@ public class OpenApiInterceptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// POST form for all operations
|
|
||||||
if (theResourceType != null) {
|
|
||||||
if (operationDefinition.getType()) {
|
|
||||||
Operation operation = getPathItem(thePaths, "/" + theResourceType + "/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
|
|
||||||
populateOperation(theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, false);
|
|
||||||
}
|
|
||||||
if (operationDefinition.getInstance()) {
|
|
||||||
Operation operation = getPathItem(thePaths, "/" + theResourceType + "/{id}/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
|
|
||||||
addResourceIdParameter(operation);
|
|
||||||
populateOperation(theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (operationDefinition.getSystem()) {
|
|
||||||
Operation operation = getPathItem(thePaths, "/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
|
|
||||||
populateOperation(theFhirContext, theOpenApi, null, operationDefinition, operation, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST form for all operations
|
||||||
|
if (theResourceType != null) {
|
||||||
|
if (operationDefinition.getType()) {
|
||||||
|
Operation operation = getPathItem(thePaths, "/" + theResourceType + "/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
|
||||||
|
populateOperation(theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, false);
|
||||||
|
}
|
||||||
|
if (operationDefinition.getInstance()) {
|
||||||
|
Operation operation = getPathItem(thePaths, "/" + theResourceType + "/{id}/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
|
||||||
|
addResourceIdParameter(operation);
|
||||||
|
populateOperation(theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (operationDefinition.getSystem()) {
|
||||||
|
Operation operation = getPathItem(thePaths, "/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
|
||||||
|
populateOperation(theFhirContext, theOpenApi, null, operationDefinition, operation, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String> primitiveTypes = Arrays.asList(
|
||||||
|
DataTypes.BOOLEAN.toCode(),
|
||||||
|
DataTypes.INTEGER.toCode(),
|
||||||
|
DataTypes.STRING.toCode(),
|
||||||
|
DataTypes.DECIMAL.toCode(),
|
||||||
|
DataTypes.URI.toCode(),
|
||||||
|
DataTypes.URL.toCode(),
|
||||||
|
DataTypes.CANONICAL.toCode(),
|
||||||
|
DataTypes.REFERENCE.toCode(),
|
||||||
|
DataTypes.BASE64BINARY.toCode(),
|
||||||
|
DataTypes.INSTANT.toCode(),
|
||||||
|
DataTypes.DATE.toCode(),
|
||||||
|
DataTypes.DATETIME.toCode(),
|
||||||
|
DataTypes.TIME.toCode(),
|
||||||
|
DataTypes.CODE.toCode(),
|
||||||
|
DataTypes.CODING.toCode(),
|
||||||
|
DataTypes.OID.toCode(),
|
||||||
|
DataTypes.ID.toCode(),
|
||||||
|
DataTypes.MARKDOWN.toCode(),
|
||||||
|
DataTypes.UNSIGNEDINT.toCode(),
|
||||||
|
DataTypes.POSITIVEINT.toCode(),
|
||||||
|
DataTypes.UUID.toCode()
|
||||||
|
);
|
||||||
|
|
||||||
|
private static boolean isPrimitive(OperationDefinitionParameterComponent parameter) {
|
||||||
|
return primitiveTypes.contains(parameter.getType());
|
||||||
|
}
|
||||||
|
|
||||||
private void populateOperation(FhirContext theFhirContext, OpenAPI theOpenApi, String theResourceType, OperationDefinition theOperationDefinition, Operation theOperation, boolean theGet) {
|
private void populateOperation(FhirContext theFhirContext, OpenAPI theOpenApi, String theResourceType, OperationDefinition theOperationDefinition, Operation theOperation, boolean theGet) {
|
||||||
if (theResourceType == null) {
|
if (theResourceType == null) {
|
||||||
theOperation.addTagsItem(PAGE_SYSTEM);
|
theOperation.addTagsItem(PAGE_SYSTEM);
|
||||||
|
@ -704,10 +747,15 @@ public class OpenApiInterceptor {
|
||||||
theOperation.setSummary(theOperationDefinition.getTitle());
|
theOperation.setSummary(theOperationDefinition.getTitle());
|
||||||
theOperation.setDescription(theOperationDefinition.getDescription());
|
theOperation.setDescription(theOperationDefinition.getDescription());
|
||||||
addFhirResourceResponse(theFhirContext, theOpenApi, theOperation, null);
|
addFhirResourceResponse(theFhirContext, theOpenApi, theOperation, null);
|
||||||
|
|
||||||
if (theGet) {
|
if (theGet) {
|
||||||
|
|
||||||
for (OperationDefinition.OperationDefinitionParameterComponent nextParameter : theOperationDefinition.getParameter()) {
|
for (OperationDefinition.OperationDefinitionParameterComponent nextParameter : theOperationDefinition.getParameter()) {
|
||||||
|
if ("0".equals(nextParameter.getMax()) || !nextParameter.getUse().equals(OperationParameterUse.IN)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isPrimitive(nextParameter) && nextParameter.getMin() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Parameter parametersItem = new Parameter();
|
Parameter parametersItem = new Parameter();
|
||||||
theOperation.addParametersItem(parametersItem);
|
theOperation.addParametersItem(parametersItem);
|
||||||
|
|
||||||
|
@ -733,6 +781,9 @@ public class OpenApiInterceptor {
|
||||||
|
|
||||||
Parameters exampleRequestBody = new Parameters();
|
Parameters exampleRequestBody = new Parameters();
|
||||||
for (OperationDefinition.OperationDefinitionParameterComponent nextSearchParam : theOperationDefinition.getParameter()) {
|
for (OperationDefinition.OperationDefinitionParameterComponent nextSearchParam : theOperationDefinition.getParameter()) {
|
||||||
|
if ("0".equals(nextSearchParam.getMax()) || !nextSearchParam.getUse().equals(OperationParameterUse.IN)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Parameters.ParametersParameterComponent param = exampleRequestBody.addParameter();
|
Parameters.ParametersParameterComponent param = exampleRequestBody.addParameter();
|
||||||
param.setName(nextSearchParam.getName());
|
param.setName(nextSearchParam.getName());
|
||||||
String paramType = nextSearchParam.getType();
|
String paramType = nextSearchParam.getType();
|
||||||
|
|
|
@ -122,7 +122,11 @@ public class OpenApiInterceptorTest {
|
||||||
assertEquals("Foo Op Short", fooOpPath.getPost().getSummary());
|
assertEquals("Foo Op Short", fooOpPath.getPost().getSummary());
|
||||||
|
|
||||||
PathItem lastNPath = parsed.getPaths().get("/Observation/$lastn");
|
PathItem lastNPath = parsed.getPaths().get("/Observation/$lastn");
|
||||||
assertNull(lastNPath.getPost());
|
assertNotNull(lastNPath.getPost());
|
||||||
|
assertEquals("LastN Description", lastNPath.getPost().getDescription());
|
||||||
|
assertEquals("LastN Short", lastNPath.getPost().getSummary());
|
||||||
|
assertNull(lastNPath.getPost().getParameters());
|
||||||
|
assertNotNull(lastNPath.getPost().getRequestBody());
|
||||||
assertNotNull(lastNPath.getGet());
|
assertNotNull(lastNPath.getGet());
|
||||||
assertEquals("LastN Description", lastNPath.getGet().getDescription());
|
assertEquals("LastN Description", lastNPath.getGet().getDescription());
|
||||||
assertEquals("LastN Short", lastNPath.getGet().getSummary());
|
assertEquals("LastN Short", lastNPath.getGet().getSummary());
|
||||||
|
|
Loading…
Reference in New Issue