Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Header Object missing attributes #4608

Merged
merged 13 commits into from
Feb 1, 2024
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.swagger.v3.oas.annotations.headers;

import io.swagger.v3.oas.annotations.enums.Explode;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;

import java.lang.annotation.Inherited;
Expand Down Expand Up @@ -64,4 +67,40 @@
**/
String ref() default "";


/**
* When this is true, parameter values of type array or object generate separate parameters for each value of the array or key-value pair of the map. For other types of parameters this property has no effect. When style is form, the default value is true. For all other styles, the default value is false. Ignored if the properties content or array are specified.
*
* @return whether or not to expand individual array members
**/
Explode explode() default Explode.DEFAULT;

/**
* Allows this header to be marked as hidden
*
* @return whether or not this header is hidden
*/
boolean hidden() default false;

/**
* Provides an example of the schema. When associated with a specific media type, the example string shall be parsed by the consumer to be treated as an object or an array. Ignored if the properties examples, content or array are specified.
*
* @return an example of the header
**/
String example() default "";

/**
* An array of examples of the schema used to show the use of the associated schema.
*
* @return array of examples of the header
**/
ExampleObject[] examples() default {};

/**
* The schema of the array that defines this header. Ignored if the property content is specified.
*
* @return the schema of the array
*/
ArraySchema array() default @ArraySchema();

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.converter.ResolvedSchema;
import io.swagger.v3.oas.annotations.StringToClassMapItem;
import io.swagger.v3.oas.annotations.enums.Explode;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import io.swagger.v3.oas.annotations.links.LinkParameter;
Expand All @@ -33,6 +34,7 @@
import io.swagger.v3.oas.models.media.JsonSchema;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used import?

