Skip to content

Commit 6394b66

Browse files
frnefrne-trifork
authored andcommitted
Fix SmallRye Health OpenAPI definitions
The quarkus-smallrye-health extension exposes an openapi definition for the health endpoints it provides. This fixes the exposed schema to actually match the JSON structure returned by the endpoints. Additionally, the filter implementation (io.quarkus.smallrye.health.deployment.HealthOpenAPIFilter) has been refactored to use a more fluid and readable schema definition.
1 parent 7bbbfe2 commit 6394b66

File tree

3 files changed

+136
-178
lines changed

3 files changed

+136
-178
lines changed
Lines changed: 115 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package io.quarkus.smallrye.health.deployment;
22

3-
import java.util.ArrayList;
43
import java.util.Collections;
5-
import java.util.HashMap;
64
import java.util.List;
75
import java.util.Map;
86

@@ -12,9 +10,7 @@
1210
import org.eclipse.microprofile.openapi.models.PathItem;
1311
import org.eclipse.microprofile.openapi.models.Paths;
1412
import org.eclipse.microprofile.openapi.models.media.Content;
15-
import org.eclipse.microprofile.openapi.models.media.MediaType;
1613
import org.eclipse.microprofile.openapi.models.media.Schema;
17-
import org.eclipse.microprofile.openapi.models.responses.APIResponse;
1814
import org.eclipse.microprofile.openapi.models.responses.APIResponses;
1915

