Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,18 @@
import java.lang.annotation.Target;

import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

/**
* Describes a single header object
* Describes a single header object.
* <p>
* Note that although the {@linkplain #org.eclipse.microprofile.openapi.models.headers.Header header} model contains a
* {@link org.eclipse.microprofile.openapi.models.headers.Header#getContent() content} property, modeling that
* information using annotations is not possible because the content annotation's
* {@link org.eclipse.microprofile.openapi.annotations.media.Content#encoding() encoding} references this annotation, a
* cycle disallowed by the Java language.
* </p>
*
* @see <a href= "https://spec.openapis.org/oas/v3.1.0.html#header-object">OpenAPI Specification Header Object</a>
**/
Expand Down Expand Up @@ -102,4 +110,32 @@
* @since 3.1
*/
Extension[] extensions() default {};

/**
* Example of the header's potential value. The example SHOULD match the specified schema and encoding properties if
* present. The {@code example} field is mutually exclusive of the {@link #examples() examples} field. Furthermore,
* if referencing a {@link #schema() schema} that contains an example, the {@code example} value SHALL override the
* example provided by the schema.
* <p>
* If the media type associated with the example allows parsing into an object, it may be converted from a string.
* </p>
*
* @return an example of the header
*
* @since 4.2
**/
String example() default "";

/**
* Examples of the header's potential value. Each example SHOULD contain a value in the correct format as specified
* in the parameter encoding. The {@code examples} field is mutually exclusive of the {@link #example() example}
* field. Furthermore, if referencing a schema that contains an example, the {@code examples} value SHALL override
* the example provided by the schema.
*
* @return the list of examples for this header
*
* @since 4.2
**/
ExampleObject[] examples() default {};

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
* </pre>
*/

@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.headers;
package org.eclipse.microprofile.openapi.annotations.headers;
10 changes: 10 additions & 0 deletions spec/src/main/asciidoc/release_notes.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
[[release_notes]]
== Release Notes

[[release_notes_42]]
=== Release Notes for MicroProfile OpenAPI 4.2

A full list of changes delivered in the 4.2 release can be found at link:https://github.com/microprofile/microprofile-open-api/milestone/14?closed=1[MicroProfile OpenAPI 4.2 Milestone]

[[api_changes_42]]
==== API/SPI changes

* Add `example` and `examples` to `@Header` and verify implementation support in TCK (https://github.com/microprofile/microprofile-open-api/issues/697)[697])

[[release_notes_41]]
=== Release Notes for MicroProfile OpenAPI 4.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,18 @@ public Response updateUser(

@PATCH
@Path("/username/{username}")
@APIResponse(responseCode = "200", description = "Password was changed successfully")
@APIResponse(responseCode = "200", description = "Password was changed successfully", headers = {
@Header(name = "X-Password-Strength", description = "Rating of the strength of the new password value.",
schema = @Schema(type = SchemaType.NUMBER), examples = {
@ExampleObject(name = "strong", summary = "Password is strong", value = "10"),
@ExampleObject(name = "adequate", summary = "Password is adequate", value = "8.5"),
@ExampleObject(name = "weak", summary = "Password is weak", value = "5.1"),
})
})
@APIResponse(responseCode = "400", description = "New password is too weak", headers = {
@Header(name = "X-Password-Strength", description = "Rating of the strength of the new password value.",
schema = @Schema(type = SchemaType.NUMBER), example = "0")
})
@Operation(summary = "Change user password", description = "This changes the password for the logged in user.",
operationId = "changePassword")
@SecurityRequirementsSet(value = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
Expand Down Expand Up @@ -312,10 +313,10 @@ public void testAPIResponse(String type) {
vr.body("paths.'/user/username/{username}'.delete.responses.'404'.description", equalTo("User not found"));

// @APIResponse at class level combined with method level
vr.body("paths.'/user/username/{username}'.patch.responses", aMapWithSize(2));
vr.body("paths.'/user/username/{username}'.patch.responses.'200'.description",
equalTo("Password was changed successfully"));
vr.body("paths.'/user/username/{username}'.patch.responses.'400'.description", equalTo("Invalid request"));
vr.body("paths.'/user/logout'.get.responses", aMapWithSize(2));
vr.body("paths.'/user/logout'.get.responses.'200'.description",
equalTo("Successful user logout."));
vr.body("paths.'/user/logout'.get.responses.'400'.description", equalTo("Invalid request"));
}

@Test(dataProvider = "formatProvider")
Expand Down Expand Up @@ -832,6 +833,27 @@ public void testExampleObject(String type) {
equalTo("bsmith"));
}

@Test(dataProvider = "formatProvider")
public void testExamplesInHeaders(String type) {
ValidatableResponse vr = callEndpoint(type);

// Multiple examples in Header
vr.body("paths.'/user/username/{username}'.patch.responses.'200'.headers.'X-Password-Strength'",
hasKey("examples"));

// Implementations MAY parse the example to the data type of the schema, so here we leniently test
// the value as a string using String#valueOf (via hasToString).

vr.body("paths.'/user/username/{username}'.patch.responses.'200'.headers.'X-Password-Strength'.examples", allOf(
hasEntry(equalTo("strong"), hasEntry(equalTo("value"), hasToString("10"))),
hasEntry(equalTo("adequate"), hasEntry(equalTo("value"), hasToString("8.5"))),
hasEntry(equalTo("weak"), hasEntry(equalTo("value"), hasToString("5.1")))));

// Single example in header
vr.body("paths.'/user/username/{username}'.patch.responses.'400'.headers.'X-Password-Strength'",
hasEntry(equalTo("example"), hasToString("0")));
}

@Test(dataProvider = "formatProvider")
public void testContentExampleAttribute(String type) {
ValidatableResponse vr = callEndpoint(type);
Expand Down