feat: add Schema.NullableMode for explicit per-property nullable override (#5160)#5161
Open
thejeff77 wants to merge 2 commits intoswagger-api:masterfrom
Open
feat: add Schema.NullableMode for explicit per-property nullable override (#5160)#5161thejeff77 wants to merge 2 commits intoswagger-api:masterfrom
thejeff77 wants to merge 2 commits intoswagger-api:masterfrom
Conversation
…ride (swagger-api#5160) Mirrors the RequiredMode (PR swagger-api#4221) and AccessMode (PR swagger-api#2675) pattern, introducing NullableMode { AUTO, NULLABLE, NOT_NULLABLE } as the override mechanism for nullable. Motivation: the existing nullable() boolean defaults to false and is indistinguishable from "not specified" via reflection, so users cannot explicitly opt a property out of nullable auto-detection (e.g. from @nullable annotations added in swagger-api#5018, or downstream Kotlin T? detection in springdoc-openapi#3256). Same problem class that prompted RequiredMode in 2022 ("@NotNull but required = false"). Precedence (mirrors PR swagger-api#4533 for requiredMode): 1. nullableMode = NULLABLE / NOT_NULLABLE wins over everything 2. Legacy nullable = true (deprecated path) → treated as NULLABLE 3. nullableMode = AUTO (default) → run heuristics (@nullable etc.) Changes: - Schema.java: add NullableMode enum + nullableMode() field; deprecate nullable() pointing to nullableMode(). - ModelResolver.java: resolveNullable honors NullableMode and extracts @Schema from the annotations array when not passed directly. Helper isSchemaAnnotationNullable for downstream "is this nullable" checks (defaultValue/example "null" handling). - AnnotationsUtils.java: parity with RequiredMode in hasSchemaAnnotation, equals, and mergeSchemaAnnotations. Property-level @Schema processing honors NullableMode and explicitly removes prior nullable indications (e.g. from @nullable auto-detection) when NOT_NULLABLE is set. Tests: 9 new tests in Issue5160Test covering all combinations across OAS 3.0 and OAS 3.1. Full existing suite (660 tests) passes. Closes swagger-api#5160
…LABLE Returning Boolean.FALSE caused the caller to set schema.nullable(false) explicitly, which would emit `"nullable": false` literally in OAS 3.0 output instead of omitting the field. Returning null preserves the original method contract (only true/null) and lets AnnotationsUtils handle the actual override-and-clear semantics for NOT_NULLABLE. Adds a regression test (testModeNotNullableOverridesNullableAnnotationOAS30 now asserts assertNull strictly) plus a new test for NOT_NULLABLE without @nullable to confirm the cleanup path is harmless when there's nothing to clear.
| protected Boolean resolveNullable(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) { | ||
| if (schema != null && schema.nullable()) { | ||
| return true; | ||
| // Resolve the effective @Schema annotation - prefer the one passed directly, otherwise scan the annotations array. |
Contributor
There was a problem hiding this comment.
As far as I am aware the general idea is that an Annotation[] should never have any @Schema annotation considered, that is explicitly what the separate schema field is for.
This is especially of importance since sibling handling is managed with the annotation passed in schema.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces
Schema.NullableMode { AUTO, NULLABLE, NOT_NULLABLE }mirroring the establishedRequiredMode(#4221) andAccessMode(#2675) pattern, providing the missing explicit per-property override for nullable.Closes #5160.
Motivation
The existing
nullable()boolean defaults tofalse, which is indistinguishable from "not specified" via reflection. Users cannot opt a property out of nullable auto-detection (added in #5018 for@Nullableannotations, and used downstream by springdoc-openapi for KotlinT?reflection in springdoc/springdoc-openapi#3256, since reverted in springdoc/springdoc-openapi#3276 pending this proposal).This is the same problem class that prompted
RequiredModein 2022. From commit b1729fc: "so we can have a property annotated @NotNull but still have required = false in the openapi spec" — identical situation, justnullableinstead ofrequired.Precedence
Mirrors PR #4533 ("give precedence to requiredMode annotation"):
nullableMode = NULLABLEorNOT_NULLABLE→ use thatnullable = true(deprecated path) → treat asNULLABLEnullableMode = AUTO(default) → run heuristics (@Nullableannotations from 5001: Add support for @Nullable annotations in OpenAPI 3.1 schemas #5018, downstream Kotlin reflection, etc.)Changes
modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Schema.javaNullableModeenum (AUTO, NULLABLE, NOT_NULLABLE).nullableMode()annotation field defaulting toAUTO.nullable()boolean pointing tonullableMode().modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.javaresolveNullablehonorsNullableModeprecedence. When the @Schema annotation is not passed directly (typical for property-level resolution), it is extracted from theannotationsarray.isSchemaAnnotationNullablehelper for downstream "is this nullable" checks (used at thedefaultValue="null"andexample="null"literal handling sites, where the existingschema.nullable()checks now route through the helper to also honorNullableMode).modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.javaRequiredModehandling inhasSchemaAnnotation(NullableMode != AUTO counts as "has annotation"),equals(comparesnullableMode), andmergeSchemaAnnotations(master-wins precedence onnullableMode).@Schemaprocessing (line ~842) honorsNullableMode. WhenNOT_NULLABLEis set, explicitly clears any prior nullable indication on the schema object (e.g.nullalready added to the types array by upstream@Nullableauto-detection from 5001: Add support for @Nullable annotations in OpenAPI 3.1 schemas #5018).modules/swagger-core/src/test/java/io/swagger/v3/core/util/AnnotationsUtilsTest.javanullableMode()method override (delegating to the wrapped schema, mirroring the existingrequiredMode()delegation).Tests
modules/swagger-core/src/test/java/io/swagger/v3/core/issues/Issue5160Test.java— 9 new tests covering all combinations across OAS 3.0 and OAS 3.1:testModeNullableForcesNullable{OAS30,OAS31}—nullableMode=NULLABLEproduces nullable output.testModeNotNullableOverridesNullableAnnotation{OAS30,OAS31}—nullableMode=NOT_NULLABLEoverrides upstream@Nullableauto-detection.testModeAutoWithAnnotationStillDetectsNullable{OAS30,OAS31}—AUTO(default) lets@Nullabledetection apply unchanged.testModeAutoWithoutAnyNullableSignalIsNotNullableOAS30—AUTOwith no signals is not nullable (no regression).testLegacyNullableTrueStillWorks{OAS30,OAS31}— backward compatibility fornullable = true.Full existing suite passes (660 tests, 0 failures, 0 errors).
OAS 3.0 vs 3.1 mapping
Same as the existing handling for
nullable: true:nullable: trueon the schema object."null"to thetypearray.NOT_NULLABLEsuppresses any of the above and clears any prior nullable indication.Impact on downstream
@Nullableauto-detection added in 5001: Add support for @Nullable annotations in OpenAPI 3.1 schemas #5018 continues to work unchanged whennullableMode = AUTO.T?auto-detection (revert: auto-set nullable for Kotlin nullable types (#3256) springdoc/springdoc-openapi#3276 reverted that work pending this proposal).nullable()boolean is now@Deprecatedbut still honored, mirroring the deprecation pattern used forrequired/readOnly/writeOnly.Test plan
./mvnw -pl modules/swagger-core test -Dmaven.javadoc.skip=true→ 660/660 tests pass including 9 newIssue5160Test