From 4feb8e67d743ae02b985c6e2004df4d5004c5d0a Mon Sep 17 00:00:00 2001 From: Benjamin Confino Date: Wed, 29 May 2024 18:28:34 +0100 Subject: [PATCH 1/8] test paramater with object and space or pipe delimiters --- .../apps/airlines/resources/ZepplinResource.java | 11 +++++++++++ .../microprofile/openapi/tck/AirlinesAppTest.java | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/ZepplinResource.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/ZepplinResource.java index 3a3dcb20..f22bd54f 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/ZepplinResource.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/ZepplinResource.java @@ -14,7 +14,12 @@ package org.eclipse.microprofile.openapi.apps.airlines.resources; import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn; +import org.eclipse.microprofile.openapi.annotations.enums.ParameterStyle; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; import org.eclipse.microprofile.openapi.annotations.enums.SecuritySchemeType; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement; import org.eclipse.microprofile.openapi.annotations.security.SecurityScheme; @@ -49,6 +54,9 @@ public Response deprecateZepplin(String string) { @Operation(summary = "Deprecate outdated airship technology", operationId = "deprecateZepplin") @Produces("text/plain") @SecurityRequirement(name = "mutualTLSScheme", scopes = "zepplinScope") + @Parameter(name = "string", description = "something about a string", in = ParameterIn.QUERY, + required = true, + schema = @Schema(type = SchemaType.OBJECT), style = ParameterStyle.SPACEDELIMITED) public Response headZepplin() { return Response.ok().build(); } @@ -60,6 +68,9 @@ public Response headZepplin() { @Operation(summary = "Deprecate outdated airship technology", operationId = "deprecateZepplin") @Produces("text/plain") @SecurityRequirement(name = "mutualTLSScheme", scopes = "zepplinScope") + @Parameter(name = "string", description = "something about a string", in = ParameterIn.QUERY, + required = true, + schema = @Schema(type = SchemaType.OBJECT), style = ParameterStyle.PIPEDELIMITED) public Response getZepplin() { return Response.ok().build(); } diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java index 514baa57..9781e1c3 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java @@ -345,6 +345,21 @@ public void testParameter(String type) { testBookingIdMethods(vr); testReviewIdMethods(vr); testUserLoginMethods(vr); + testParameterWithObjectAndStyle(vr); + } + + private void testParameterWithObjectAndStyle(ValidatableResponse vr) { + String headParameters = "paths.'/zepplins/{id}'.head.parameters"; + String getParameters = "paths.'/zepplins/{id}'.get.parameters"; + + vr.body(headParameters, hasSize(1)); + vr.body(getParameters, hasSize(1)); + + vr.body(headParameters + "[0].schema.type", equalTo("object")); + vr.body(getParameters + "[0].schema.type", equalTo("object")); + + vr.body(headParameters + "[0].style", equalTo("spaceDelimited")); + vr.body(getParameters + "[0].style", equalTo("pipeDelimited")); } private void testUserLoginMethods(ValidatableResponse vr) { From 1999871337e4177953938b924798e5ad82f73949 Mon Sep 17 00:00:00 2001 From: Benjamin Confino Date: Tue, 4 Jun 2024 13:41:32 +0100 Subject: [PATCH 2/8] remove id from zepplin --- .../openapi/apps/airlines/resources/ZepplinResource.java | 3 --- .../microprofile/openapi/reader/MyOASModelReaderImpl.java | 6 +++--- .../eclipse/microprofile/openapi/tck/AirlinesAppTest.java | 6 +++--- .../microprofile/openapi/tck/ModelReaderAppTest.java | 8 ++++---- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/ZepplinResource.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/ZepplinResource.java index f22bd54f..8c1afc72 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/ZepplinResource.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/ZepplinResource.java @@ -37,7 +37,6 @@ public class ZepplinResource { @DELETE - @Path("{id}") @APIResponse(responseCode = "200", description = "Review deleted") @APIResponse(responseCode = "404", description = "Review not found") @Operation(summary = "Deprecate outdated airship technology", operationId = "deprecateZepplin") @@ -48,7 +47,6 @@ public Response deprecateZepplin(String string) { } @HEAD - @Path("{id}") @APIResponse(responseCode = "200", description = "Review deleted") @APIResponse(responseCode = "404", description = "Review not found") @Operation(summary = "Deprecate outdated airship technology", operationId = "deprecateZepplin") @@ -62,7 +60,6 @@ public Response headZepplin() { } @GET - @Path("{id}") @APIResponse(responseCode = "200", description = "Review deleted") @APIResponse(responseCode = "404", description = "Review not found") @Operation(summary = "Deprecate outdated airship technology", operationId = "deprecateZepplin") diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java index e886cfbe..1dd3facf 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java @@ -280,13 +280,13 @@ public OpenAPI buildModel() { .description( "Indicates that the deletion event was processed successfully"))))) .paths(OASFactory.createObject(Paths.class) - .addPathItem("/zepplins/{id}", OASFactory.createObject(PathItem.class) + .addPathItem("/zepplins", OASFactory.createObject(PathItem.class) .HEAD(OASFactory.createObject(Operation.class) .requestBody(OASFactory.createRequestBody() - .ref("#/paths/~1zepplins~1{id}/delete/requestBody"))) + .ref("#/paths/~1zepplins/delete/requestBody"))) .GET(OASFactory.createObject(Operation.class) .requestBody(OASFactory.createRequestBody() - .ref("#/paths/~1zepplins~1{id}/delete/requestBody"))) + .ref("#/paths/~1zepplins/delete/requestBody"))) .DELETE(OASFactory.createObject(Operation.class) .requestBody(OASFactory.createRequestBody() .description("Something about a zepplin.") diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java index 9781e1c3..7b238eb3 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java @@ -349,8 +349,8 @@ public void testParameter(String type) { } private void testParameterWithObjectAndStyle(ValidatableResponse vr) { - String headParameters = "paths.'/zepplins/{id}'.head.parameters"; - String getParameters = "paths.'/zepplins/{id}'.get.parameters"; + String headParameters = "paths.'/zepplins'.head.parameters"; + String getParameters = "paths.'/zepplins'.get.parameters"; vr.body(headParameters, hasSize(1)); vr.body(getParameters, hasSize(1)); @@ -565,7 +565,7 @@ public void testSecurityRequirement(String type) { hasEntry(equalTo("userApiKey"), empty()), hasEntry(equalTo("userBearerHttp"), empty())))); - vr.body("paths.'/zepplins/{id}'.delete.security[0].mutualTLSScheme[0]", equalTo("zepplinScope")); + vr.body("paths.'/zepplins'.delete.security[0].mutualTLSScheme[0]", equalTo("zepplinScope")); } @Test(dataProvider = "formatProvider") diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java index ac60bc1d..ec2dd42f 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java @@ -373,10 +373,10 @@ public void testWebhooks(String type) { public void testRequestBodyInOperations(String type) { ValidatableResponse vr = callEndpoint(type); - vr.body("paths.'/zepplins/{id}'.delete.requestBody.description", equalTo("Something about a zepplin.")); - vr.body("paths.'/zepplins/{id}'.head.requestBody.$ref", equalTo("#/paths/~1zepplins~1{id}/delete/requestBody")); - vr.body("paths.'/zepplins/{id}'.get.requestBody.$ref", equalTo("#/paths/~1zepplins~1{id}/delete/requestBody")); + vr.body("paths.'/zepplins'.delete.requestBody.description", equalTo("Something about a zepplin.")); + vr.body("paths.'/zepplins'.head.requestBody.$ref", equalTo("#/paths/~1zepplins/delete/requestBody")); + vr.body("paths.'/zepplins'.get.requestBody.$ref", equalTo("#/paths/~1zepplins/delete/requestBody")); - vr.body("paths.'/zepplins/{id}'.delete.requestBody.content", notNullValue()); + vr.body("paths.'/zepplins'.delete.requestBody.content", notNullValue()); } } From bf6d085ab12bd33210aac3a84df2ff091b1083ac Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Wed, 15 May 2024 15:17:54 +0100 Subject: [PATCH 3/8] Allow ref with description and summary $ref is now allowed alongside description and summary. - Update model and annotation javadoc accordingly. - Test model - Test annotations --- .../annotations/callbacks/Callback.java | 3 +- .../openapi/annotations/headers/Header.java | 5 ++- .../openapi/annotations/links/Link.java | 5 ++- .../annotations/media/ExampleObject.java | 6 ++- .../annotations/parameters/Parameter.java | 5 ++- .../annotations/parameters/RequestBody.java | 5 ++- .../annotations/responses/APIResponse.java | 5 ++- .../annotations/security/SecurityScheme.java | 5 ++- .../openapi/annotations/tags/Tag.java | 5 ++- .../openapi/models/Reference.java | 7 ++-- .../openapi/apps/airlines/JAXRSApp.java | 41 ++++++++++++++---- .../openapi/reader/MyOASModelReaderImpl.java | 42 ++++++++++++++++++- .../openapi/tck/AirlinesAppTest.java | 31 ++++++++++++++ .../openapi/tck/ModelReaderAppTest.java | 31 ++++++++++++++ 14 files changed, 167 insertions(+), 29 deletions(-) diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/Callback.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/Callback.java index 46a3bb2b..94d3b526 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/Callback.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/Callback.java @@ -70,7 +70,8 @@ * Reference value to a Callback object. *

