Skip to content

Commit

Permalink
Merge pull request #42480 from frne/smallrye-health/fix-openapi-schema
Browse files Browse the repository at this point in the history
Fix SmallRye Health OpenAPI definitions
  • Loading branch information
gsmet authored Aug 14, 2024
2 parents b624a88 + 6394b66 commit dc5f022
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 178 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package io.quarkus.smallrye.health.deployment;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand All @@ -12,9 +10,7 @@
import org.eclipse.microprofile.openapi.models.PathItem;
import org.eclipse.microprofile.openapi.models.Paths;
import org.eclipse.microprofile.openapi.models.media.Content;
import org.eclipse.microprofile.openapi.models.media.MediaType;
import org.eclipse.microprofile.openapi.models.media.Schema;
import org.eclipse.microprofile.openapi.models.responses.APIResponse;
import org.eclipse.microprofile.openapi.models.responses.APIResponses;

import io.smallrye.openapi.api.models.ComponentsImpl;
Expand All @@ -31,9 +27,42 @@
* Create OpenAPI entries (if configured)
*/
public class HealthOpenAPIFilter implements OASFilter {

private static final List<String> MICROPROFILE_HEALTH_TAG = Collections.singletonList("MicroProfile Health");
private static final String SCHEMA_HEALTH_RESPONSE = "HealthCheckResponse";
private static final String SCHEMA_HEALTH_STATUS = "HealthCheckStatus";
private static final String HEALTH_RESPONSE_SCHEMA_NAME = "HealthResponse";
private static final String HEALTH_CHECK_SCHEMA_NAME = "HealthCheck";

private static final Schema healthResponseSchemaDefinition = new SchemaImpl(HEALTH_RESPONSE_SCHEMA_NAME)
.type(Schema.SchemaType.OBJECT)
.properties(Map.ofEntries(

Map.entry("status",
new SchemaImpl()
.type(Schema.SchemaType.STRING)
.enumeration(List.of("UP", "DOWN"))),

Map.entry("checks",
new SchemaImpl()
.type(Schema.SchemaType.ARRAY)
.items(new SchemaImpl().ref("#/components/schemas/" + HEALTH_CHECK_SCHEMA_NAME)))));

private static final Schema healthCheckSchemaDefinition = new SchemaImpl(HEALTH_CHECK_SCHEMA_NAME)
.type(Schema.SchemaType.OBJECT)
.properties(Map.ofEntries(

Map.entry("name",
new SchemaImpl()
.type(Schema.SchemaType.STRING)),

Map.entry("status",
new SchemaImpl()
.type(Schema.SchemaType.STRING)
.enumeration(List.of("UP", "DOWN"))),

Map.entry("data",
new SchemaImpl()
.type(Schema.SchemaType.OBJECT)
.nullable(Boolean.TRUE))));

private final String rootPath;
private final String livenessPath;
Expand All @@ -52,192 +81,102 @@ public void filterOpenAPI(OpenAPI openAPI) {
if (openAPI.getComponents() == null) {
openAPI.setComponents(new ComponentsImpl());
}
openAPI.getComponents().addSchema(SCHEMA_HEALTH_RESPONSE, createHealthCheckResponse());
openAPI.getComponents().addSchema(SCHEMA_HEALTH_STATUS, createHealthCheckStatus());
openAPI.getComponents().addSchema(HEALTH_RESPONSE_SCHEMA_NAME, healthResponseSchemaDefinition);
openAPI.getComponents().addSchema(HEALTH_CHECK_SCHEMA_NAME, healthCheckSchemaDefinition);

if (openAPI.getPaths() == null) {
openAPI.setPaths(new PathsImpl());
}
Paths paths = openAPI.getPaths();

final Paths paths = openAPI.getPaths();

// Health
paths.addPathItem(rootPath, createHealthPathItem());
paths.addPathItem(
rootPath,
createHealthEndpoint(
"MicroProfile Health Endpoint",
"MicroProfile Health provides a way for your application to distribute " +
"information about its healthiness state to state whether or not it is able to " +
"function properly",
"Check the health of the application",
"microprofile_health_root",
"An aggregated view of the Liveness, Readiness and Startup of this application"));

// Liveness
paths.addPathItem(livenessPath, createLivenessPathItem());
paths.addPathItem(
livenessPath,
createHealthEndpoint(
"MicroProfile Health - Liveness Endpoint",
"Liveness checks are utilized to tell whether the application should be " +
"restarted",
"Check the liveness of the application",
"microprofile_health_liveness",
"The Liveness check of this application"));

// Readiness
paths.addPathItem(readinessPath, createReadinessPathItem());
paths.addPathItem(
readinessPath,
createHealthEndpoint(
"MicroProfile Health - Readiness Endpoint",
"Readiness checks are used to tell whether the application is able to " +
"process requests",
"Check the readiness of the application",
"microprofile_health_readiness",
"The Readiness check of this application"));

// Startup
paths.addPathItem(startupPath, createStartupPathItem());
}

private PathItem createHealthPathItem() {
PathItem pathItem = new PathItemImpl();
pathItem.setDescription("MicroProfile Health Endpoint");
pathItem.setSummary(
"MicroProfile Health provides a way for your application to distribute information about its healthiness state to state whether or not it is able to function properly");
pathItem.setGET(createHealthOperation());
return pathItem;
}

private PathItem createLivenessPathItem() {
PathItem pathItem = new PathItemImpl();
pathItem.setDescription("MicroProfile Health - Liveness Endpoint");
pathItem.setSummary(
"Liveness checks are utilized to tell whether the application should be restarted");
pathItem.setGET(createLivenessOperation());
return pathItem;
}

private PathItem createReadinessPathItem() {
PathItem pathItem = new PathItemImpl();
pathItem.setDescription("MicroProfile Health - Readiness Endpoint");
pathItem.setSummary(
"Readiness checks are used to tell whether the application is able to process requests");
pathItem.setGET(createReadinessOperation());
return pathItem;
}

private PathItem createStartupPathItem() {
PathItem pathItem = new PathItemImpl();
pathItem.setDescription("MicroProfile Health - Startup Endpoint");
pathItem.setSummary(
"Startup checks are an used to tell when the application has started");
pathItem.setGET(createStartupOperation());
return pathItem;
}

private Operation createHealthOperation() {
Operation operation = new OperationImpl();
operation.setDescription("Check the health of the application");
operation.setOperationId("microprofile_health_root");
operation.setTags(MICROPROFILE_HEALTH_TAG);
operation.setSummary("An aggregated view of the Liveness, Readiness and Startup of this application");
operation.setResponses(createAPIResponses());
return operation;
}

private Operation createLivenessOperation() {
Operation operation = new OperationImpl();
operation.setDescription("Check the liveness of the application");
operation.setOperationId("microprofile_health_liveness");
operation.setTags(MICROPROFILE_HEALTH_TAG);
operation.setSummary("The Liveness check of this application");
operation.setResponses(createAPIResponses());
return operation;
}

private Operation createReadinessOperation() {
Operation operation = new OperationImpl();
operation.setDescription("Check the readiness of the application");
operation.setOperationId("microprofile_health_readiness");
operation.setTags(MICROPROFILE_HEALTH_TAG);
operation.setSummary("The Readiness check of this application");
operation.setResponses(createAPIResponses());
return operation;
}

private Operation createStartupOperation() {
Operation operation = new OperationImpl();
operation.setDescription("Check the startup of the application");
operation.setOperationId("microprofile_health_startup");
operation.setTags(MICROPROFILE_HEALTH_TAG);
operation.setSummary("The Startup check of this application");
operation.setResponses(createAPIResponses());
return operation;
}

private APIResponses createAPIResponses() {
APIResponses responses = new APIResponsesImpl();
responses.addAPIResponse("200", createAPIResponse("OK"));
responses.addAPIResponse("503", createAPIResponse("Service Unavailable"));
responses.addAPIResponse("500", createAPIResponse("Internal Server Error"));
return responses;
}

private APIResponse createAPIResponse(String description) {
APIResponse response = new APIResponseImpl();
response.setDescription(description);
response.setContent(createContent());
return response;
}

private Content createContent() {
Content content = new ContentImpl();
content.addMediaType("application/json", createMediaType());
return content;
}

private MediaType createMediaType() {
MediaType mediaType = new MediaTypeImpl();
mediaType.setSchema(new SchemaImpl().ref("#/components/schemas/" + SCHEMA_HEALTH_RESPONSE));
return mediaType;
paths.addPathItem(
startupPath,
createHealthEndpoint(
"MicroProfile Health - Startup Endpoint",
"Startup checks are an used to tell when the application has started",
"Check the startup of the application",
"microprofile_health_startup",
"The Startup check of this application"));
}

/**
* HealthCheckResponse:
* type: object
* properties:
* data:
* type: object
* nullable: true
* name:
* type: string
* status:
* $ref: '#/components/schemas/HealthCheckStatus'
* Creates a {@link PathItem} containing the endpoint definition and GET {@link Operation} for health endpoints.
*
* @return Schema representing HealthCheckResponse
* @param endpointDescription The description for the endpoint definition
* @param endpointSummary The summary for the endpoint definition
* @param operationDescription The description for the operation definition
* @param operationId The operation-id for the operation definition
* @param operationSummary The summary for the operation definition
*/
private Schema createHealthCheckResponse() {
Schema schema = new SchemaImpl(SCHEMA_HEALTH_RESPONSE);
schema.setType(Schema.SchemaType.OBJECT);
schema.setProperties(createProperties());
return schema;
}

private Map<String, Schema> createProperties() {
Map<String, Schema> map = new HashMap<>();
map.put("data", createData());
map.put("name", createName());
map.put("status", new SchemaImpl().ref("#/components/schemas/" + SCHEMA_HEALTH_STATUS));
return map;
}

private Schema createData() {
Schema schema = new SchemaImpl("data");
schema.setType(Schema.SchemaType.OBJECT);
schema.setNullable(Boolean.TRUE);
return schema;
}

private Schema createName() {
Schema schema = new SchemaImpl("name");
schema.setType(Schema.SchemaType.STRING);
return schema;
}

/**
* HealthCheckStatus:
* enum:
* - DOWN
* - UP
* type: string
*
* @return Schema representing Status
*/
private Schema createHealthCheckStatus() {
Schema schema = new SchemaImpl(SCHEMA_HEALTH_STATUS);
schema.setEnumeration(createStateEnumValues());
schema.setType(Schema.SchemaType.STRING);
return schema;
}

private List<Object> createStateEnumValues() {
List<Object> values = new ArrayList<>();
values.add("DOWN");
values.add("UP");
return values;
private PathItem createHealthEndpoint(
String endpointDescription,
String endpointSummary,
String operationDescription,
String operationId,
String operationSummary) {
final Content content = new ContentImpl()
.addMediaType(
"application/json",
new MediaTypeImpl()
.schema(new SchemaImpl().ref("#/components/schemas/" + HEALTH_RESPONSE_SCHEMA_NAME)));

final APIResponses responses = new APIResponsesImpl()
.addAPIResponse(
"200",
new APIResponseImpl().description("OK").content(content))
.addAPIResponse(
"503",
new APIResponseImpl().description("Service Unavailable").content(content))
.addAPIResponse(
"500",
new APIResponseImpl().description("Internal Server Error").content(content));

final Operation getOperation = new OperationImpl()
.operationId(operationId)
.description(operationDescription)
.tags(MICROPROFILE_HEALTH_TAG)
.summary(operationSummary)
.responses(responses);

return new PathItemImpl()
.description(endpointDescription)
.summary(endpointSummary)
.GET(getOperation);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,21 @@ void testOpenApiPathAccessResource() {
.when().get(OPEN_API_PATH)
.then()
.header("Content-Type", "application/json;charset=UTF-8")

.body("paths", Matchers.hasKey("/q/health/ready"))
.body("paths", Matchers.hasKey("/q/health/live"))
.body("paths", Matchers.hasKey("/q/health/started"))
.body("paths", Matchers.hasKey("/q/health"))
.body("components.schemas.HealthCheckResponse.type", Matchers.equalTo("object"));

.body("components.schemas.HealthResponse.type", Matchers.equalTo("object"))
.body("components.schemas.HealthResponse.properties.status.type", Matchers.equalTo("string"))
.body("components.schemas.HealthResponse.properties.checks.type", Matchers.equalTo("array"))

.body("components.schemas.HealthCheck.type", Matchers.equalTo("object"))
.body("components.schemas.HealthCheck.properties.status.type", Matchers.equalTo("string"))
.body("components.schemas.HealthCheck.properties.name.type", Matchers.equalTo("string"))
.body("components.schemas.HealthCheck.properties.data.type", Matchers.equalTo("object"))
.body("components.schemas.HealthCheck.properties.data.nullable", Matchers.is(true));

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,16 @@ void testOpenApiPathAccessResource() {
.body("paths", Matchers.hasKey("/q/health/live"))
.body("paths", Matchers.hasKey("/q/health/started"))
.body("paths", Matchers.hasKey("/q/health"))
.body("components.schemas.HealthCheckResponse.type", Matchers.equalTo("object"));

.body("components.schemas.HealthResponse.type", Matchers.equalTo("object"))
.body("components.schemas.HealthResponse.properties.status.type", Matchers.equalTo("string"))
.body("components.schemas.HealthResponse.properties.checks.type", Matchers.equalTo("array"))

.body("components.schemas.HealthCheck.type", Matchers.equalTo("object"))
.body("components.schemas.HealthCheck.properties.status.type", Matchers.equalTo("string"))
.body("components.schemas.HealthCheck.properties.name.type", Matchers.equalTo("string"))
.body("components.schemas.HealthCheck.properties.data.type", Matchers.equalTo("object"))
.body("components.schemas.HealthCheck.properties.data.nullable", Matchers.is(true));

}

Expand Down

0 comments on commit dc5f022

Please sign in to comment.