2016
import io.smallrye.openapi.api.models.ComponentsImpl;
@@ -31,9 +27,42 @@
3127
* Create OpenAPI entries (if configured)
3228
*/
3329
public class HealthOpenAPIFilter implements OASFilter {
30+
3431
private static final List<String> MICROPROFILE_HEALTH_TAG = Collections.singletonList("MicroProfile Health");
35-
private static final String SCHEMA_HEALTH_RESPONSE = "HealthCheckResponse";
36-
private static final String SCHEMA_HEALTH_STATUS = "HealthCheckStatus";
32+
private static final String HEALTH_RESPONSE_SCHEMA_NAME = "HealthResponse";
33+
private static final String HEALTH_CHECK_SCHEMA_NAME = "HealthCheck";
34+
35+
private static final Schema healthResponseSchemaDefinition = new SchemaImpl(HEALTH_RESPONSE_SCHEMA_NAME)
36+
.type(Schema.SchemaType.OBJECT)
37+
.properties(Map.ofEntries(
38+
39+
Map.entry("status",
40+
new SchemaImpl()
41+
.type(Schema.SchemaType.STRING)
42+
.enumeration(List.of("UP", "DOWN"))),
43+
44+
Map.entry("checks",
45+
new SchemaImpl()
46+
.type(Schema.SchemaType.ARRAY)
47+
.items(new SchemaImpl().ref("#/components/schemas/" + HEALTH_CHECK_SCHEMA_NAME)))));
48+
49+
private static final Schema healthCheckSchemaDefinition = new SchemaImpl(HEALTH_CHECK_SCHEMA_NAME)
50+
.type(Schema.SchemaType.OBJECT)
51+
.properties(Map.ofEntries(
52+
53+
Map.entry("name",
54+
new SchemaImpl()
55+
.type(Schema.SchemaType.STRING)),
56+
57+
Map.entry("status",
58+
new SchemaImpl()
59+
.type(Schema.SchemaType.STRING)
60+
.enumeration(List.of("UP", "DOWN"))),
61+
62+
Map.entry("data",
63+
new SchemaImpl()
64+
.type(Schema.SchemaType.OBJECT)
65+
.nullable(Boolean.TRUE))));
3766

3867
private final String rootPath;
3968
private final String livenessPath;
@@ -52,192 +81,102 @@ public void filterOpenAPI(OpenAPI openAPI) {
5281
if (openAPI.getComponents() == null) {
5382
openAPI.setComponents(new ComponentsImpl());
5483
}
55-
openAPI.getComponents().addSchema(SCHEMA_HEALTH_RESPONSE, createHealthCheckResponse());
56-
openAPI.getComponents().addSchema(SCHEMA_HEALTH_STATUS, createHealthCheckStatus());
84+
openAPI.getComponents().addSchema(HEALTH_RESPONSE_SCHEMA_NAME, healthResponseSchemaDefinition);
85+
openAPI.getComponents().addSchema(HEALTH_CHECK_SCHEMA_NAME, healthCheckSchemaDefinition);
5786

5887
if (openAPI.getPaths() == null) {
5988
openAPI.setPaths(new PathsImpl());
6089
}
61-
Paths paths = openAPI.getPaths();
90+
91+
final Paths paths = openAPI.getPaths();
6292

6393
// Health
64-
paths.addPathItem(rootPath, createHealthPathItem());
94+
paths.addPathItem(
95+
rootPath,
96+
createHealthEndpoint(
97+
"MicroProfile Health Endpoint",
98+
"MicroProfile Health provides a way for your application to distribute " +
99+
"information about its healthiness state to state whether or not it is able to " +
100+
"function properly",
101+
"Check the health of the application",
102+
"microprofile_health_root",
103+
"An aggregated view of the Liveness, Readiness and Startup of this application"));
65104

66105
// Liveness
67-
paths.addPathItem(livenessPath, createLivenessPathItem());
106+
paths.addPathItem(
107+
livenessPath,
108+
createHealthEndpoint(
109+
"MicroProfile Health - Liveness Endpoint",
110+
"Liveness checks are utilized to tell whether the application should be " +
111+
"restarted",
112+
"Check the liveness of the application",
113+
"microprofile_health_liveness",
114+
"The Liveness check of this application"));
68115

69116
// Readiness
70-
paths.addPathItem(readinessPath, createReadinessPathItem());
117+
paths.addPathItem(
118+
readinessPath,
119+
createHealthEndpoint(
120+
"MicroProfile Health - Readiness Endpoint",
121+
"Readiness checks are used to tell whether the application is able to " +
122+
"process requests",
123+
"Check the readiness of the application",
124+
"microprofile_health_readiness",
125+
"The Readiness check of this application"));
71126

72127
// Startup
73-
paths.addPathItem(startupPath, createStartupPathItem());
74-
}
75-
76-
private PathItem createHealthPathItem() {
77-
PathItem pathItem = new PathItemImpl();
78-
pathItem.setDescription("MicroProfile Health Endpoint");
79-
pathItem.setSummary(
80-
"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");
81-
pathItem.setGET(createHealthOperation());
82-
return pathItem;
83-
}
84-
85-
private PathItem createLivenessPathItem() {
86-
PathItem pathItem = new PathItemImpl();
87-
pathItem.setDescription("MicroProfile Health - Liveness Endpoint");
88-
pathItem.setSummary(
89-
"Liveness checks are utilized to tell whether the application should be restarted");
90-
pathItem.setGET(createLivenessOperation());
91-
return pathItem;
92-
}
93-
94-
private PathItem createReadinessPathItem() {
95-
PathItem pathItem = new PathItemImpl();
96-
pathItem.setDescription("MicroProfile Health - Readiness Endpoint");
97-
pathItem.setSummary(
98-
"Readiness checks are used to tell whether the application is able to process requests");
99-
pathItem.setGET(createReadinessOperation());
100-
return pathItem;
101-
}
102-
103-
private PathItem createStartupPathItem() {
104-
PathItem pathItem = new PathItemImpl();
105-
pathItem.setDescription("MicroProfile Health - Startup Endpoint");
106-
pathItem.setSummary(
107-
"Startup checks are an used to tell when the application has started");
108-
pathItem.setGET(createStartupOperation());
109-
return pathItem;
110-
}
111-
112-
private Operation createHealthOperation() {
113-
Operation operation = new OperationImpl();
114-
operation.setDescription("Check the health of the application");
115-
operation.setOperationId("microprofile_health_root");
116-
operation.setTags(MICROPROFILE_HEALTH_TAG);
117-
operation.setSummary("An aggregated view of the Liveness, Readiness and Startup of this application");
118-
operation.setResponses(createAPIResponses());
119-
return operation;
120-
}
121-
122-
private Operation createLivenessOperation() {
123-
Operation operation = new OperationImpl();
124-
operation.setDescription("Check the liveness of the application");
125-
operation.setOperationId("microprofile_health_liveness");
126-
operation.setTags(MICROPROFILE_HEALTH_TAG);
127-
operation.setSummary("The Liveness check of this application");
128-
operation.setResponses(createAPIResponses());
129-
return operation;
130-
}
131-
132-
private Operation createReadinessOperation() {
133-
Operation operation = new OperationImpl();
134-
operation.setDescription("Check the readiness of the application");
135-
operation.setOperationId("microprofile_health_readiness");
136-
operation.setTags(MICROPROFILE_HEALTH_TAG);
137-
operation.setSummary("The Readiness check of this application");
138-
operation.setResponses(createAPIResponses());
139-
return operation;
140-
}
141-
142-
private Operation createStartupOperation() {
143-
Operation operation = new OperationImpl();
144-
operation.setDescription("Check the startup of the application");
145-
operation.setOperationId("microprofile_health_startup");
146-
operation.setTags(MICROPROFILE_HEALTH_TAG);
147-
operation.setSummary("The Startup check of this application");
148-
operation.setResponses(createAPIResponses());
149-
return operation;
150-
}
151-
152-
private APIResponses createAPIResponses() {
153-
APIResponses responses = new APIResponsesImpl();
154-
responses.addAPIResponse("200", createAPIResponse("OK"));
155-
responses.addAPIResponse("503", createAPIResponse("Service Unavailable"));
156-
responses.addAPIResponse("500", createAPIResponse("Internal Server Error"));
157-
return responses;
158-
}
159-
160-
private APIResponse createAPIResponse(String description) {
161-
APIResponse response = new APIResponseImpl();
162-
response.setDescription(description);
163-
response.setContent(createContent());
164-
return response;
165-
}
166-
167-
private Content createContent() {
168-
Content content = new ContentImpl();
169-
content.addMediaType("application/json", createMediaType());
170-
return content;
171-
}
172-
173-
private MediaType createMediaType() {
174-
MediaType mediaType = new MediaTypeImpl();
175-
mediaType.setSchema(new SchemaImpl().ref("#/components/schemas/" + SCHEMA_HEALTH_RESPONSE));
176-
return mediaType;
128+
paths.addPathItem(
129+
startupPath,
130+
createHealthEndpoint(
131+
"MicroProfile Health - Startup Endpoint",
132+
"Startup checks are an used to tell when the application has started",
133+
"Check the startup of the application",
134+
"microprofile_health_startup",
135+
"The Startup check of this application"));
177136
}
178137

179138
/**
180-
* HealthCheckResponse:
181-
* type: object
182-
* properties:
183-
* data:
184-
* type: object
185-
* nullable: true
186-
* name:
187-
* type: string
188-
* status:
189-
* $ref: '#/components/schemas/HealthCheckStatus'
139+
* Creates a {@link PathItem} containing the endpoint definition and GET {@link Operation} for health endpoints.
190140
*
191-
* @return Schema representing HealthCheckResponse
141+
* @param endpointDescription The description for the endpoint definition
142+
* @param endpointSummary The summary for the endpoint definition
143+
* @param operationDescription The description for the operation definition
144+
* @param operationId The operation-id for the operation definition
145+
* @param operationSummary The summary for the operation definition
192146
*/
193-
private Schema createHealthCheckResponse() {
194-
Schema schema = new SchemaImpl(SCHEMA_HEALTH_RESPONSE);
195-
schema.setType(Schema.SchemaType.OBJECT);
196-
schema.setProperties(createProperties());
197-
return schema;
198-
}
199-
200-
private Map<String, Schema> createProperties() {
201-
Map<String, Schema> map = new HashMap<>();
202-
map.put("data", createData());
203-
map.put("name", createName());
204-
map.put("status", new SchemaImpl().ref("#/components/schemas/" + SCHEMA_HEALTH_STATUS));
205-
return map;
206-
}
207-
208-
private Schema createData() {
209-
Schema schema = new SchemaImpl("data");
210-
schema.setType(Schema.SchemaType.OBJECT);
211-
schema.setNullable(Boolean.TRUE);
212-
return schema;
213-
}
214-
215-
private Schema createName() {
216-
Schema schema = new SchemaImpl("name");
217-
schema.setType(Schema.SchemaType.STRING);
218-
return schema;
219-
}
220-
221-
/**
222-
* HealthCheckStatus:
223-
* enum:
224-
* - DOWN
225-
* - UP
226-
* type: string
227-
*
228-
* @return Schema representing Status
229-
*/
230-
private Schema createHealthCheckStatus() {
231-
Schema schema = new SchemaImpl(SCHEMA_HEALTH_STATUS);
232-
schema.setEnumeration(createStateEnumValues());
233-
schema.setType(Schema.SchemaType.STRING);
234-
return schema;
235-
}
236-
237-
private List<Object> createStateEnumValues() {
238-
List<Object> values = new ArrayList<>();
239-
values.add("DOWN");
240-
values.add("UP");
241-
return values;
147+
private PathItem createHealthEndpoint(
148+
String endpointDescription,
149+
String endpointSummary,
150+
String operationDescription,
151+
String operationId,
152+
String operationSummary) {
153+
final Content content = new ContentImpl()
154+
.addMediaType(
155+
"application/json",
156+
new MediaTypeImpl()
157+
.schema(new SchemaImpl().ref("#/components/schemas/" + HEALTH_RESPONSE_SCHEMA_NAME)));
158+
159+
final APIResponses responses = new APIResponsesImpl()
160+
.addAPIResponse(
161+
"200",
162+
new APIResponseImpl().description("OK").content(content))
163+
.addAPIResponse(
164+
"503",
165+
new APIResponseImpl().description("Service Unavailable").content(content))
166+
.addAPIResponse(
167+
"500",
168+
new APIResponseImpl().description("Internal Server Error").content(content));
169+
170+
final Operation getOperation = new OperationImpl()
171+
.operationId(operationId)
172+
.description(operationDescription)
173+
.tags(MICROPROFILE_HEALTH_TAG)
174+
.summary(operationSummary)
175+
.responses(responses);
176+
177+
return new PathItemImpl()
178+
.description(endpointDescription)
179+
.summary(endpointSummary)
180+
.GET(getOperation);
242181
}
243182
}

extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/DeprecatedHealthOpenAPITest.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,21 @@ void testOpenApiPathAccessResource() {
2929
.when().get(OPEN_API_PATH)
3030
.then()
3131
.header("Content-Type", "application/json;charset=UTF-8")
32+
3233
.body("paths", Matchers.hasKey("/q/health/ready"))
3334
.body("paths", Matchers.hasKey("/q/health/live"))
3435
.body("paths", Matchers.hasKey("/q/health/started"))
3536
.body("paths", Matchers.hasKey("/q/health"))
36-
.body("components.schemas.HealthCheckResponse.type", Matchers.equalTo("object"));
37+
38+
.body("components.schemas.HealthResponse.type", Matchers.equalTo("object"))
39+
.body("components.schemas.HealthResponse.properties.status.type", Matchers.equalTo("string"))
40+
.body("components.schemas.HealthResponse.properties.checks.type", Matchers.equalTo("array"))
41+
42+
.body("components.schemas.HealthCheck.type", Matchers.equalTo("object"))
43+
.body("components.schemas.HealthCheck.properties.status.type", Matchers.equalTo("string"))
44+
.body("components.schemas.HealthCheck.properties.name.type", Matchers.equalTo("string"))
45+
.body("components.schemas.HealthCheck.properties.data.type", Matchers.equalTo("object"))
46+
.body("components.schemas.HealthCheck.properties.data.nullable", Matchers.is(true));
3747

3848
}
3949

extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/HealthOpenAPITest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,16 @@ void testOpenApiPathAccessResource() {
3232
.body("paths", Matchers.hasKey("/q/health/live"))
3333
.body("paths", Matchers.hasKey("/q/health/started"))
3434
.body("paths", Matchers.hasKey("/q/health"))
35-
.body("components.schemas.HealthCheckResponse.type", Matchers.equalTo("object"));
35+
36+
.body("components.schemas.HealthResponse.type", Matchers.equalTo("object"))
37+
.body("components.schemas.HealthResponse.properties.status.type", Matchers.equalTo("string"))
38+
.body("components.schemas.HealthResponse.properties.checks.type", Matchers.equalTo("array"))
39+
40+
.body("components.schemas.HealthCheck.type", Matchers.equalTo("object"))
41+
.body("components.schemas.HealthCheck.properties.status.type", Matchers.equalTo("string"))
42+
.body("components.schemas.HealthCheck.properties.name.type", Matchers.equalTo("string"))
43+
.body("components.schemas.HealthCheck.properties.data.type", Matchers.equalTo("object"))
44+
.body("components.schemas.HealthCheck.properties.data.nullable", Matchers.is(true));
3645

3746
}
3847

0 commit comments

Comments
 (0)