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

Fix SmallRye Health OpenAPI definitions #42480

Merged
merged 1 commit into from
Aug 14, 2024
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
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
Loading