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

Support proper mapping of the byte array #3955

Closed
wants to merge 12 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,20 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
isPrimitive = true;
}
if (model == null) {
PrimitiveType primitiveType = PrimitiveType.fromType(type);
if (primitiveType != null) {
model = PrimitiveType.fromType(type).createProperty();
isPrimitive = true;
if (resolvedSchemaAnnotation != null && StringUtils.isEmpty(resolvedSchemaAnnotation.type())) {
PrimitiveType primitiveType = PrimitiveType.fromTypeAndFormat(type, resolvedSchemaAnnotation.format());
if (primitiveType != null) {
model = primitiveType.createProperty();
isPrimitive = true;
}
}

if (model == null) {
PrimitiveType primitiveType = PrimitiveType.fromType(type);
if (primitiveType != null) {
model = primitiveType.createProperty();
isPrimitive = true;
}
}
}

Expand Down Expand Up @@ -629,7 +639,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
propType = ((AnnotatedMethod)member).getParameterType(0);
}

}
}

String propSchemaName = null;
io.swagger.v3.oas.annotations.media.Schema ctxSchema = AnnotationsUtils.getSchemaAnnotation(annotations);
if (AnnotationsUtils.hasSchemaAnnotation(ctxSchema)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -41,13 +43,23 @@ public Schema createProperty() {
},
BYTE(Byte.class, "byte") {
@Override
public ByteArraySchema createProperty() {
public Schema createProperty() {
if (
(System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString())) ||
(System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString()))) {
return new StringSchema().format("byte");
}
return new ByteArraySchema();
}
},
BINARY(Byte.class, "binary") {
@Override
public BinarySchema createProperty() {
public Schema createProperty() {
if (
(System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString())) ||
(System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY) != null && System.getenv(Schema.BINARY_STRING_CONVERSION_PROPERTY).equals(Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString()))) {
return new StringSchema().format("binary");
}
return new BinarySchema();
}
},
Expand Down Expand Up @@ -149,6 +161,7 @@ public Schema createProperty() {
};