import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.servers.ServerVariable;
import io.swagger.v3.oas.models.servers.ServerVariables;
Expand Down Expand Up @@ -1313,7 +1315,7 @@ public static Optional<Header> getHeader(io.swagger.v3.oas.annotations.headers.H

public static Optional<Header> getHeader(io.swagger.v3.oas.annotations.headers.Header header, JsonView jsonViewAnnotation, boolean openapi31) {

if (header == null) {
if (header == null || header.hidden()) {
return Optional.empty();
}

Expand All @@ -1327,13 +1329,32 @@ public static Optional<Header> getHeader(io.swagger.v3.oas.annotations.headers.H
headerObject.set$ref(header.ref());
isEmpty = false;
}
if (StringUtils.isNotBlank(header.example())) {
try {
headerObject.setExample(Json.mapper().readTree(header.example()));
} catch (IOException e) {
headerObject.setExample(header.example());
}
}
if (header.deprecated()) {
headerObject.setDeprecated(header.deprecated());
}
if (header.required()) {
headerObject.setRequired(header.required());
isEmpty = false;
}
Map<String, Example> exampleMap = new LinkedHashMap<>();
if (header.examples().length == 1 && StringUtils.isBlank(header.examples()[0].name())) {
Optional<Example> exampleOptional = AnnotationsUtils.getExample(header.examples()[0], true);
exampleOptional.ifPresent(headerObject::setExample);
} else {
for (ExampleObject exampleObject : header.examples()) {
AnnotationsUtils.getExample(exampleObject).ifPresent(example -> exampleMap.put(exampleObject.name(), example));
}
}
if (!exampleMap.isEmpty()) {
headerObject.setExamples(exampleMap);
}
headerObject.setStyle(Header.StyleEnum.SIMPLE);

if (header.schema() != null) {
Expand All @@ -1342,14 +1363,43 @@ public static Optional<Header> getHeader(io.swagger.v3.oas.annotations.headers.H
headerObject::setSchema);
}
}
if (hasArrayAnnotation(header.array())){
AnnotationsUtils.getArraySchema(header.array(), null, jsonViewAnnotation, openapi31).ifPresent(
headerObject::setSchema);
}

setHeaderExplode(headerObject, header);
if (isEmpty) {
return Optional.empty();
}

return Optional.of(headerObject);
}

public static void setHeaderExplode (Header header, io.swagger.v3.oas.annotations.headers.Header h) {
if (isHeaderExplodable(h, header)) {
if (Explode.TRUE.equals(h.explode())) {
header.setExplode(Boolean.TRUE);
} else if (Explode.FALSE.equals(h.explode())) {
header.setExplode(Boolean.FALSE);
}
}
}

private static boolean isHeaderExplodable(io.swagger.v3.oas.annotations.headers.Header h, Header header) {
io.swagger.v3.oas.annotations.media.Schema schema = h.schema();
boolean explode = true;
if (schema != null) {
Class implementation = schema.implementation();
if (implementation == Void.class) {
if (!schema.type().equals("object") && !schema.type().equals("array")) {
explode = false;
}
}
}
return explode;
}

public static void addEncodingToMediaType(MediaType mediaType, io.swagger.v3.oas.annotations.media.Encoding encoding, JsonView jsonViewAnnotation) {
addEncodingToMediaType(mediaType, encoding, jsonViewAnnotation, false);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.swagger.v3.jaxrs2.annotations.operations;

import io.swagger.v3.jaxrs2.annotations.AbstractAnnotationTest;
import io.swagger.v3.jaxrs2.petstore31.User;
import io.swagger.v3.jaxrs2.resources.GenericResponsesResource;
import io.swagger.v3.jaxrs2.resources.HiddenAnnotatedUserResource;
import io.swagger.v3.jaxrs2.resources.HiddenUserResource;
Expand All @@ -10,8 +11,10 @@
import io.swagger.v3.jaxrs2.resources.UserResource;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.Explode;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
Expand Down Expand Up @@ -359,6 +362,139 @@ static class GetOperationWithResponseMultipleHeaders {
public void simpleGet() {
}
}
@Test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a new line (applies to all added methods)

public void testOperationWithResponseArraySchema() {
String openApiYAML = readIntoYaml(GetOperationResponseHeaderWithArraySchema.class);
int start = openApiYAML.indexOf("get:");
String extractedYAML = openApiYAML.substring(start);
String expectedYAML = "get:\n" +
" summary: Simple get operation\n" +
" description: Defines a simple get operation with no inputs and a complex output\n" +
" operationId: getWithPayloadResponse\n" +
" responses:\n" +
" \"200\":\n" +
" description: voila!\n" +
" headers:\n" +
" Rate-Limit-Limit:\n" +
" description: The number of allowed requests in the current period\n" +
" style: simple\n" +
" schema:\n" +
" maxItems: 10\n" +
" minItems: 1\n" +
" type: array\n" +
" items:\n" +
" type: integer\n" +
" deprecated: true\n";
assertEquals(expectedYAML, extractedYAML);
}
static class GetOperationResponseHeaderWithArraySchema {
@Operation(
summary = "Simple get operation",
description = "Defines a simple get operation with no inputs and a complex output",
operationId = "getWithPayloadResponse",
deprecated = true,
responses = {
@ApiResponse(
responseCode = "200",
description = "voila!",
headers = {@Header(
name = "Rate-Limit-Limit",
description = "The number of allowed requests in the current period",
array = @ArraySchema(maxItems = 10, minItems = 1,schema = @Schema(type = "integer")))})})
@GET
@Path("/path")
public void simpleGet() {
}
}
static class GetOperationResponseWithoutHiddenHeader {
@Operation(
summary = "Simple get operation",
description = "Defines a simple get operation with no inputs and a complex output",
operationId = "getWithPayloadResponse",
deprecated = true,
responses = {
@ApiResponse(
responseCode = "200",
description = "voila!",
headers = {@Header(
hidden = true,
name = "Rate-Limit-Limit",
description = "The number of allowed requests in the current period",
schema = @Schema(type = "integer")),
@Header(
name = "X-Rate-Limit-Desc",
description = "The description of rate limit",
schema = @Schema(type = "string"))})
})
@GET
@Path("/path")
public void simpleGet() {
}
}
static class GetOperationWithResponseMultipleHeadersAndExamples {
@Operation(
summary = "Simple get operation",
description = "Defines a simple get operation with no inputs and a complex output",
operationId = "getWithPayloadResponse",
deprecated = true,
responses = {
@ApiResponse(
responseCode = "200",
description = "voila!",
headers = {@Header(
examples = {
@ExampleObject(
name = "ex 1",
description = "example description",
value = "example value"
),
@ExampleObject(
name = "ex 2",
description = "example description 2",
value = "example value 2"
)
},
name = "Rate-Limit-Limit",
description = "The number of allowed requests in the current period",
schema = @Schema(type = "object")),
@Header(
name = "X-Rate-Limit-Desc",
description = "The description of rate limit",
array = @ArraySchema(schema = @Schema()),
example = "example1")})

})
@GET
@Path("/path")
public void simpleGet() {
}
}
static class GetOperationResponseWithHeaderExplodeAttribute {
@Operation(
summary = "Simple get operation",
description = "Defines a simple get operation with no inputs and a complex output",
operationId = "getWithPayloadResponse",
deprecated = true,
responses = {
@ApiResponse(
responseCode = "200",
description = "voila!",
headers = {@Header(
name = "Rate-Limit-Limit",
description = "The number of allowed requests in the current period",
explode = Explode.TRUE,
schema = @Schema(type = "object")),
@Header(
name = "X-Rate-Limit-Desc",
description = "The description of rate limit",
explode = Explode.FALSE,
schema = @Schema(type = "array"))})
})
@GET
@Path("/path")
public void simpleGet() {
}
}

@Test
public void testOperationWithResponseMultipleHeaders() {
Expand Down Expand Up @@ -386,6 +522,87 @@ public void testOperationWithResponseMultipleHeaders() {
" deprecated: true\n";
assertEquals(expectedYAML, extractedYAML);
}
@Test
public void testOperationWithResponseMultipleHeadersAndExplodeAttribute() {
String openApiYAML = readIntoYaml(GetOperationResponseWithHeaderExplodeAttribute.class);
int start = openApiYAML.indexOf("get:");
String extractedYAML = openApiYAML.substring(start);
String expectedYAML = "get:\n" +
" summary: Simple get operation\n" +
" description: Defines a simple get operation with no inputs and a complex output\n" +
" operationId: getWithPayloadResponse\n" +
" responses:\n" +
" \"200\":\n" +
" description: voila!\n" +
" headers:\n" +
" X-Rate-Limit-Desc:\n" +
" description: The description of rate limit\n" +
" style: simple\n" +
" explode: false\n" +
" schema:\n" +
" type: array\n" +
" Rate-Limit-Limit:\n" +
" description: The number of allowed requests in the current period\n" +
" style: simple\n" +
" explode: true\n" +
" schema:\n" +
" type: object\n" +
" deprecated: true\n";
assertEquals(expectedYAML, extractedYAML);
}
@Test
public void testOperationResponseWithoutHiddenHeader() {
String openApiYAML = readIntoYaml(GetOperationResponseWithoutHiddenHeader.class);
int start = openApiYAML.indexOf("get:");
String extractedYAML = openApiYAML.substring(start);
String expectedYAML = "get:\n" +
" summary: Simple get operation\n" +
" description: Defines a simple get operation with no inputs and a complex output\n" +
" operationId: getWithPayloadResponse\n" +
" responses:\n" +
" \"200\":\n" +
" description: voila!\n" +
" headers:\n" +
" X-Rate-Limit-Desc:\n" +
" description: The description of rate limit\n" +
" style: simple\n" +
" schema:\n" +
" type: string\n" +
" deprecated: true\n";
assertEquals(expectedYAML, extractedYAML);
}
@Test
public void testOperationWithResponseMultipleHeadersAndExamples() {
String openApiYAML = readIntoYaml(GetOperationWithResponseMultipleHeadersAndExamples.class);
int start = openApiYAML.indexOf("get:");
String extractedYAML = openApiYAML.substring(start);
String expectedYAML = "get:\n" +
" summary: Simple get operation\n" +
" description: Defines a simple get operation with no inputs and a complex output\n" +
" operationId: getWithPayloadResponse\n" +
" responses:\n" +
" \"200\":\n" +
" description: voila!\n" +
" headers:\n" +
" X-Rate-Limit-Desc:\n" +
" description: The description of rate limit\n" +
" style: simple\n" +
" example: example1\n" +
" Rate-Limit-Limit:\n" +
" description: The number of allowed requests in the current period\n" +
" style: simple\n" +
" schema:\n" +
" type: object\n" +
" examples:\n" +
" ex 1:\n" +
" description: example description\n" +
" value: example value\n" +
" ex 2:\n" +
" description: example description 2\n" +
" value: example value 2\n" +
" deprecated: true\n";
assertEquals(expectedYAML, extractedYAML);
}

@Test(description = "reads the pet resource from sample")
public void testCompletePetResource() throws IOException {
Expand Down
Loading