Issue5001Test.java
package io.swagger.v3.core.issues;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverterContextImpl;
import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.converter.ResolvedSchema;
import io.swagger.v3.core.jackson.ModelResolver;
import io.swagger.v3.core.util.Configuration;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Json31;
import io.swagger.v3.oas.annotations.media.Schema;
import org.testng.annotations.Test;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Set;
import static org.testng.Assert.*;
/**
* Reproduces GitHub Issue #5001
* Native support for @Nullable annotations to generate proper nullable types
*
* <p>Tests that @Nullable annotation is recognized and generates appropriate nullable output:
* - OAS 3.0: nullable keyword
* - OAS 3.1: type array with "null"
*
* <p>Note: This test uses javax.annotation.Nullable which is automatically transformed to
* jakarta.annotation.Nullable in the swagger-core-jakarta module via the Eclipse Transformer.
*
* @see <a href="https://github.com/swagger-api/swagger-core/issues/5001">...</a>
*/
public class Issue5001Test {
/**
* Tests @Nullable annotation with OAS 3.1 (type array output)
*/
@Test
public void testNullableWithOAS31() {
final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
Configuration configuration = new Configuration();
configuration.setOpenAPI31(true);
modelResolver.setConfiguration(configuration);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
final io.swagger.v3.oas.models.media.Schema model = context
.resolve(new AnnotatedType(NullableModel.class));
assertNotNull(model);
assertNotNull(model.getProperties());
// Field with @Nullable should generate type array ["string", "null"]
io.swagger.v3.oas.models.media.Schema nullableField =
(io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableString");
assertNotNull(nullableField, "nullableString property should exist");
assertNotNull(nullableField.getTypes(), "@Nullable should generate types array in OAS 3.1");
assertTrue(nullableField.getTypes().contains("string"), "types should include 'string'");
assertTrue(nullableField.getTypes().contains("null"), "types should include 'null'");
assertEquals(((Set<?>) nullableField.getTypes()).size(), 2, "Should have exactly 2 types");
assertNull(nullableField.getNullable(),
"OAS 3.1: @Nullable should NOT set nullable property - only use types array. " +
"Setting nullable property is a spec violation in OpenAPI 3.1");
// Non-nullable field should only have string type
io.swagger.v3.oas.models.media.Schema requiredField =
(io.swagger.v3.oas.models.media.Schema) model.getProperties().get("requiredString");
assertNotNull(requiredField);
assertNotNull(requiredField.getTypes());
assertTrue(requiredField.getTypes().contains("string"));
assertFalse(requiredField.getTypes().contains("null"), "Non-nullable field should not have 'null' type");
}
/**
* Tests @Nullable annotation with OAS 3.0 (nullable keyword output)
*/
@Test
public void testNullableWithOAS30() {
final ModelResolver modelResolver = new ModelResolver(Json.mapper());
Configuration configuration = new Configuration();
configuration.setOpenAPI31(false);
modelResolver.setConfiguration(configuration);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
final io.swagger.v3.oas.models.media.Schema model = context
.resolve(new AnnotatedType(NullableModel.class));
assertNotNull(model);
assertNotNull(model.getProperties());
// Field with @Nullable should set nullable=true in OAS 3.0
io.swagger.v3.oas.models.media.Schema nullableField =
(io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableString");
assertNotNull(nullableField, "nullableString property should exist");
assertEquals(nullableField.getNullable(), Boolean.TRUE, "@Nullable should set nullable=true in OAS 3.0");
assertEquals(nullableField.getType(), "string", "type should be 'string'");
// Non-nullable field should not have nullable property
io.swagger.v3.oas.models.media.Schema requiredField =
(io.swagger.v3.oas.models.media.Schema) model.getProperties().get("requiredString");
assertNotNull(requiredField);
assertNotEquals(requiredField.getNullable(), Boolean.TRUE, "Non-nullable field should not be nullable");
}
/**
* Tests explicit @Schema annotations with OAS 3.1
*/
@Test
public void testExplicitSchemaAnnotationsWithOAS31() {
final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
Configuration configuration = new Configuration();
configuration.setOpenAPI31(true);
modelResolver.setConfiguration(configuration);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
final io.swagger.v3.oas.models.media.Schema model = context
.resolve(new AnnotatedType(ExplicitSchemaModel.class));
assertNotNull(model);
assertNotNull(model.getProperties());
// @Schema(nullable=true) should generate types array but not set nullable property in OAS 3.1
io.swagger.v3.oas.models.media.Schema explicitNullable =
(io.swagger.v3.oas.models.media.Schema) model.getProperties().get("explicitNullableString");
assertNotNull(explicitNullable);
assertTrue(explicitNullable.getTypes().contains("string"));
assertTrue(explicitNullable.getTypes().contains("null"));
assertNull(explicitNullable.getNullable(),
"OAS 3.1 SPEC VIOLATION: @Schema(nullable=true) should NOT set nullable property - " +
"only use types array. The nullable keyword doesn't exist in OpenAPI 3.1 spec");
// @Schema(types={"string", "null"}) should work
io.swagger.v3.oas.models.media.Schema explicitTypes =
(io.swagger.v3.oas.models.media.Schema) model.getProperties().get("explicitTypesString");
assertNotNull(explicitTypes);
assertNotNull(explicitTypes.getTypes());
assertTrue(explicitTypes.getTypes().contains("string"));
assertTrue(explicitTypes.getTypes().contains("null"));
}
/**
* Model using @Nullable annotation
* Note: Uses javax.annotation.Nullable which gets transformed to jakarta.annotation.Nullable
* in the swagger-core-jakarta module
*/
public static class NullableModel {
@Nullable
private String nullableString;
private String requiredString;
public String getNullableString() {
return nullableString;
}
public void setNullableString(String nullableString) {
this.nullableString = nullableString;
}
public String getRequiredString() {
return requiredString;
}
public void setRequiredString(String requiredString) {
this.requiredString = requiredString;
}
}
/**
* Model using explicit @Schema annotations
*/
public static class ExplicitSchemaModel {
@Schema(nullable = true)
private String explicitNullableString;
@Schema(types = {"string", "null"})
private String explicitTypesString;
public String getExplicitNullableString() {
return explicitNullableString;
}
public void setExplicitNullableString(String explicitNullableString) {
this.explicitNullableString = explicitNullableString;
}
public String getExplicitTypesString() {
return explicitTypesString;
}
public void setExplicitTypesString(String explicitTypesString) {
this.explicitTypesString = explicitTypesString;
}
}
}