Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#lambda.jSpecifyNullable}}{{^required}}{{^defaultValue}}{{^useOptional}}{{#openApiNullable}}{{^isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/useOptional}}{{/defaultValue}}{{#defaultValue}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/defaultValue}}{{/required}}{{/lambda.jSpecifyNullable}}
{{#lambda.jSpecifyNullable}}{{^required}}{{^defaultValue}}{{^useOptional}}{{#openApiNullable}}{{^isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/useOptional}}{{/defaultValue}}{{#defaultValue}}{{! non-required + default: model properties are nullable (a caller may still pass null); request params keep Spring's non-null default binding }}{{#useJspecify}}{{^isQueryParam}}{{^isHeaderParam}}{{^isCookieParam}}{{^isPathParam}}{{^isBodyParam}}{{^isFormParam}}{{^useOptional}}{{#openApiNullable}}{{^isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/useOptional}}{{/isFormParam}}{{/isBodyParam}}{{/isPathParam}}{{/isCookieParam}}{{/isHeaderParam}}{{/isQueryParam}}{{#isQueryParam}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/isQueryParam}}{{#isHeaderParam}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/isHeaderParam}}{{#isCookieParam}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/isCookieParam}}{{#isPathParam}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/isPathParam}}{{#isBodyParam}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/isBodyParam}}{{#isFormParam}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/isFormParam}}{{/useJspecify}}{{^useJspecify}}{{^openApiNullable}}{{#isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{/useJspecify}}{{/defaultValue}}{{/required}}{{/lambda.jSpecifyNullable}}
Original file line number Diff line number Diff line change
Expand Up @@ -7084,6 +7084,41 @@ public void testJspecify(String library, int springBootVersion, String fooApiFil
}
}

@Test
public void testJspecifyNullableWithDefaultValue_issue24294() throws IOException {
// A non-required field is nullable regardless of whether it declares a default value,
// because the caller can still explicitly pass null. The @Nullable (jspecify) annotation
// must therefore be emitted even when the property has a `default`.
final Map<String, File> files = generateFromContract("src/test/resources/3_0/java/jspecify-default.yaml",
SPRING_BOOT,
Map.of(USE_JSPECIFY, true,
CONTAINER_DEFAULT_TO_NULL, true,
SpringCodegen.OPENAPI_NULLABLE, false,
USE_BEANVALIDATION, true,
INTERFACE_ONLY, false));

JavaFileAssert.assertThat(files.get("CobJob.java"))
.assertTypeAnnotations().doesImportAnnotation("org.jspecify.annotations.Nullable").toType()
.fileContains(
// non-required fields carrying a default value keep @Nullable
"private @Nullable Boolean force = false;",
"private @Nullable Integer retries = 3;",
"private @Nullable String label = \"none\";"
)
// required field must NOT be annotated with @Nullable
.fileContains("private String name;")
.fileDoesNotContain("private @Nullable String name;");

// A non-required request parameter carrying a default value must NOT be @Nullable:
// Spring's @RequestParam(defaultValue = ...) supplies a non-null value when the
// request value is absent, so annotating it @Nullable would be an incorrect contract.
// A non-required parameter without a default stays @Nullable.
JavaFileAssert.assertThat(files.get("JobApi.java"))
.assertMethod("listJobs")
.assertParameter("limit").assertParameterAnnotations().doesNotContainWithName("Nullable").toParameter().toMethod()
.assertParameter("filter").assertParameterAnnotations().containsWithName("Nullable");
}

// -------------------------------------------------------------------------
// autoXSpringPaginated tests
// -------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
openapi: 3.0.0
info:
description: test jspecify @Nullable on non-required fields that carry a default value
title: jspecify-default
version: 1.0.0
paths:
/job:
get:
operationId: listJobs
parameters:
# non-required query param WITH a default: Spring supplies the default when
# the request value is absent, so the generated param must NOT be @Nullable
- in: query
name: limit
required: false
schema:
type: integer
format: int32
default: 20
# non-required query param WITHOUT a default: nullable, keeps @Nullable
- in: query
name: filter
required: false
schema:
type: string
responses:
'200':
description: ok
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/CobJob'
post:
operationId: createJob
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CobJob'
responses:
'200':
description: ok
content:
application/json:
schema:
$ref: '#/components/schemas/CobJob'
components:
schemas:
CobJob:
type: object
required:
- name
properties:
name:
type: string
force:
type: boolean
default: false
retries:
type: integer
format: int32
default: 3
label:
type: string
default: "none"