AnnotationsUtilsTest.java
package io.swagger.v3.core.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.google.common.collect.ImmutableMap;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.StringToClassMapItem;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.DependentRequired;
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.media.Schema;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.io.File;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.AssertJUnit.assertNull;
public class AnnotationsUtilsTest {
@DataProvider
private Object[][] expectedSchemaFromTypes() {
return new Object[][]{
{String.class, ImmutableMap.of("type", "string")},
{Character.class, ImmutableMap.of("type", "string")},
{Boolean.class, ImmutableMap.of("type", "boolean")},
{Byte.class, ImmutableMap.of("type", "string", "format", "byte")},
{URI.class, ImmutableMap.of("type", "string", "format", "uri")},
{URL.class, ImmutableMap.of("type", "string", "format", "url")},
{UUID.class, ImmutableMap.of("type", "string", "format", "uuid")},
{Short.class, ImmutableMap.of("type", "integer", "format", "int32")},
{Integer.class, ImmutableMap.of("type", "integer", "format", "int32")},
{Long.class, ImmutableMap.of("type", "integer", "format", "int64")},
{Float.class, ImmutableMap.of("type", "number", "format", "float")},
{Double.class, ImmutableMap.of("type", "number", "format", "double")},
{BigInteger.class, ImmutableMap.of("type", "integer")},
{BigDecimal.class, ImmutableMap.of("type", "number")},
{Number.class, ImmutableMap.of("type", "number")},
{Date.class, ImmutableMap.of("type", "string", "format", "date-time")},
{File.class, ImmutableMap.of("type", "string", "format", "binary")},
{Object.class, ImmutableMap.of("type", "object")},
{DummyClass.class, ImmutableMap.of("$ref", "#/components/schemas/DummyClass")}
};
}
@Test(dataProvider = "expectedSchemaFromTypes")
public void resolveSchemaFromType(Class<?> aClass, Map<String, Object> expected) {
Schema schema = AnnotationsUtils.resolveSchemaFromType(aClass, new Components(), null);
assertEquals(schema.getType(), expected.get("type"));
assertEquals(schema.getFormat(), expected.get("format"));
assertEquals(schema.get$ref(), expected.get("$ref"));
}
@DataProvider
private Object[][] expectedSchemaFromTypeAndFormat() {
return new Object[][]{
{"byteType", ImmutableMap.of("type", "string", "format", "byte")},
{"binaryType", ImmutableMap.of("type", "string", "format", "binary")},
{"emailType", ImmutableMap.of("type", "string", "format", "email")},
{"dummyType", ImmutableMap.of("$ref", "#/components/schemas/DummyClass")}
};
}
@Test(dataProvider = "expectedSchemaFromTypeAndFormat")
public void getSchema(String methodName, Map<String, Object> expected) throws NoSuchMethodException {
final Method method = getClass().getDeclaredMethod(methodName);
Content annotationContent = method.getAnnotation(ApiResponse.class).content()[0];
Optional<? extends Schema> schema = AnnotationsUtils.getSchema(annotationContent, new Components(), null, false);
assertTrue(schema.isPresent());
assertEquals(schema.get().getType(), expected.get("type"));
assertEquals(schema.get().getFormat(), expected.get("format"));
assertEquals(schema.get().get$ref(), expected.get("$ref"));
}
@ApiResponse(content = @Content(schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = Byte.class)))
private void byteType() {
}
@ApiResponse(content = @Content(schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = Byte.class, format = "binary")))
private void binaryType() {
}
@ApiResponse(content = @Content(schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = String.class, format = "email")))
private void emailType() {
}
@ApiResponse(content = @Content(schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = DummyClass.class)))
private void dummyType() {
}
class DummyClass implements Serializable {}
static class ExampleHolder {
@io.swagger.v3.oas.annotations.media.Schema(type = "string", example = "5 lacs per annum")
String value;
@io.swagger.v3.oas.annotations.media.Schema(type = "number", example = "10")
String numberValue;
@io.swagger.v3.oas.annotations.media.Schema(type = "integer", example = "5")
String integerValue;
@io.swagger.v3.oas.annotations.media.Schema(example = "10.00")
BigDecimal bigDecimalValue;
@io.swagger.v3.oas.annotations.media.Schema(example = "42", type = "string")
String stringWith42;
@io.swagger.v3.oas.annotations.media.Schema(defaultValue = "10.00")
BigDecimal bigDecimalDefault;
@io.swagger.v3.oas.annotations.media.Schema(defaultValue = "42", type = "string")
String stringDefaultWith42;
@io.swagger.v3.oas.annotations.media.Schema(examples = {"10.00", "20.50"})
BigDecimal bigDecimalExamples;
@io.swagger.v3.oas.annotations.media.Schema(examples = {"42"}, type = "string")
String stringExamplesWith42;
@io.swagger.v3.oas.annotations.media.Schema(example = "true")
boolean aBoolean;
@io.swagger.v3.oas.annotations.media.Schema(example = "true", type = "boolean")
String stringWithBooleanType;
@io.swagger.v3.oas.annotations.media.Schema(example = "true", type = "string")
String stringWithBooleanExample;
}
@Test
public void testExampleStartingWithNumberShouldBeString() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("value")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertEquals(schema.get().getExample(), "5 lacs per annum");
}
@Test
public void testExampleWithNumberTypeShouldHaveExampleAsNumber() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("numberValue")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertEquals(schema.get().getExample(), IntNode.valueOf(10));
}
@Test
public void testExampleWithIntegerTypeShouldHaveExampleAsNumber() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("integerValue")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertEquals(schema.get().getExample(), IntNode.valueOf(5));
}
@Test
public void testExampleBooleanShouldBeNode() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("aBoolean")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertEquals(schema.get().getExample(), BooleanNode.getTrue());
}
@Test
public void testExampleStringWithBooleanTypeShouldBeNode() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("stringWithBooleanType")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertEquals(schema.get().getExample(), BooleanNode.getTrue());
}
@Test
public void testExampleStringWithBooleanShouldBeString() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("stringWithBooleanExample")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertEquals(schema.get().getExample(), "true");
}
static class DefaultHolder {
@io.swagger.v3.oas.annotations.media.Schema(type = "string")
String noDefault;
@io.swagger.v3.oas.annotations.media.Schema(type = "string", defaultValue = "null")
String nullDefaultValue;
@io.swagger.v3.oas.annotations.media.Schema(type = "string", nullable = true, defaultValue = "null")
String nullDefaultNullableValue;
}
@Test
public void testNoDefaultSet() throws NoSuchFieldException {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
DefaultHolder.class
.getDeclaredField("noDefault")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertFalse(schema.get().getDefaultSetFlag());
assertNull(schema.get().getDefault());
}
@Test
public void testSetDefaultNullWithoutNullable() throws NoSuchFieldException {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
DefaultHolder.class
.getDeclaredField("nullDefaultValue")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertTrue(schema.get().getDefaultSetFlag());
assertEquals(schema.get().getDefault(), "null");
}
@Test
public void testSetDefaultNullWithNullable() throws NoSuchFieldException {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
DefaultHolder.class
.getDeclaredField("nullDefaultNullableValue")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertTrue(schema.get().getDefaultSetFlag());
assertNull(schema.get().getDefault());
}
@Test
public void testOverrideSoGetDefaultValueReturnsNull() throws NoSuchFieldException {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
DefaultHolder.class
.getDeclaredField("noDefault")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema nullableDefaultSchema = new io.swagger.v3.oas.annotations.media.Schema() {
@Override
public Class<? extends Annotation> annotationType() {
return schemaAnnotation.annotationType();
}
@Override
public Class<?> implementation() {
return schemaAnnotation.implementation();
}
@Override
public Class<?> not() {
return schemaAnnotation.not();
}
@Override
public Class<?>[] oneOf() {
return schemaAnnotation.oneOf();
}
@Override
public Class<?>[] anyOf() {
return schemaAnnotation.anyOf();
}
@Override
public Class<?>[] allOf() {
return schemaAnnotation.allOf();
}
@Override
public String name() {
return schemaAnnotation.name();
}
@Override
public String title() {
return schemaAnnotation.title();
}
@Override
public double multipleOf() {
return schemaAnnotation.multipleOf();
}
@Override
public String maximum() {
return schemaAnnotation.maximum();
}
@Override
public boolean exclusiveMaximum() {
return schemaAnnotation.exclusiveMaximum();
}
@Override
public String minimum() {
return schemaAnnotation.minimum();
}
@Override
public boolean exclusiveMinimum() {
return schemaAnnotation.exclusiveMinimum();
}
@Override
public int maxLength() {
return schemaAnnotation.maxLength();
}
@Override
public int minLength() {
return schemaAnnotation.minLength();
}
@Override
public String pattern() {
return schemaAnnotation.pattern();
}
@Override
public int maxProperties() {
return schemaAnnotation.maxProperties();
}
@Override
public int minProperties() {
return schemaAnnotation.minProperties();
}
@Override
public String[] requiredProperties() {
return schemaAnnotation.requiredProperties();
}
@Override
public boolean required() {
return schemaAnnotation.required();
}
@Override
public RequiredMode requiredMode() {
return schemaAnnotation.requiredMode();
}
@Override
public String description() {
return schemaAnnotation.description();
}
@Override
public String format() {
return schemaAnnotation.format();
}
@Override
public String ref() {
return schemaAnnotation.ref();
}
@Override
public boolean nullable() {
return schemaAnnotation.nullable();
}
@Override
public boolean readOnly() {
return schemaAnnotation.readOnly();
}
@Override
public boolean writeOnly() {
return schemaAnnotation.writeOnly();
}
@Override
public AccessMode accessMode() {
return schemaAnnotation.accessMode();
}
@Override
public String example() {
return schemaAnnotation.example();
}
@Override
public ExternalDocumentation externalDocs() {
return schemaAnnotation.externalDocs();
}
@Override
public boolean deprecated() {
return schemaAnnotation.deprecated();
}
@Override
public String type() {
return schemaAnnotation.type();
}
@Override
public String[] allowableValues() {
return schemaAnnotation.allowableValues();
}
@Override
public String defaultValue() {
return null;
}
@Override
public String discriminatorProperty() {
return schemaAnnotation.discriminatorProperty();
}
@Override
public DiscriminatorMapping[] discriminatorMapping() {
return schemaAnnotation.discriminatorMapping();
}
@Override
public boolean hidden() {
return schemaAnnotation.hidden();
}
@Override
public boolean enumAsRef() {
return schemaAnnotation.enumAsRef();
}
@Override
public Class<?>[] subTypes() {
return schemaAnnotation.subTypes();
}
@Override
public Extension[] extensions() {
return schemaAnnotation.extensions();
}
@Override
public Class<?>[] prefixItems() {
return schemaAnnotation.prefixItems();
}
@Override
public String[] types() {
return schemaAnnotation.types();
}
@Override
public int exclusiveMaximumValue() {
return schemaAnnotation.exclusiveMaximumValue();
}
@Override
public int exclusiveMinimumValue() {
return schemaAnnotation.exclusiveMinimumValue();
}
@Override
public Class<?> contains() {
return schemaAnnotation.contains();
}
@Override
public String $id() {
return schemaAnnotation.$id();
}
@Override
public String $schema() {
return schemaAnnotation.$schema();
}
@Override
public String $anchor() {
return schemaAnnotation.$anchor();
}
@Override
public String $vocabulary() {
return schemaAnnotation.$vocabulary();
}
@Override
public String $dynamicAnchor() {
return schemaAnnotation.$dynamicAnchor();
}
@Override
public String $dynamicRef() {
return schemaAnnotation.$dynamicRef();
}
@Override
public String contentEncoding() {
return schemaAnnotation.contentEncoding();
}
@Override
public String contentMediaType() {
return schemaAnnotation.contentMediaType();
}
@Override
public Class<?> contentSchema() {
return schemaAnnotation.contentSchema();
}
@Override
public Class<?> propertyNames() {
return schemaAnnotation.propertyNames();
}
@Override
public int maxContains() {
return schemaAnnotation.maxContains();
}
@Override
public int minContains() {
return schemaAnnotation.minContains();
}
@Override
public Class<?> additionalItems() {
return schemaAnnotation.additionalItems();
}
@Override
public Class<?> unevaluatedItems() {
return schemaAnnotation.unevaluatedItems();
}
@Override
public Class<?> _if() {
return schemaAnnotation._if();
}
@Override
public Class<?> _else() {
return schemaAnnotation._else();
}
@Override
public Class<?> then() {
return schemaAnnotation.then();
}
@Override
public String $comment() {
return schemaAnnotation.$comment();
}
@Override
public Class<?>[] exampleClasses() {
return schemaAnnotation.exampleClasses();
}
@Override
public AdditionalPropertiesValue additionalProperties() {
return schemaAnnotation.additionalProperties();
}
@Override
public DependentRequired[] dependentRequiredMap() {
return schemaAnnotation.dependentRequiredMap();
}
@Override
public StringToClassMapItem[] dependentSchemas() {
return schemaAnnotation.dependentSchemas();
}
@Override
public StringToClassMapItem[] patternProperties() {
return schemaAnnotation.patternProperties();
}
@Override
public StringToClassMapItem[] properties() {
return schemaAnnotation.properties();
}
@Override
public Class<?> unevaluatedProperties() {
return schemaAnnotation.unevaluatedProperties();
}
@Override
public Class<?> additionalPropertiesSchema() {
return schemaAnnotation.additionalPropertiesSchema();
}
@Override
public String[] examples() {
return schemaAnnotation.examples();
}
@Override
public String _const() {
return schemaAnnotation._const();
}
@Override
public SchemaResolution schemaResolution() {
return schemaAnnotation.schemaResolution();
}
};
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
nullableDefaultSchema,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
assertFalse(schema.get().getDefaultSetFlag());
assertNull(schema.get().getDefault());
}
@Test
public void testExampleOnBigDecimalFieldWithoutExplicitTypeShouldBeNumber() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("bigDecimalValue")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
Object example = schema.get().getExample();
assertTrue(example instanceof JsonNode, "Example should be a JsonNode, not a plain String");
assertTrue(((JsonNode) example).isNumber(), "Example should be a numeric node for a BigDecimal field");
}
@Test
public void testExampleWithExplicitStringTypeShouldNotProduceNumericNode() throws Exception {
// Regression guard for #4999: explicit type="string" must never yield a numeric node
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("stringWith42")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
Object example = schema.get().getExample();
assertFalse(example instanceof JsonNode && ((JsonNode) example).isNumber(),
"Example must not be a numeric node when type is explicitly 'string'");
assertEquals(example, "42", "Example should be the string \"42\"");
}
@Test
public void testDefaultWithExplicitStringTypeShouldNotProduceNumericNode() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("stringDefaultWith42")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
Optional<Schema> schema =
AnnotationsUtils.getSchemaFromAnnotation(
schemaAnnotation,
null,
null,
false,
null,
Schema.SchemaResolution.DEFAULT,
null
);
assertTrue(schema.isPresent());
Object defaultValue = schema.get().getDefault();
assertFalse(defaultValue instanceof JsonNode && ((JsonNode) defaultValue).isNumber(),
"Default must not be a numeric node when type is explicitly 'string'");
assertEquals(defaultValue, "42", "Default should be the string \"42\"");
}
@Test
public void testExamplesArrayWithExplicitStringTypeShouldNotProduceNumericNodes31() throws Exception {
io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
ExampleHolder.class
.getDeclaredField("stringExamplesWith42")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
List<Object> examples = AnnotationsUtils.parseExamplesArray(schemaAnnotation);
assertEquals(examples.size(), 1);
assertFalse(examples.get(0) instanceof JsonNode && ((JsonNode) examples.get(0)).isNumber(),
"Example must not be a numeric JsonNode");
assertEquals(examples.get(0), "42", "Example should be the string \"42\"");
}
// --- mergeSchemaAnnotations defaultValue tests ---
@io.swagger.v3.oas.annotations.media.Schema(description = "type-level description")
static class TypeWithDescriptionOnly {}
static class FieldHolderWithDefault {
@io.swagger.v3.oas.annotations.media.Schema(defaultValue = "myDefault")
TypeWithDescriptionOnly field;
}
static class FieldHolderWithEmptyDefault {
@io.swagger.v3.oas.annotations.media.Schema(defaultValue = "")
TypeWithDescriptionOnly field;
}
@io.swagger.v3.oas.annotations.media.Schema(defaultValue = "typeDefault")
static class TypeWithDefault {}
static class FieldHolderWithTypeDefault {
@io.swagger.v3.oas.annotations.media.Schema(description = "field-level description")
TypeWithDefault field;
}
@Test
public void mergedDefaultValueShouldUsePatchWhenMasterHasSentinel() throws Exception {
io.swagger.v3.oas.annotations.media.Schema master =
TypeWithDescriptionOnly.class.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema patch =
FieldHolderWithDefault.class.getDeclaredField("field")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema merged =
AnnotationsUtils.mergeSchemaAnnotations(master, patch);
assertEquals(merged.defaultValue(), "myDefault",
"When master has no defaultValue (sentinel) and patch has one, the patch value should win");
}
@Test
public void mergedDefaultValueShouldUseMasterWhenMasterHasRealValue() throws Exception {
io.swagger.v3.oas.annotations.media.Schema master =
TypeWithDefault.class.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema patch =
FieldHolderWithDefault.class.getDeclaredField("field")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema merged =
AnnotationsUtils.mergeSchemaAnnotations(master, patch);
assertEquals(merged.defaultValue(), "typeDefault",
"When master has a real defaultValue, it should take precedence over patch");
}
@Test
public void mergedDefaultValueShouldReturnSentinelWhenBothHaveSentinel() throws Exception {
io.swagger.v3.oas.annotations.media.Schema master =
TypeWithDescriptionOnly.class.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema patch =
FieldHolderWithTypeDefault.class.getDeclaredField("field")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema merged =
AnnotationsUtils.mergeSchemaAnnotations(master, patch);
assertEquals(merged.defaultValue(), "",
"When both master and patch are empty, the merged result should also be empty");
}
@Test
public void mergedDefaultValueShouldUseEmptyStringFromPatch() throws Exception {
io.swagger.v3.oas.annotations.media.Schema master =
TypeWithDescriptionOnly.class.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema patch =
FieldHolderWithEmptyDefault.class.getDeclaredField("field")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema merged =
AnnotationsUtils.mergeSchemaAnnotations(master, patch);
assertEquals(merged.defaultValue(), "",
"When master has sentinel and patch has empty string, empty string should win");
}
@Test
public void sentinelShouldNeverAppearInResolvedSchema() throws Exception {
io.swagger.v3.oas.annotations.media.Schema master =
TypeWithDescriptionOnly.class.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema patch =
FieldHolderWithDefault.class.getDeclaredField("field")
.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
io.swagger.v3.oas.annotations.media.Schema merged =
AnnotationsUtils.mergeSchemaAnnotations(master, patch);
assertNotEquals(merged.defaultValue(), "",
"Sentinel should never be the result when patch has a real defaultValue");
Optional<Schema> resolvedSchema = AnnotationsUtils.getSchemaFromAnnotation(
merged, null, null, false, null, Schema.SchemaResolution.DEFAULT, null);
assertTrue(resolvedSchema.isPresent());
assertNotEquals(resolvedSchema.get().getDefault(), "",
"Sentinel value must never appear in resolved schema default");
}
@DataProvider
private Object[][] expectedEnumSchema() {
return new Object[][]{
{"emptyImplementationSchemaEnum", ImmutableMap.of("type", "string", "enum", Arrays.asList("foo", "bar"))},
{"stringImplementationSchemaEnum", ImmutableMap.of("type", "string", "enum", Arrays.asList("foo", "bar"))},
{"enumSchema", ImmutableMap.of("type", "string", "enum", Arrays.asList("FOO", "BAR"))},
};
}
@Test(dataProvider = "expectedEnumSchema")
public void testEnumSchemaFromAllowableValues(String methodName, Map<String, Object> expected) throws NoSuchMethodException {
final Method method = getClass().getDeclaredMethod(methodName);
Content annotationContent = method.getAnnotation(ApiResponse.class).content()[0];
Optional<? extends Schema> schema = AnnotationsUtils.getSchema(annotationContent, new Components(), null, false);
assertTrue(schema.isPresent());
assertEquals(schema.get().getType(), expected.get("type"));
assertEquals(schema.get().getEnum(), expected.get("enum"));
}
@ApiResponse(content = @Content(schema = @io.swagger.v3.oas.annotations.media.Schema(allowableValues = {"foo", "bar"})))
private void emptyImplementationSchemaEnum() {
}
@ApiResponse(content = @Content(schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = String.class, allowableValues = {"foo", "bar"})))
private void stringImplementationSchemaEnum() {
}
@ApiResponse(content = @Content(schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = Foo.class)))
private void enumSchema() {
}
enum Foo {
FOO, BAR;
}
}