private static final Map<Class<?>, PrimitiveType> KEY_CLASSES;
private static final Map<Class<?>, Collection<PrimitiveType>> MULTI_KEY_CLASSES;
private static final Map<Class<?>, PrimitiveType> BASE_CLASSES;
/**
* Adds support of a small number of "well-known" types, specifically for
Expand Down Expand Up @@ -244,6 +257,11 @@ public Schema createProperty() {
addKeys(keyClasses, OBJECT, Object.class);
KEY_CLASSES = Collections.unmodifiableMap(keyClasses);

final Map<Class<?>, Collection<PrimitiveType>> multiKeyClasses = new HashMap<>();
addMultiKeys(multiKeyClasses, BYTE, byte[].class);
addMultiKeys(multiKeyClasses, BINARY, byte[].class);
MULTI_KEY_CLASSES = Collections.unmodifiableMap(multiKeyClasses);

final Map<Class<?>, PrimitiveType> baseClasses = new HashMap<>();
addKeys(baseClasses, DATE_TIME, java.util.Date.class, java.util.Calendar.class);
BASE_CLASSES = Collections.unmodifiableMap(baseClasses);
Expand Down Expand Up @@ -343,6 +361,20 @@ public static Set<String> nonSystemTypePackages() {
return nonSystemTypePackages;
}

public static PrimitiveType fromTypeAndFormat(Type type, String format) {
final Class<?> raw = TypeFactory.defaultInstance().constructType(type).getRawClass();
final Collection<PrimitiveType> keys = MULTI_KEY_CLASSES.get(raw);
if (keys == null || keys.isEmpty() || StringUtils.isBlank(format)) {
return fromType(type);
} else {
return keys
.stream()
.filter(t -> t.getCommonName().equalsIgnoreCase(format))
.findAny()
.orElse(null);
}
}

public static PrimitiveType fromType(Type type) {
final Class<?> raw = TypeFactory.defaultInstance().constructType(type).getRawClass();
final PrimitiveType key = KEY_CLASSES.get(raw);
Expand All @@ -351,6 +383,14 @@ public static PrimitiveType fromType(Type type) {
return key;
}
}

final Collection<PrimitiveType> keys = MULTI_KEY_CLASSES.get(raw);
if (keys != null && !keys.isEmpty()) {
final PrimitiveType first = keys.iterator().next();
if (!customExcludedClasses.contains(raw.getName())) {
return first;
}
}

final PrimitiveType custom = customClasses.get(raw.getName());
if (custom != null) {
Expand Down Expand Up @@ -424,6 +464,15 @@ private static <K> void addKeys(Map<K, PrimitiveType> map, PrimitiveType type, K
}
}

private static <K> void addMultiKeys(Map<K, Collection<PrimitiveType>> map, PrimitiveType type, K... keys) {
for (K key : keys) {
if (!map.containsKey(key)) {
map.put(key, new ArrayList<>());
}
map.get(key).add(type);
}
}

private static class DateStub {
private DateStub() {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.swagger.v3.jaxrs2;

import java.io.IOException;

import io.swagger.v3.oas.models.media.Schema;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import io.swagger.v3.jaxrs2.annotations.AbstractAnnotationTest;
import io.swagger.v3.jaxrs2.resources.BinaryParameterResource;

public class BinaryParameterResourceTest extends AbstractAnnotationTest {

@Test(description = "check binary model serialization with base64", singleThreaded = true) // tests issue #2466
public void shouldSerializeBinaryParameterBase64() throws IOException {
try {
System.setProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY, Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_BASE64.toString());
compareAsYaml(BinaryParameterResource.class, getOpenAPIAsString("BinaryParameterResource.yaml"));
} finally {
System.clearProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY);
}
}

@BeforeTest
public void before() {
System.clearProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY);
}

@AfterTest
public void after() {
System.clearProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY);
}


@Test(description = "check binary model serialization with StringSchema", singleThreaded = true) // tests issue #2466
public void shouldSerializeBinaryParameterStringSchema() throws IOException {
try {
System.setProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY, Schema.BynaryStringConversion.BINARY_STRING_CONVERSION_STRING_SCHEMA.toString());
compareAsYaml(BinaryParameterResource.class, getOpenAPIAsString("BinaryParameterResource.yaml"));
} finally {
System.clearProperty(Schema.BINARY_STRING_CONVERSION_PROPERTY);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.swagger.v3.jaxrs2.resources;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import io.swagger.v3.jaxrs2.resources.model.Item;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;

@Path("/")
public class BinaryParameterResource {
@Consumes({ MediaType.APPLICATION_JSON })
@Path("/binary")
@POST
@Operation(
summary = "Create new item",
description = "Post operation with entity in a body",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(implementation = Item.class),
mediaType = MediaType.APPLICATION_JSON
),
headers = @Header(name = "Location"),
responseCode = "201"
)
}
)
public Response createItem(@Context final UriInfo uriInfo, @Parameter(required = true) final Item item) {
return Response
.created(uriInfo.getBaseUriBuilder().path(item.getName()).build())
.entity(item).build();
Dismissed Show dismissed Hide dismissed
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.swagger.v3.jaxrs2.resources.model;

import io.swagger.v3.oas.annotations.media.Schema;

public class Item {
private String name;
private String value;
@Schema(example = "Ynl0ZQ==")
private byte[] bytes;
@Schema(format = "binary", example = "YmluYXJ5")
private byte[] binary;

private byte[] byteNoAnnotation;

public Item() {
}

public void setBinary(byte[] binary) {
this.binary = binary;
}

public byte[] getBinary() {
return binary;
}

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setValue(String value) {
this.value = value;
}

public String getValue() {
return value;
}

public void setBytes(byte[] bytes) {
this.bytes = bytes;
}

public byte[] getBytes() {
return bytes;
}

public void setByteNoAnnotation(byte[] byteNoAnnotation) {
this.byteNoAnnotation = byteNoAnnotation;
}

public byte[] getByteNoAnnotation() {
return byteNoAnnotation;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
openapi: 3.0.1
paths:
/binary:
post:
summary: Create new item
description: Post operation with entity in a body
operationId: createItem
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
required: true
responses:
"201":
headers:
Location:
style: simple
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
components:
schemas:
Item:
type: object
properties:
name:
type: string
value:
type: string
bytes:
type: string
format: byte
example: Ynl0ZQ==
binary:
type: string
format: binary
example: YmluYXJ5
byteNoAnnotation:
type: string
format: byte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.v3.oas.models.media;

import java.util.Base64;
import java.util.List;
import java.util.Objects;

Expand Down Expand Up @@ -36,6 +37,13 @@ protected byte[] cast(Object value) {
try {
if (value instanceof byte[]) {
return (byte[]) value;
} else if (value instanceof String) {
if (
(System.getProperty(BINARY_STRING_CONVERSION_PROPERTY) != null && System.getProperty(BINARY_STRING_CONVERSION_PROPERTY).equals(BynaryStringConversion.BINARY_STRING_CONVERSION_BASE64.toString())) ||
(System.getenv(BINARY_STRING_CONVERSION_PROPERTY) != null && System.getenv(BINARY_STRING_CONVERSION_PROPERTY).equals(BynaryStringConversion.BINARY_STRING_CONVERSION_BASE64.toString()))) {
return Base64.getDecoder().decode((String) value);
}
return value.toString().getBytes();
} else {
return value.toString().getBytes();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.v3.oas.models.media;

import java.util.Base64;
import java.util.List;
import java.util.Objects;

Expand Down Expand Up @@ -36,6 +37,13 @@ protected byte[] cast(Object value) {
try {
if (value instanceof byte[]) {
return (byte[]) value;
} else if (value instanceof String) {
if (
(System.getProperty(BINARY_STRING_CONVERSION_PROPERTY) != null && System.getProperty(BINARY_STRING_CONVERSION_PROPERTY).equals(BynaryStringConversion.BINARY_STRING_CONVERSION_BASE64.toString())) ||
(System.getenv(BINARY_STRING_CONVERSION_PROPERTY) != null && System.getenv(BINARY_STRING_CONVERSION_PROPERTY).equals(BynaryStringConversion.BINARY_STRING_CONVERSION_BASE64.toString()))) {
return Base64.getDecoder().decode((String) value);
}
return value.toString().getBytes();
} else {
return value.toString().getBytes();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@
public class Schema<T> {

public static final String BIND_TYPE_AND_TYPES = "bind-type";
public static final String BINARY_STRING_CONVERSION_PROPERTY = "binary-string-conversion";
public enum BynaryStringConversion {
BINARY_STRING_CONVERSION_BASE64("base64"),
BINARY_STRING_CONVERSION_DEFAULT_CHARSET("default"),
BINARY_STRING_CONVERSION_STRING_SCHEMA("string-schema");
private String value;

BynaryStringConversion(String value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}
}

protected T _default;

Expand Down
Loading