* This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * mutually exclusive. If other properties are defined in addition to the {@code ref} property then the result is + * undefined. * * @return reference to a callback object definition **/ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/headers/Header.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/headers/Header.java index e14a5e8f..ce4387c0 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/headers/Header.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/headers/Header.java @@ -85,8 +85,9 @@ /** * Reference value to a Header object. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. This property may be used with + * {@link #description()} but is mutually exclusive with all other properties. If properties other than + * {@code description} are defined in addition to the {@code ref} property then the result is undefined. * * @return reference to a header **/ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/links/Link.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/links/Link.java index b46fef51..23db474c 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/links/Link.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/links/Link.java @@ -96,8 +96,9 @@ /** * Reference value to a Link object. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. This property may be used with + * {@link #description()} but is mutually exclusive with all other properties. If properties other than + * {@code description} are defined in addition to the {@code ref} property then the result is undefined. * * @return reference to a link **/ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/ExampleObject.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/ExampleObject.java index 1dd530ed..7b69c3fd 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/ExampleObject.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/media/ExampleObject.java @@ -85,8 +85,10 @@ /** * Reference value to an Example object. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. This property may be used with + * {@link #description()} and {@link #summary()} but is mutually exclusive with all other properties. If properties + * other than {@code description} and {@code summary} are defined in addition to the {@code ref} property then the + * result is undefined. * * @return reference to an example **/ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/Parameter.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/Parameter.java index 634e0af5..7edaa596 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/Parameter.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/Parameter.java @@ -200,8 +200,9 @@ /** * Reference value to a Parameter object. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. This property may be used with + * {@link #description()} but is mutually exclusive with all other properties. If properties other than + * {@code description} are defined in addition to the {@code ref} property then the result is undefined. * * @return reference to a parameter **/ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBody.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBody.java index 0f07d832..bacde139 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBody.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBody.java @@ -74,8 +74,9 @@ /** * Reference value to a RequestBody object. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. This property may be used with + * {@link #description()} but is mutually exclusive with all other properties. If properties other than + * {@code description} are defined in addition to the {@code ref} property then the result is undefined. * * @return reference to a request body **/ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/responses/APIResponse.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/responses/APIResponse.java index 87cbb817..cce59f7f 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/responses/APIResponse.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/responses/APIResponse.java @@ -128,8 +128,9 @@ /** * Reference value to a Response object. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. This property may be used with + * {@link #description()} but is mutually exclusive with all other properties. If properties other than + * {@code description} are defined in addition to the {@code ref} property then the result is undefined. * * @return reference to a response **/ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityScheme.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityScheme.java index 5155f134..4990678f 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityScheme.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityScheme.java @@ -132,8 +132,9 @@ /** * Reference value to a SecurityScheme object. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. This property may be used with + * {@link #description()} but is mutually exclusive with all other properties. If properties other than + * {@code description} are defined in addition to the {@code ref} property then the result is undefined. * * @return reference to a security scheme **/ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/tags/Tag.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/tags/Tag.java index 34f8fcee..602fdc2d 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/tags/Tag.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/tags/Tag.java @@ -101,8 +101,9 @@ /** * Reference value to a Tag object. *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the ref property then the result is undefined. + * This property provides a reference to an object defined elsewhere. This property may be used with + * {@link #description()} but is mutually exclusive with all other properties. If properties other than + * {@code description} are defined in addition to the {@code ref} property then the result is undefined. * * @return reference to a tag **/ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/Reference.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/Reference.java index 3639a3b4..0226781e 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/models/Reference.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/Reference.java @@ -53,9 +53,10 @@ public interface Reference> { * response.setRef("NotFound"); // #/components/responses/NotFound * *

- * This property provides a reference to an object defined elsewhere. This property and all other properties are - * mutually exclusive. If other properties are defined in addition to the reference property then the result is - * undefined. + * This property provides a reference to an object defined elsewhere. This property may be used alongside + * {@code description} and {@code summary} if they exist on the object, but is mutually exclusive with all other + * properties. If properties other than {@code description} and {@code summary} are defined in addition to the + * reference property then the result is undefined. * * @param ref * a reference to a T object in the components section of this OpenAPI document or a JSON pointer to diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java index 50d6aae1..e9cc8a2d 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java @@ -137,7 +137,10 @@ @APIResponse(name = "FoundBookings", responseCode = "200", description = "Bookings retrieved", content = @Content(schema = @Schema(type = SchemaType.ARRAY, - implementation = Booking.class))) + implementation = Booking.class))), + @APIResponse(name = "FoundBookingsARef", + ref = "#/components/responses/FoundBookings", + description = "Found Bookings Reference") }, parameters = { @Parameter(name = "departureDate", in = ParameterIn.QUERY, @@ -147,7 +150,10 @@ @Parameter(name = "username", in = ParameterIn.QUERY, description = "The name that needs to be deleted", schema = @Schema(type = SchemaType.STRING), - required = true) + required = true), + @Parameter(name = "usernameARef", + ref = "#/components/parameters/username", + description = "username reference") }, examples = { @ExampleObject(name = "review", summary = "External review example", @@ -156,13 +162,21 @@ extensions = @Extension(name = "x-example-object", value = "test-example-object")), @ExampleObject(name = "user", summary = "External user example", - externalValue = "http://foo.bar/examples/user-example.json") + externalValue = "http://foo.bar/examples/user-example.json"), + @ExampleObject(name = "userARef", + ref = "#/components/examples/user", + description = "User reference", + summary = "Referenced example") }, requestBodies = { @RequestBody(name = "review", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = Review.class)), - required = true, description = "example review to add") + required = true, + description = "example review to add"), + @RequestBody(name = "reviewARef", + ref = "#/components/requestBodies/review", + description = "Review reference") }, headers = { @Header(name = "Max-Rate", description = "Maximum rate", @@ -173,20 +187,29 @@ value = "test-header")), @Header(name = "Request-Limit", description = "The number of allowed requests in the current period", - schema = @Schema(type = SchemaType.INTEGER)) + schema = @Schema(type = SchemaType.INTEGER)), + @Header(name = "Request-Limit-ARef", + ref = "#/components/headers/Request-Limit", + description = "Request-Limit reference") }, securitySchemes = { @SecurityScheme(securitySchemeName = "httpTestScheme", description = "user security scheme", type = SecuritySchemeType.HTTP, - scheme = "testScheme") + scheme = "testScheme"), + @SecurityScheme(securitySchemeName = "httpTestSchemeARef", + ref = "#/components/securitySchemes/httpTestScheme", + description = "httpTestScheme reference") }, links = { @Link(name = "UserName", description = "The username corresponding to provided user id", operationId = "getUserByName", parameters = @LinkParameter(name = "userId", - expression = "$request.path.id")) + expression = "$request.path.id")), + @Link(name = "UserNameARef", + ref = "#/components/links/UserName", + description = "UserName reference") }, callbacks = { @Callback(name = "GetBookings", @@ -194,7 +217,9 @@ operations = @CallbackOperation(summary = "Retrieve all bookings for current user", responses = { @APIResponse(ref = "FoundBookings") - })) + })), + @Callback(name = "GetBookingsARef", + ref = "#/components/callbacks/GetBookings") }, extensions = @Extension(name = "x-components", value = "test-components")), extensions = @Extension(name = "x-openapi-definition", value = "test-openapi-definition")) diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java index 45ce98a3..8f0ed769 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java @@ -105,6 +105,8 @@ public OpenAPI buildModel() { .title("Airlines")) .addSchema("AirlinesRef", OASFactory.createObject(Schema.class) .ref("#/components/schemas/Airlines")) + .addSchema("Flight", OASFactory.createSchema() + .addType(Schema.SchemaType.OBJECT)) .addSchema("id", OASFactory.createObject(Schema.class) .addType(Schema.SchemaType.INTEGER) .format("int32")) @@ -176,6 +178,9 @@ public OpenAPI buildModel() { .schema(OASFactory.createObject(Schema.class) .addType(Schema.SchemaType.ARRAY) .ref("#/components.schemas.Booking"))))) + .addResponse("FoundBookingsRef", OASFactory.createAPIResponse() + .ref("#/components/responses/FoundBookings") + .description("Found Bookings Reference")) .parameters(new HashMap()) .addParameter("departureDate", OASFactory.createObject(Parameter.class) .required(true) @@ -185,6 +190,9 @@ public OpenAPI buildModel() { .required(true) .description("The name that needs to be deleted") .schema(OASFactory.createObject(Schema.class))) + .addParameter("usernameRef", OASFactory.createParameter() + .ref("#/components/parameters/username") + .description("username reference")) .examples(new HashMap()) .addExample("review", OASFactory.createObject(Example.class) .summary("External review example") @@ -193,6 +201,10 @@ public OpenAPI buildModel() { .addExample("user", OASFactory.createObject(Example.class) .summary("External user example") .externalValue("http://foo.bar/examples/user-example.json")) + .addExample("userRef", OASFactory.createExample() + .ref("#/componets/examples/user") + .summary("Referenced example") + .description("User reference")) .requestBodies(new HashMap()) .addRequestBody("review", OASFactory.createObject(RequestBody.class) .required(true) @@ -201,6 +213,9 @@ public OpenAPI buildModel() { .addMediaType("application/json", OASFactory.createObject(MediaType.class) .schema(OASFactory.createObject(Schema.class) .ref("#/components.schemas.Review"))))) + .addRequestBody("reviewRef", OASFactory.createRequestBody() + .ref("#/components/requestBodies/review") + .description("Review reference")) .headers(new HashMap()) .addHeader("Max-Rate", OASFactory.createObject(Header.class) .description("Maximum rate") @@ -213,17 +228,42 @@ public OpenAPI buildModel() { .description("The number of allowed requests in the current period") .schema(OASFactory.createObject(Schema.class) .addType(Schema.SchemaType.INTEGER))) + .addHeader("Request-Limit-Ref", OASFactory.createHeader() + .ref("#/components/headers/Request-Limit") + .description("Request-Limit reference")) .securitySchemes(new HashMap()) .addSecurityScheme("httpTestScheme", OASFactory.createObject(SecurityScheme.class) .description("user security scheme") .type(SecurityScheme.Type.HTTP) .scheme("testScheme")) + .addSecurityScheme("httpTestSchemeRef", OASFactory.createSecurityScheme() + .ref("#/components/securitySchemes/httpTestScheme") + .description("httpTestScheme reference")) .links(new HashMap()) .addLink("UserName", OASFactory.createObject(Link.class) .description("The username corresponding to provided user id") .operationId("getUserByName") .parameters(new HashMap()) - .addParameter("userId", "$request.link-path.userId"))) + .addParameter("userId", "$request.link-path.userId")) + .addLink("UserNameRef", OASFactory.createLink() + .ref("#/components/links/UserName") + .description("UserName reference")) + .callbacks(new HashMap<>()) + .addCallback("availabilityCallback", OASFactory.createCallback() + .addPathItem("http://localhost:9080/oas3-airlines/availability", OASFactory + .createPathItem() + .GET(OASFactory.createOperation() + .summary("Retrieve available flights") + .responses(OASFactory.createAPIResponses() + .addAPIResponse("200", OASFactory.createAPIResponse() + .description("successful operation") + .content(OASFactory.createContent() + .addMediaType("application/json", OASFactory + .createMediaType() + .schema(OASFactory.createSchema() + .ref("#/components/schemas/Flight"))))))))) + .addCallback("availabilityCallbackRef", OASFactory.createCallback() + .ref("#/components/callbacks/availabilityCallback"))) .tags(new ArrayList()) .addTag(OASFactory.createObject(Tag.class) .name("Get Airlines") diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java index b83d3bb2..e2e4b934 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java @@ -1128,4 +1128,35 @@ public void testOpenAPIDefinitionExtension(String type) { vr.body("x-openapi-definition", equalTo("test-openapi-definition")); } + @Test(dataProvider = "formatProvider") + public void testRef(String type) { + ValidatableResponse vr = callEndpoint(type); + + vr.body("components.responses.FoundBookingsARef.$ref", equalTo("#/components/responses/FoundBookings")); + vr.body("components.responses.FoundBookingsARef.description", equalTo("Found Bookings Reference")); + + vr.body("components.parameters.usernameARef.$ref", equalTo("#/components/parameters/username")); + vr.body("components.parameters.usernameARef.description", equalTo("username reference")); + + vr.body("components.examples.userARef.$ref", equalTo("#/components/examples/user")); + vr.body("components.examples.userARef.description", equalTo("User reference")); + vr.body("components.examples.userARef.summary", equalTo("Referenced example")); + + vr.body("components.requestBodies.reviewARef.$ref", equalTo("#/components/requestBodies/review")); + vr.body("components.requestBodies.reviewARef.description", equalTo("Review reference")); + + vr.body("components.headers.Request-Limit-ARef.$ref", equalTo("#/components/headers/Request-Limit")); + vr.body("components.headers.Request-Limit-ARef.description", equalTo("Request-Limit reference")); + + vr.body("components.securitySchemes.httpTestSchemeARef.$ref", + equalTo("#/components/securitySchemes/httpTestScheme")); + vr.body("components.securitySchemes.httpTestSchemeARef.description", equalTo("httpTestScheme reference")); + + vr.body("components.links.UserNameARef.$ref", equalTo("#/components/links/UserName")); + vr.body("components.links.UserNameARef.description", equalTo("UserName reference")); + + vr.body("components.callbacks.GetBookingsARef.$ref", + equalTo("#/components/callbacks/GetBookings")); + } + } diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java index ae928428..f7284acb 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java @@ -327,6 +327,37 @@ public void testComponents(String type) { vr.body("components.links.UserName", notNullValue()); } + @Test(dataProvider = "formatProvider") + public void testReferences(String type) { + ValidatableResponse vr = callEndpoint(type); + + vr.body("components.responses.FoundBookingsRef.$ref", equalTo("#/components/responses/FoundBookings")); + vr.body("components.responses.FoundBookingsRef.description", equalTo("Found Bookings Reference")); + + vr.body("components.parameters.usernameRef.$ref", equalTo("#/components/parameters/username")); + vr.body("components.parameters.usernameRef.description", equalTo("username reference")); + + vr.body("components.examples.userRef.$ref", equalTo("#/componets/examples/user")); + vr.body("components.examples.userRef.description", equalTo("User reference")); + vr.body("components.examples.userRef.summary", equalTo("Referenced example")); + + vr.body("components.requestBodies.reviewRef.$ref", equalTo("#/components/requestBodies/review")); + vr.body("components.requestBodies.reviewRef.description", equalTo("Review reference")); + + vr.body("components.headers.Request-Limit-Ref.$ref", equalTo("#/components/headers/Request-Limit")); + vr.body("components.headers.Request-Limit-Ref.description", equalTo("Request-Limit reference")); + + vr.body("components.securitySchemes.httpTestSchemeRef.$ref", + equalTo("#/components/securitySchemes/httpTestScheme")); + vr.body("components.securitySchemes.httpTestSchemeRef.description", equalTo("httpTestScheme reference")); + + vr.body("components.links.UserNameRef.$ref", equalTo("#/components/links/UserName")); + vr.body("components.links.UserNameRef.description", equalTo("UserName reference")); + + vr.body("components.callbacks.availabilityCallbackRef.$ref", + equalTo("#/components/callbacks/availabilityCallback")); + } + @Test(dataProvider = "formatProvider") public void testHeaderInComponents(String type) { ValidatableResponse vr = callEndpoint(type); From 8f3ec8798abb42b3b5353ccdeb3f33bb12ead789 Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Wed, 29 May 2024 15:48:15 +0100 Subject: [PATCH 4/8] Make request body required by default When scanning classes and annotations, default to the request body being required for operations which have a request body. --- .../annotations/parameters/RequestBody.java | 6 ++++- .../annotations/parameters/package-info.java | 2 +- .../openapi/apps/airlines/JAXRSApp.java | 11 ++++++++- .../apps/airlines/resources/UserResource.java | 6 ++--- .../apps/petstore/resource/PetResource.java | 3 +-- .../petstore/resource/PetStoreResource.java | 2 +- .../apps/petstore/resource/UserResource.java | 5 ++-- .../openapi/tck/AirlinesAppTest.java | 23 +++++++++++++++++++ 8 files changed, 46 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBody.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBody.java index bacde139..3b61fd38 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBody.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBody.java @@ -56,10 +56,14 @@ /** * Determines if the request body is required in the request. + *

+ * Note that the default value of this property is {@code true}, while the default value of the {@code required} + * property in the OpenAPI specification is {@code false}, because Jakarta REST resource methods which accept a + * request body generally require it. * * @return whether or not this requestBody is required **/ - boolean required() default false; + boolean required() default true; /** * The unique name to identify this request body. Unless this annotation is used on the actual request body diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/package-info.java index 633bda27..e59e772b 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/package-info.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/package-info.java @@ -32,6 +32,6 @@ * */ -@org.osgi.annotation.versioning.Version("1.2") +@org.osgi.annotation.versioning.Version("1.2.1") @org.osgi.annotation.versioning.ProviderType package org.eclipse.microprofile.openapi.annotations.parameters; \ No newline at end of file diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java index e9cc8a2d..6ab78821 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java @@ -172,8 +172,17 @@ @RequestBody(name = "review", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = Review.class)), - required = true, description = "example review to add"), + @RequestBody(name = "nonRequiredReview", + content = @Content(mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = Review.class)), + required = false, + description = "example non-required review"), + @RequestBody(name = "requiredReview", + content = @Content(mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = Review.class)), + required = true, + description = "example required review"), @RequestBody(name = "reviewARef", ref = "#/components/requestBodies/review", description = "Review reference") diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/UserResource.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/UserResource.java index d6ba7682..726d5f05 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/UserResource.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/resources/UserResource.java @@ -148,7 +148,7 @@ public Response createUser( /* tags = {"user"}, //this operation intentionally doesn't have tags attribute, since above Tag ref should apply */ ) public Response createUsersWithArrayInput( - @RequestBody(description = "Array of user object", required = true, + @RequestBody(description = "Array of user object", content = @Content(mediaType = "application/json", schema = @Schema(type = SchemaType.ARRAY, implementation = User.class, nullable = true, writeOnly = true, minItems = 2, @@ -168,7 +168,7 @@ public Response createUsersWithArrayInput( @Operation(summary = "Creates list of users with given input list", // List of User objects operationId = "createUsersFromList") public Response createUsersWithListInput( - @RequestBody(description = "List of user object", required = true) java.util.List users) { + @RequestBody(description = "List of user object") java.util.List users) { for (User user : users) { userData.addUser(user); } @@ -177,7 +177,7 @@ public Response createUsersWithListInput( @Path("/username/{username}") @PUT - @RequestBody(name = "user", description = "Record of a new user to be created in the system.", + @RequestBody(name = "user", description = "Record of a new user to be created in the system.", required = false, content = @Content(mediaType = "application/json", schema = @Schema(implementation = User.class), examples = @ExampleObject(name = "user", summary = "Example user properties to update", diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/PetResource.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/PetResource.java index 9d74f262..cfae2c91 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/PetResource.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/PetResource.java @@ -172,7 +172,7 @@ public Response deletePet( @RequestBody(name = "pet", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Pet.class), examples = @ExampleObject(ref = "http://example.org/petapi-examples/openapi.json#/components/examples/pet-example")), - required = true, description = "example of a new pet to add") + description = "example of a new pet to add") @Operation(summary = "Add pet to store", description = "Add a new pet to the store") public Response addPet(Pet pet) { Pet updatedPet = petData.addPet(pet); @@ -193,7 +193,6 @@ public Response addPet(Pet pet) { @Operation(summary = "Update an existing pet", description = "Update an existing pet with the given new attributes") public Response updatePet( @RequestBody(description = "Attribute to update existing pet record", - required = true, content = @Content(schema = @Schema(implementation = Pet.class))) Pet pet) { Pet updatedPet = petData.addPet(pet); return Response.ok().entity(updatedPet).build(); diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/PetStoreResource.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/PetStoreResource.java index a4a9426c..1dc91525 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/PetStoreResource.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/PetStoreResource.java @@ -115,7 +115,7 @@ public Response getOrderById( @APIResponse(responseCode = "200", description = "successful operation") @APIResponse(responseCode = "400", description = "Invalid Order") public Order placeOrder( - @RequestBody(description = "order placed for purchasing the pet", required = true) Order order) { + @RequestBody(description = "order placed for purchasing the pet") Order order) { storeData.placeOrder(order); return storeData.placeOrder(order); } diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/UserResource.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/UserResource.java index be994b3a..15a672c4 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/UserResource.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/petstore/resource/UserResource.java @@ -69,8 +69,7 @@ public class UserResource { }) public Response createUser( @RequestBody(description = "Created user object", - content = @Content(schema = @Schema(ref = "#/components/schemas/User")), - required = true) User user) { + content = @Content(schema = @Schema(ref = "#/components/schemas/User"))) User user) { userData.addUser(user); return Response.ok().entity("").build(); } @@ -112,7 +111,7 @@ public Response updateUser( @Parameter(name = "username", description = "name that need to be deleted", schema = @Schema(type = SchemaType.STRING), required = true) @PathParam("username") String username, - @RequestBody(description = "Updated user object", required = true) User user) { + @RequestBody(description = "Updated user object") User user) { userData.addUser(user); return Response.ok().entity("").build(); } diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java index d9973b4c..31fc29a5 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java @@ -487,9 +487,16 @@ public void testRequestBodyAnnotations(String type) { vr.body(endpoint + ".description", equalTo("Create a new booking with the provided information.")); vr.body(endpoint + ".content", notNullValue()); vr.body(endpoint + ".x-request-body", equalTo("test-request-body")); + vr.body(endpoint + ".required", equalTo(true)); + // PUT method with entity parameter but no @RequestBody annotation endpoint = "paths.'/bookings/{id}'.put.requestBody"; vr.body(endpoint + ".content", notNullValue()); + vr.body(endpoint + ".required", equalTo(true)); + + // GET method without @RequestBody annotation + endpoint = "paths.'/bookings/{id}'.get.requestBody"; + vr.body(endpoint, nullValue()); endpoint = "paths.'/user'.post.requestBody"; vr.body(endpoint + ".description", equalTo("Record of a new user to be created in the system.")); @@ -499,6 +506,7 @@ public void testRequestBodyAnnotations(String type) { endpoint = "paths.'/user/username/{username}'.put.requestBody"; vr.body(endpoint + ".description", equalTo("Record of a new user to be created in the system.")); vr.body(endpoint + ".content", notNullValue()); + vr.body(endpoint + ".required", either(nullValue()).or(equalTo(false))); endpoint = "paths.'/user/createWithArray'.post.requestBody"; vr.body(endpoint + ".description", equalTo("Array of user object")); @@ -509,6 +517,21 @@ public void testRequestBodyAnnotations(String type) { vr.body(endpoint + ".description", equalTo("List of user object")); vr.body(endpoint + ".content", notNullValue()); vr.body(endpoint + ".required", equalTo(true)); + + endpoint = "components.requestBodies.review"; + vr.body(endpoint + ".description", equalTo("example review to add")); + vr.body(endpoint + ".content", notNullValue()); + vr.body(endpoint + ".required", equalTo(true)); + + endpoint = "components.requestBodies.nonRequiredReview"; + vr.body(endpoint + ".description", equalTo("example non-required review")); + vr.body(endpoint + ".content", notNullValue()); + vr.body(endpoint + ".required", either(nullValue()).or(equalTo(false))); + + endpoint = "components.requestBodies.requiredReview"; + vr.body(endpoint + ".description", equalTo("example required review")); + vr.body(endpoint + ".content", notNullValue()); + vr.body(endpoint + ".required", equalTo(true)); } @Test(dataProvider = "formatProvider") From 350d6c117a73167654a832b7af5828fd6bc3d51a Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Fri, 24 May 2024 16:36:07 +0100 Subject: [PATCH 5/8] Add PathItems to Components - update model interfaces - update ModelConstructionTest - update OASModelReader and static file tests --- .../openapi/models/Components.java | 49 +++++++++++++++++++ .../openapi/reader/MyOASModelReaderImpl.java | 14 +++++- .../openapi/tck/ModelConstructionTest.java | 48 ++++++++++++------ .../openapi/tck/ModelReaderAppTest.java | 17 +++++++ .../openapi/tck/StaticDocumentTest.java | 9 ++++ tck/src/main/resources/simpleapi.yaml | 21 ++++++++ 6 files changed, 143 insertions(+), 15 deletions(-) diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/Components.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/Components.java index 174af0d4..6f8ab497 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/models/Components.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/Components.java @@ -483,4 +483,53 @@ default Components callbacks(Map callbacks) { */ void removeCallback(String key); + /** + * Returns the pathItems property of this Components instance. Path items listed here can be referenced from + * elsewhere in the OpenAPI document. + * + * @return a copy Map (potentially immutable) of path items + */ + Map getPathItems(); + + /** + * Sets the pathItems property of this Components instance. Path items listed here can be referenced from elsewhere + * in the OpenAPI document. + * + * @param pathItems + * a map of path items + */ + void setPathItems(Map pathItems); + + /** + * Sets the pathItems property of this Components instance. Path items listed here can be referenced from elsewhere + * in the OpenAPI document. + * + * @param pathItems + * a map of path items + * @return the current Schema instance + */ + default Components pathItems(Map pathItems) { + setPathItems(pathItems); + return this; + } + + /** + * Adds a path item. + * + * @param name + * name of the path item to add + * @param pathItem + * the path item to add + * @return the current Schema instance + */ + Components addPathItem(String name, PathItem pathItem); + + /** + * Removes a path item. + * + * @param name + * name of the path item to remove + */ + void removePathItem(String name); + } \ No newline at end of file diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java index 4036fb14..4b1a745e 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java @@ -264,7 +264,19 @@ public OpenAPI buildModel() { .schema(OASFactory.createSchema() .ref("#/components/schemas/Flight"))))))))) .addCallback("availabilityCallbackRef", OASFactory.createCallback() - .ref("#/components/callbacks/availabilityCallback"))) + .ref("#/components/callbacks/availabilityCallback")) + .pathItems(new HashMap<>()) + .addPathItem("idCrud", OASFactory.createPathItem() + .DELETE(OASFactory.createOperation() + .responses(OASFactory.createAPIResponses() + .addAPIResponse("202", OASFactory.createAPIResponse() + .content(OASFactory.createContent()) + .description("Delete item")))) + .addParameter(OASFactory.createParameter() + .name("id") + .in(In.PATH) + .description("The item parameter") + .required(true)))) .tags(new ArrayList()) .addTag(OASFactory.createObject(Tag.class) .name("Get Airlines") diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java index f62faa42..ae674fe9 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java @@ -393,6 +393,27 @@ public void componentsTest() { SecurityScheme otherSecuritySchemeValue = createConstructibleInstance(SecurityScheme.class); checkMapImmutable(c, Components::getSecuritySchemes, "otherSecurityScheme", otherSecuritySchemeValue); checkNullValueInAdd(c::getSecuritySchemes, c::addSecurityScheme, "someSecurityScheme", securitySchemeValue); + + final String pathItemKey = "myPathItem"; + final PathItem pathItemValue = createConstructibleInstance(PathItem.class); + checkSameObject(c, c.addPathItem(pathItemKey, pathItemValue)); + checkMapEntry(c.getPathItems(), pathItemKey, pathItemValue); + assertEquals(c.getPathItems().size(), 1, "The map is expected to contain one entry."); + c.removePathItem(pathItemKey); + assertEquals(c.getPathItems().size(), 0, "The map is expected to be empty."); + + final String pathItemKey2 = "myPathItem2"; + final PathItem pathItemValue2 = createConstructibleInstance(PathItem.class); + c.setPathItems(Collections.singletonMap(pathItemKey2, pathItemValue2)); + checkMapEntry(c.getPathItems(), pathItemKey2, pathItemValue2); + assertEquals(c.getPathItems().size(), 1, "The map is expected to contain one entry."); + checkSameObject(c, c.addPathItem(pathItemKey, pathItemValue)); + checkMapEntry(c.getPathItems(), pathItemKey, pathItemValue); + assertEquals(c.getPathItems().size(), 2, "The map is expected to contain two entries."); + + PathItem otherPathItemValue = createConstructibleInstance(PathItem.class); + checkMapImmutable(c, Components::getPathItems, "otherPathItem", otherPathItemValue); + checkNullValueInAdd(c::getPathItems, c::addPathItem, "somePathItem", pathItemValue); } @Test @@ -1779,14 +1800,14 @@ private void processReference(Reference r) { r.setRef(myRef1); assertEquals(r.getRef(), myRef1, "The return value of getRef() is expected to be equal to the value that was set."); + // Check that the short name ref value can be set using the setter method and that the getter method returns the // expanded value. - if (!(r instanceof PathItem)) { - final String shortName = "myRef2"; - final String myRef2 = createReference(r, shortName); - r.setRef(shortName); - assertEquals(r.getRef(), myRef2, "The return value of getRef() is expected to be a fully expanded name."); - } + final String shortName2 = "myRef2"; + final String myRef2 = createReference(r, shortName2); + r.setRef(shortName2); + assertEquals(r.getRef(), myRef2, "The return value of getRef() is expected to be a fully expanded name."); + // Check that the ref value can be set using the builder method and that the getter method returns the same // value. final String myRef3 = createReference(r, "myRef3"); @@ -1794,15 +1815,14 @@ private void processReference(Reference r) { assertSame(self, r, "The return value of ref() is expected to return the current instance."); assertEquals(r.getRef(), myRef3, "The return value of getRef() is expected to be equal to the value that was set."); + // Check that the short name ref value can be set using the builder method and that the getter method returns // the expanded value. - if (!(r instanceof PathItem)) { - final String shortName = "myRef4"; - final String myRef4 = createReference(r, shortName); - final Reference self2 = r.ref(shortName); - assertSame(self2, r, "The return value of ref() is expected to return the current instance."); - assertEquals(r.getRef(), myRef4, "The return value of getRef() is expected to be a fully expanded name."); - } + final String shortName4 = "myRef4"; + final String myRef4 = createReference(r, shortName4); + final Reference self2 = r.ref(shortName4); + assertSame(self2, r, "The return value of ref() is expected to return the current instance."); + assertEquals(r.getRef(), myRef4, "The return value of getRef() is expected to be a fully expanded name."); } private void processConstructibleProperty(Constructible o, Property p, Class enclosingInterface) { @@ -1896,7 +1916,7 @@ private String createReference(Reference r, String v) { } else if (r instanceof Parameter) { sb.append("#/components/parameters/"); } else if (r instanceof PathItem) { - sb.append("http://www.abc.def.ghi/"); + sb.append("#/components/pathItems/"); } else if (r instanceof RequestBody) { sb.append("#/components/requestBodies/"); } else if (r instanceof Schema) { diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java index a265b850..300ca235 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelReaderAppTest.java @@ -325,6 +325,7 @@ public void testComponents(String type) { vr.body("components.headers.Request-Limit", notNullValue()); vr.body("components.securitySchemes.httpTestScheme", notNullValue()); vr.body("components.links.UserName", notNullValue()); + vr.body("components.pathItems.idCrud", notNullValue()); } @Test(dataProvider = "formatProvider") @@ -368,6 +369,22 @@ public void testHeaderInComponents(String type) { vr.body(maxRate + ".allowEmptyValue", equalTo(true)); } + @Test(dataProvider = "formatProvider") + public void testPathItemWithRef(String type) { + ValidatableResponse vr = callEndpoint(type); + + // Referencing path item + String refpath = "paths.'/refpath/{id}'"; + vr.body(refpath + ".$ref", equalTo("#/components/pathItems/idCrud")); + vr.body(refpath + ".get.responses.'200'", notNullValue()); + + // Referenced path item + String idCrud = "components.pathItems.idCrud"; + vr.body(idCrud, notNullValue()); + vr.body(idCrud + ".parameters[0].description", equalTo("The item parameter")); + vr.body(idCrud + ".delete.responses.'202'.description", equalTo("Delete item")); + } + @Test(dataProvider = "formatProvider") public void testContentInAPIResponse(String type) { ValidatableResponse vr = callEndpoint(type); diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/StaticDocumentTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/StaticDocumentTest.java index 3d47f946..350160bf 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/StaticDocumentTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/StaticDocumentTest.java @@ -140,5 +140,14 @@ public void testStaticDocument(String type) { equalTo("#/components/schemas/Booking")); vr.body(webhookDelete + ".responses.'204'.description", equalTo("Indicates that the deletion event was processed successfully")); + + String idCrud = "components.pathItems.idCrud"; + vr.body(idCrud, notNullValue()); + vr.body(idCrud + ".parameters[0].description", equalTo("The item parameter")); + vr.body(idCrud + ".delete.responses.'202'.description", equalTo("Delete item")); + + String refpath = "paths.'/refpath/{id}'"; + vr.body(refpath + ".$ref", equalTo("#/components/pathItems/idCrud")); + vr.body(refpath + ".get.responses.'200'", notNullValue()); } } diff --git a/tck/src/main/resources/simpleapi.yaml b/tck/src/main/resources/simpleapi.yaml index 77086d95..b38c11d3 100644 --- a/tck/src/main/resources/simpleapi.yaml +++ b/tck/src/main/resources/simpleapi.yaml @@ -120,6 +120,15 @@ paths: responses: '200': description: trace operation tested + /refpath/{id}: + $ref: '#/components/pathItems/idCrud' + get: + responses: + '200': + content: + 'application/json': + schema: + $ref: '#/components/schemas/InventoryItem' webhooks: bookingEvent: put: @@ -186,3 +195,15 @@ components: examples: - 408-867-5309 type: object + pathItems: + idCrud: + delete: + responses: + '202': + content: [] + description: Delete item + parameters: + - name: id + in: path + description: The item parameter + required: true From 09270cc1738c3b7773dbcd11a6f28fe889e0569a Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Thu, 6 Jun 2024 10:57:42 +0100 Subject: [PATCH 6/8] Re-add change to model reader test lost in merge --- .../openapi/reader/MyOASModelReaderImpl.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java index 2087ed21..e94c0a7a 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/reader/MyOASModelReaderImpl.java @@ -455,6 +455,16 @@ public OpenAPI buildModel() { .title("id") .description("id of the new booking") .addType( - Schema.SchemaType.STRING))))))))); + Schema.SchemaType.STRING)))))))) + .addPathItem("/refpath/{id}", OASFactory.createPathItem() + .ref("idCrud") // Note PathItem allows ref with other properties + .GET(OASFactory.createOperation() + .responses(OASFactory.createAPIResponses() + .addAPIResponse("200", OASFactory.createAPIResponse() + .content(OASFactory.createContent() + .addMediaType("application/json", + OASFactory.createMediaType() + .schema(OASFactory.createSchema() + .ref("Airlines"))))))))); } } From 96366ebe8f0489747fa7ac7ca1a52b1627f33a66 Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Wed, 5 Jun 2024 15:28:49 +0100 Subject: [PATCH 7/8] PathItem and PathItemOperation annotations Allows PathItems to be defined under Components. Allows webhooks to be defined. --- .../openapi/annotations/Components.java | 9 + .../annotations/OpenAPIDefinition.java | 9 + .../openapi/annotations/PathItem.java | 99 ++++++++++ .../annotations/PathItemOperation.java | 176 ++++++++++++++++++ .../annotations/callbacks/Callback.java | 10 + .../annotations/callbacks/package-info.java | 2 +- .../openapi/annotations/package-info.java | 2 +- 7 files changed, 305 insertions(+), 2 deletions(-) create mode 100644 api/src/main/java/org/eclipse/microprofile/openapi/annotations/PathItem.java create mode 100644 api/src/main/java/org/eclipse/microprofile/openapi/annotations/PathItemOperation.java diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/Components.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/Components.java index abb1d56c..122db138 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/Components.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/Components.java @@ -107,6 +107,15 @@ */ Callback[] callbacks() default {}; + /** + * An object to hold reusable Path Item Objects. + * + * @return the reusable PathItem objects. + * + * @since 4.0 + */ + PathItem[] pathItems() default {}; + /** * List of extensions to be added to the {@link org.eclipse.microprofile.openapi.models.Components Components} model * corresponding to the containing annotation. diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/OpenAPIDefinition.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/OpenAPIDefinition.java index 2459313f..9f2da51e 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/OpenAPIDefinition.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/OpenAPIDefinition.java @@ -93,6 +93,15 @@ */ ExternalDocumentation externalDocs() default @ExternalDocumentation; + /** + * An array of webhook definitions which the API may be instructed to call using out-of-band mechanisms. + * + * @return the array of webhooks + * + * @since 4.0 + */ + PathItem[] webhooks() default {}; + /** * An element to hold a set of reusable objects for different aspects of the OpenAPI Specification (OAS). * diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/PathItem.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/PathItem.java new file mode 100644 index 00000000..c6238096 --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/PathItem.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.microprofile.openapi.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.eclipse.microprofile.openapi.annotations.extensions.Extension; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.servers.Server; + +/** + * Describes a set of operations available on a single path. + * + * @since 4.0 + */ +@Target({}) +@Retention(RetentionPolicy.RUNTIME) +public @interface PathItem { + + /** + * The name of the Path Item object, used as the map key when the path item is used under + * {@link Components#pathItems()} or {@link OpenAPIDefinition#webhooks()} + * + * @return the path item name + */ + String name(); + + /** + * Reference value to a PathItem object. + *

+ * This property provides a reference to an object defined elsewhere. + *

+ * Unlike {@code ref} on most MP OpenAPI annotations, this property is not mutually exclusive with other + * properties. + * + * @return reference to a path item object definition + **/ + String ref() default ""; + + /** + * The summary of the path item. + * + * @return the summary + */ + String summary() default ""; + + /** + * The description of the path item. + * + * @return the description + */ + String description() default ""; + + /** + * The operations available under this Path Item. + *

+ * The {@link PathItemOperation#method() method} MUST be defined for each operation. + * + * @return the list of operations + */ + PathItemOperation[] operations() default {}; + + /** + * A list of servers to be used for this Path Item, overriding those defined for the whole API. + * + * @return the list of servers + */ + Server[] servers() default {}; + + /** + * A list of parameters which apply to all operations under this path item. + * + * @return the list of parameters + */ + Parameter[] parameters() default {}; + + /** + * List of extensions to be added to the {@link org.eclipse.microprofile.openapi.models.PathItem PathItem} model + * corresponding to the containing annotation. + * + * @return the list of extensions + */ + Extension[] extensions() default {}; +} diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/PathItemOperation.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/PathItemOperation.java new file mode 100644 index 00000000..261928a7 --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/PathItemOperation.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.microprofile.openapi.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.eclipse.microprofile.openapi.annotations.callbacks.Callback; +import org.eclipse.microprofile.openapi.annotations.extensions.Extension; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement; +import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirementsSet; +import org.eclipse.microprofile.openapi.annotations.servers.Server; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; + +/** + * Describes an Operation + *

+ * This annotation is only used for operations defined under a {@link PathItem}. For operations provided by the API + * itself, it's more common to use the {@link Operation} annotation applied to a Jakarta REST resource method instead. + * + * @since 4.0 + */ +@Target({}) +@Retention(RetentionPolicy.RUNTIME) +public @interface PathItemOperation { + + /** + * The HTTP method for this operation. + * + * @return the HTTP method of this operation + **/ + String method(); + + /** + * The tags which apply to this operation. + * + * @return the list of tags + */ + Tag[] tags() default {}; + + /** + * Provides a brief description of what this operation does. + * + * @return a summary of this operation + **/ + String summary() default ""; + + /** + * A verbose description of the operation behavior. CommonMark syntax MAY be used for rich text representation. + * + * @return a description of this operation + **/ + String description() default ""; + + /** + * Additional external documentation for this operation. + * + * @return external documentation associated with this operation instance + **/ + ExternalDocumentation externalDocs() default @ExternalDocumentation(); + + /** + * Unique string used to identify the operation. The id MUST be unique among all operations described in the API. + *

+ * Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to + * follow common programming naming conventions. + *

+ * + * @return the ID of this operation + **/ + String operationId() default ""; + + /** + * An array of parameters applicable for this operation. + *

+ * The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and + * location. + *

+ * + * @return the list of parameters for this operation + **/ + Parameter[] parameters() default {}; + + /** + * The request body for this operation. + * + * @return the request body of this operation + **/ + RequestBody requestBody() default @RequestBody(); + + /** + * The list of possible responses that can be returned when executing this operation. + * + * @return the list of responses for this operation + **/ + APIResponse[] responses() default {}; + + /** + * A list of possible out-of-band callbacks related to this operation. Each entry represents a set of requests that + * may be initiated by the API provided and an expression, evaluated at runtime, that identifies the URL used to + * make those requests. + * + * @return the list of callbacks for this operation + */ + Callback[] callbacks() default {}; + + /** + * Allows an operation to be marked as deprecated + *

+ * Consumers SHOULD refrain from usage of a deprecated operation. + *

+ * + * @return whether or not this operation is deprecated + **/ + boolean deprecated() default false; + + /** + * A declaration of which security mechanisms can be used for this callback operation. Only one of the security + * requirement objects need to be satisfied to authorize a request. + *

+ * Adding a {@code SecurityRequirement} to this array is equivalent to adding a {@code SecurityRequirementsSet} + * containing a single {@code SecurityRequirement} to {@link #securitySets()}. + *

+ * This definition overrides any declared top-level security. To remove a top-level security declaration, an empty + * array can be used. + * + * @return the list of security mechanisms for this operation + */ + SecurityRequirement[] security() default {}; + + /** + * A declaration of which security mechanisms can be used for this callback operation. All of the security + * requirements within any one of the sets needs needs to be satisfied to authorize a request. + *

+ * This definition overrides any declared top-level security. To remove a top-level security declaration, an empty + * array can be used. + *

+ * Including an empty set within this list indicates that the other requirements are optional. + * + * @return the list of security mechanisms for this operation + */ + SecurityRequirementsSet[] securitySets() default {}; + + /** + * A list of servers to be used for this operation, overriding those defined for the parent path item or for the + * whole API. + * + * @return the list of servers + */ + Server[] servers() default {}; + + /** + * List of extensions to be added to the {@link org.eclipse.microprofile.openapi.models.Operation Operation} model + * corresponding to the containing annotation. + * + * @return array of extensions + */ + Extension[] extensions() default {}; +} diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/Callback.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/Callback.java index 94d3b526..9106a388 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/Callback.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/Callback.java @@ -77,6 +77,16 @@ **/ String ref() default ""; + /** + * Reference value to a Path Item object, to be referenced by the Callback object. + *

+ * This property provides a reference to a Path Item object defined elsewhere. {@code #callbackUrlExpression()} is + * REQUIRED when this property is set. The referenced Path Item will be used for the URL expression specified. + * + * @since 4.0 + */ + String pathItemRef() default ""; + /** * List of extensions to be added to the {@link org.eclipse.microprofile.openapi.models.callbacks.Callback Callback} * model corresponding to the containing annotation. diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/package-info.java index fcb926f5..4eb880e7 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/package-info.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/callbacks/package-info.java @@ -44,6 +44,6 @@ * */ -@org.osgi.annotation.versioning.Version("1.1") +@org.osgi.annotation.versioning.Version("1.2") @org.osgi.annotation.versioning.ProviderType package org.eclipse.microprofile.openapi.annotations.callbacks; \ No newline at end of file diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/package-info.java index 8963a720..6bbd7b54 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/package-info.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/package-info.java @@ -34,5 +34,5 @@ * */ -@org.osgi.annotation.versioning.Version("1.1") +@org.osgi.annotation.versioning.Version("1.2") package org.eclipse.microprofile.openapi.annotations; \ No newline at end of file From 9117ff2a3bedee698e265fb8bc95db4389d618fa Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Wed, 5 Jun 2024 17:57:45 +0100 Subject: [PATCH 8/8] Add TCKs for PathItem annotations - Define a PathItem under Components - test all parameters - Define Operations under a Path Item - test all parameters - Define a webhook - Reference a Path Item from a webhook - Reference a Path Item from a callback - Reference a Path Item from another Path Item under Components --- .../openapi/apps/airlines/JAXRSApp.java | 120 +++++++++++++++++- .../openapi/tck/AirlinesAppTest.java | 82 ++++++++++++ 2 files changed, 201 insertions(+), 1 deletion(-) diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java index e9cc8a2d..9975c627 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java @@ -20,6 +20,8 @@ import org.eclipse.microprofile.openapi.annotations.Components; import org.eclipse.microprofile.openapi.annotations.ExternalDocumentation; import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition; +import org.eclipse.microprofile.openapi.annotations.PathItem; +import org.eclipse.microprofile.openapi.annotations.PathItemOperation; import org.eclipse.microprofile.openapi.annotations.callbacks.Callback; import org.eclipse.microprofile.openapi.annotations.callbacks.CallbackOperation; import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn; @@ -60,6 +62,7 @@ import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.MediaType; +@SuppressWarnings("checkstyle:linelength") // indentation of annotations leads to long lines @ApplicationPath("/") @OpenAPIDefinition( tags = {@Tag(name = "user", description = "Operations about user"), @@ -107,6 +110,27 @@ extensions = @Extension(name = "x-server", value = "test-server")), @Server(url = "https://test-server.com:80/basePath", description = "The test API server") }, + webhooks = { + @PathItem(name = "bookingEvent", + description = "Notifies about booking creation and deletion", + summary = "Booking Events", + operations = { + @PathItemOperation(method = "put", + summary = "Notifies that a booking has been created", + requestBody = @RequestBody(content = @Content(mediaType = "application/json", + schema = @Schema(ref = "#/components/schemas/Booking"))), + responses = @APIResponse(responseCode = "204", + description = "Indicates that the creation event was processed successfully")), + @PathItemOperation(method = "delete", + summary = "Notifies that a booking has been deleted", + requestBody = @RequestBody(content = @Content(mediaType = "application/json", + schema = @Schema(ref = "#/components/schemas/Booking"))), + responses = @APIResponse(responseCode = "204", + description = "Indicates that the deletion event was processed successfully")) + }, + extensions = @Extension(name = "x-webhook", value = "test-webhook")), + @PathItem(name = "userEvent", ref = "UserEvent") + }, components = @Components( schemas = { @Schema(name = "Bookings", title = "Bookings", @@ -219,7 +243,101 @@ @APIResponse(ref = "FoundBookings") })), @Callback(name = "GetBookingsARef", - ref = "#/components/callbacks/GetBookings") + ref = "#/components/callbacks/GetBookings"), + @Callback(name = "UserEvents", + callbackUrlExpression = "http://localhost:9080/users/events", + pathItemRef = "UserEvent") + }, + pathItems = { + @PathItem(name = "UserEvent", + description = "Standard definition for receiving events about users", + summary = "User Event reception API", + operations = { + @PathItemOperation( + method = "PUT", + summary = "User added event", + description = "A user was added", + externalDocs = @ExternalDocumentation(url = "http://example.com/docs"), + operationId = "userAddedEvent", + parameters = @Parameter(name = "authenticated", + description = "Whether the user is authenticated", + in = ParameterIn.QUERY, + schema = @Schema(type = SchemaType.BOOLEAN), + required = false), + requestBody = @RequestBody( + description = "The added user", + content = @Content(mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(ref = "User"))), + responses = { + @APIResponse(responseCode = "200", + description = "Event received"), + @APIResponse(responseCode = "429", + description = "Server is too busy to process the event. It will be sent again later") + }), + @PathItemOperation( + method = "DELETE", + summary = "A user was deleted", + parameters = @Parameter(name = "id", + in = ParameterIn.QUERY, + schema = @Schema(type = SchemaType.STRING), + required = true), + responses = { + @APIResponse(responseCode = "200", + description = "Event received") + }) + }), + @PathItem(name = "UserEventARef", + ref = "#/components/pathItems/UserEvent", + description = "UserEvent reference", + summary = "Referenced PathItem", + operations = @PathItemOperation( + method = "POST", + summary = "User updated event", + description = "A user was modified", + requestBody = @RequestBody( + description = "The modified user", + content = @Content(mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(ref = "User"))), + responses = { + @APIResponse(responseCode = "200", + description = "Event received") + })), + @PathItem(name = "CallbackPathItem", + operations = @PathItemOperation( + method = "POST", + responses = @APIResponse(responseCode = "200"), + callbacks = @Callback(name = "getBookings", + ref = "#/components/callbacks/GetBookings"))), + // Test remaining properties on PathItemOperation + @PathItem(name = "OperationTest", + operations = @PathItemOperation( + method = "POST", + responses = @APIResponse(responseCode = "200"), + deprecated = true, + tags = { + @Tag(ref = "create"), + @Tag(name = "pathItemTest", + description = "part of the path item tests") + }, + security = @SecurityRequirement(name = "testScheme1"), + securitySets = @SecurityRequirementsSet({}), + servers = @Server(url = "http://old.example.com/api"), + extensions = @Extension(name = "x-operation", + value = "test operation"))), + // Test remaining properties on PathItem + @PathItem(name = "PathItemTest", + operations = { + @PathItemOperation(method = "POST", + responses = @APIResponse(responseCode = "200")), + @PathItemOperation(method = "PUT", + responses = @APIResponse(responseCode = "200")) + }, + servers = @Server(url = "http://example.com"), + parameters = @Parameter(name = "id", + in = ParameterIn.PATH, + schema = @Schema(type = SchemaType.STRING)), + extensions = @Extension(name = "x-pathItem", + value = "test path item")) }, extensions = @Extension(name = "x-components", value = "test-components")), extensions = @Extension(name = "x-openapi-definition", value = "test-openapi-definition")) diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java index 1b427048..35512762 100644 --- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/AirlinesAppTest.java @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.emptyIterable; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasEntry; @@ -472,6 +473,10 @@ public void testCallbackAnnotations(String type) { endpoint = "paths.'/bookings'.post.callbacks"; vr.body(endpoint, hasKey("bookingCallback")); vr.body(endpoint + ".'bookingCallback'", hasKey("http://localhost:9080/airlines/bookings")); + + endpoint = "components.callbacks.UserEvents"; + vr.body(endpoint, hasKey("http://localhost:9080/users/events")); + vr.body(endpoint + ".'http://localhost:9080/users/events'.$ref", equalTo("#/components/pathItems/UserEvent")); } @Test(dataProvider = "formatProvider") @@ -871,6 +876,7 @@ public void testComponents(String type) { vr.body("components.securitySchemes.httpTestScheme", notNullValue()); vr.body("components.links.UserName", notNullValue()); vr.body("components.callbacks.GetBookings", notNullValue()); + vr.body("components.pathItems.UserEvent", notNullValue()); // Test an extension on the components object itself vr.body("components.x-components", equalTo("test-components")); @@ -1186,6 +1192,82 @@ public void testRef(String type) { vr.body("components.callbacks.GetBookingsARef.$ref", equalTo("#/components/callbacks/GetBookings")); + + vr.body("components.pathItems.UserEventARef.$ref", equalTo("#/components/pathItems/UserEvent")); + vr.body("components.pathItems.UserEventARef.description", equalTo("UserEvent reference")); + vr.body("components.pathItems.UserEventARef.summary", equalTo("Referenced PathItem")); + } + + @Test(dataProvider = "formatProvider") + public void testPathItem(String type) { + ValidatableResponse vr = callEndpoint(type); + + String pathItem = "components.pathItems.UserEvent"; + vr.body(pathItem + ".description", equalTo("Standard definition for receiving events about users")); + vr.body(pathItem + ".summary", equalTo("User Event reception API")); + vr.body(pathItem + ".put", notNullValue()); + vr.body(pathItem + ".delete", notNullValue()); + + pathItem = "components.pathItems.PathItemTest"; + vr.body(pathItem + ".servers[0].url", equalTo("http://example.com")); + vr.body(pathItem + ".parameters[0].name", equalTo("id")); + vr.body(pathItem + ".x-pathItem", equalTo("test path item")); + + pathItem = "components.pathItems.UserEventARef"; + vr.body(pathItem + ".$ref", equalTo("#/components/pathItems/UserEvent")); + vr.body(pathItem + ".post", notNullValue()); + vr.body(pathItem + ".post.summary", equalTo("User updated event")); + } + + @Test(dataProvider = "formatProvider") + public void testPathItemOperation(String type) { + ValidatableResponse vr = callEndpoint(type); + + String op = "components.pathItems.UserEvent.put"; + vr.body(op, notNullValue()); + vr.body(op + ".summary", equalTo("User added event")); + vr.body(op + ".description", equalTo("A user was added")); + vr.body(op + ".externalDocs.url", equalTo("http://example.com/docs")); + vr.body(op + ".operationId", equalTo("userAddedEvent")); + vr.body(op + ".parameters[0].name", equalTo("authenticated")); + vr.body(op + ".requestBody.description", equalTo("The added user")); + vr.body(op + ".responses.'200'.description", equalTo("Event received")); + vr.body(op + ".responses.'429'.description", containsString("Server is too busy")); + + op = "components.pathItems.CallbackPathItem.post"; + vr.body(op, notNullValue()); + vr.body(op + ".callbacks.getBookings.$ref", equalTo("#/components/callbacks/GetBookings")); + + op = "components.pathItems.OperationTest.post"; + vr.body(op, notNullValue()); + vr.body(op + ".tags", containsInAnyOrder("create", "pathItemTest")); + vr.body(op + ".deprecated", equalTo(true)); + vr.body(op + ".security", hasSize(2)); + vr.body(op + ".security", hasItem(anEmptyMap())); + // JsonPath syntax sucks - this expects security to contain two items, one of which + // maps "testScheme1" to an empty list and the other of which doesn't have a "testScheme1" entry. + vr.body(op + ".security.testScheme1", containsInAnyOrder(emptyIterable(), nullValue())); + vr.body(op + ".servers[0].url", equalTo("http://old.example.com/api")); + vr.body(op + ".x-operation", equalTo("test operation")); + + // Check the new tag was created + vr.body("tags.findAll { it.name == 'pathItemTest'}.description", contains("part of the path item tests")); } + @Test(dataProvider = "formatProvider") + public void testWebhooks(String type) { + ValidatableResponse vr = callEndpoint(type); + + String webhook = "webhooks.bookingEvent"; + vr.body(webhook, notNullValue()); + vr.body(webhook + ".description", equalTo("Notifies about booking creation and deletion")); + vr.body(webhook + ".summary", equalTo("Booking Events")); + vr.body(webhook + ".put", notNullValue()); + vr.body(webhook + ".delete", notNullValue()); + vr.body(webhook + ".x-webhook", equalTo("test-webhook")); + + webhook = "webhooks.userEvent"; + vr.body(webhook, notNullValue()); + vr.body(webhook + ".$ref", equalTo("#/components/pathItems/UserEvent")); + } }