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,4 +1,4 @@
@JsonProperty("{{baseName}}")
@JsonProperty({{#isReadOnly}}value = {{/isReadOnly}}"{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This template change alters generated output for every readOnly property in existing Spring samples, but no samples were regenerated. The committed samples (e.g. samples/client/petstore/spring-http-interface/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java, which currently emits @JsonProperty("bar") for its readOnly fields) will now produce @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY), so the sample-verification CI will fail. Regenerate the affected Spring samples (./bin/generate-samples.sh for the spring configs) as part of this PR.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/JavaSpring/jackson_annotations.mustache, line 1:

<comment>This template change alters generated output for every readOnly property in existing Spring samples, but no samples were regenerated. The committed samples (e.g. samples/client/petstore/spring-http-interface/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java, which currently emits `@JsonProperty("bar")` for its readOnly fields) will now produce `@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)`, so the sample-verification CI will fail. Regenerate the affected Spring samples (./bin/generate-samples.sh for the spring configs) as part of this PR.</comment>

<file context>
@@ -1,4 +1,4 @@
-  @JsonProperty("{{baseName}}")
+  @JsonProperty({{#isReadOnly}}value = {{/isReadOnly}}"{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}})
 {{#withXml}}
   @JacksonXmlProperty(localName = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#isXmlAttribute}}, isAttribute = true{{/isXmlAttribute}}{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}})
</file context>

{{#withXml}}
@JacksonXmlProperty(localName = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#isXmlAttribute}}, isAttribute = true{{/isXmlAttribute}}{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}})
{{#isContainer}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9398,4 +9398,37 @@ public void oneOfDiscriminatorType(String filename, boolean resolveInlineEnum, S
.fileContains(expectedContains);
}

@Test
public void readOnlyPropertiesGetJsonPropertyAnnotation() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/readonly-properties-test.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());

ClientOptInput input = new ClientOptInput();
input.openAPI(openAPI);
input.config(codegen);

DefaultGenerator generator = new DefaultGenerator();
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false");
generator.setGenerateMetadata(false);
generator.opts(input).generate();

// Verify that readOnly properties generate @JsonProperty with access=READ_ONLY
// while non-readOnly properties keep the simple @JsonProperty("name") format
JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/User.java"))
.fileContains("@JsonProperty(value = \"id\", access = JsonProperty.Access.READ_ONLY)")
.fileContains("@JsonProperty(value = \"createdAt\", access = JsonProperty.Access.READ_ONLY)")
.fileContains("@JsonProperty(\"username\")")
.fileContains("@JsonProperty(\"email\")");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
openapi: 3.0.0
info:
title: ReadOnly Properties Test
version: 1.0.0
paths:
/users:
get:
operationId: getUsers
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/User'

components:
schemas:
User:
type: object
properties:
id:
type: string
description: Unique identifier
readOnly: true
createdAt:
type: string
format: date-time
description: Creation timestamp
readOnly: true
username:
type: string
description: User's login name
email:
type: string
format: email
description: User's email address
required:
- username
- email
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ public HasOnlyReadOnlyDto bar(@Nullable String bar) {
* @return bar
*/

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
}

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public void setBar(@Nullable String bar) {
this.bar = bar;
}
Expand All @@ -60,12 +60,12 @@ public HasOnlyReadOnlyDto foo(@Nullable String foo) {
* @return foo
*/

@JsonProperty("foo")
@JsonProperty(value = "foo", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getFoo() {
return foo;
}

@JsonProperty("foo")
@JsonProperty(value = "foo", access = JsonProperty.Access.READ_ONLY)
public void setFoo(@Nullable String foo) {
this.foo = foo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ public NameDto snakeCase(@Nullable Integer snakeCase) {
* @return snakeCase
*/

@JsonProperty("snake_case")
@JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer getSnakeCase() {
return snakeCase;
}

@JsonProperty("snake_case")
@JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public void setSnakeCase(@Nullable Integer snakeCase) {
this.snakeCase = snakeCase;
}
Expand Down Expand Up @@ -110,12 +110,12 @@ public NameDto _123number(@Nullable Integer _123number) {
* @return _123number
*/

@JsonProperty("123Number")
@JsonProperty(value = "123Number", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer get123number() {
return _123number;
}

@JsonProperty("123Number")
@JsonProperty(value = "123Number", access = JsonProperty.Access.READ_ONLY)
public void set123number(@Nullable Integer _123number) {
this._123number = _123number;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ public ReadOnlyFirstDto bar(@Nullable String bar) {
* @return bar
*/

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
}

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public void setBar(@Nullable String bar) {
this.bar = bar;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ public HasOnlyReadOnlyDto bar(@Nullable String bar) {
* @return bar
*/

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Jackson READ_ONLY means serialize but never deserialize. That is the right contract for a server model (don't accept readOnly values in requests), but this shared template is also used by client-side Spring generators — this very sample is samples/client/petstore/spring-http-interface-noResponseEntity. For a client, a readOnly value arrives in the server response and must be deserialized; with READ_ONLY on the getter/setter Jackson now silently skips it, so getBar()/getFoo() return null for values the server actually sent. This is a real behavior change for Spring clients that the PR does not account for (it only frames the server benefit). Confirm client deserialization of readOnly values is not needed, or gate the READ_ONLY emission to server-side generation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/spring-http-interface-noResponseEntity/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java, line 42:

<comment>Jackson `READ_ONLY` means serialize but never deserialize. That is the right contract for a server model (don't accept readOnly values in requests), but this shared template is also used by client-side Spring generators — this very sample is `samples/client/petstore/spring-http-interface-noResponseEntity`. For a client, a readOnly value arrives in the server response and must be deserialized; with `READ_ONLY` on the getter/setter Jackson now silently skips it, so `getBar()`/`getFoo()` return null for values the server actually sent. This is a real behavior change for Spring clients that the PR does not account for (it only frames the server benefit). Confirm client deserialization of readOnly values is not needed, or gate the `READ_ONLY` emission to server-side generation.</comment>

<file context>
@@ -39,12 +39,12 @@ public HasOnlyReadOnlyDto bar(@Nullable String bar) {
    */
   
-  @JsonProperty("bar")
+  @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
   public @Nullable String getBar() {
     return bar;
</file context>

public @Nullable String getBar() {
return bar;
}

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public void setBar(@Nullable String bar) {
this.bar = bar;
}
Expand All @@ -59,12 +59,12 @@ public HasOnlyReadOnlyDto foo(@Nullable String foo) {
* @return foo
*/

@JsonProperty("foo")
@JsonProperty(value = "foo", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getFoo() {
return foo;
}

@JsonProperty("foo")
@JsonProperty(value = "foo", access = JsonProperty.Access.READ_ONLY)
public void setFoo(@Nullable String foo) {
this.foo = foo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ public NameDto snakeCase(@Nullable Integer snakeCase) {
* @return snakeCase
*/

@JsonProperty("snake_case")
@JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer getSnakeCase() {
return snakeCase;
}

@JsonProperty("snake_case")
@JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public void setSnakeCase(@Nullable Integer snakeCase) {
this.snakeCase = snakeCase;
}
Expand Down Expand Up @@ -109,12 +109,12 @@ public NameDto _123number(@Nullable Integer _123number) {
* @return _123number
*/

@JsonProperty("123Number")
@JsonProperty(value = "123Number", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer get123number() {
return _123number;
}

@JsonProperty("123Number")
@JsonProperty(value = "123Number", access = JsonProperty.Access.READ_ONLY)
public void set123number(@Nullable Integer _123number) {
this._123number = _123number;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ public ReadOnlyFirstDto bar(@Nullable String bar) {
* @return bar
*/

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
}

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public void setBar(@Nullable String bar) {
this.bar = bar;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ public HasOnlyReadOnlyDto bar(@Nullable String bar) {
* @return bar
*/

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: For these Spring CLIENT models the shared READ_ONLY setter change prevents Jackson from deserializing readOnly fields, so the client silently receives null for server-generated values (e.g. IDs) in API responses. OpenAPI readOnly means 'may be sent in responses but should not be sent in requests', so a client must still be able to read these fields. Gate the READ_ONLY emission to server-side generators only, or apply it only where the model is used for request deserialization.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/spring-http-interface/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java, line 42:

<comment>For these Spring CLIENT models the shared READ_ONLY setter change prevents Jackson from deserializing readOnly fields, so the client silently receives null for server-generated values (e.g. IDs) in API responses. OpenAPI readOnly means 'may be sent in responses but should not be sent in requests', so a client must still be able to read these fields. Gate the READ_ONLY emission to server-side generators only, or apply it only where the model is used for request deserialization.</comment>

<file context>
@@ -39,12 +39,12 @@ public HasOnlyReadOnlyDto bar(@Nullable String bar) {
    */
   
-  @JsonProperty("bar")
+  @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
   public @Nullable String getBar() {
     return bar;
</file context>

public @Nullable String getBar() {
return bar;
}

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public void setBar(@Nullable String bar) {
this.bar = bar;
}
Expand All @@ -59,12 +59,12 @@ public HasOnlyReadOnlyDto foo(@Nullable String foo) {
* @return foo
*/

@JsonProperty("foo")
@JsonProperty(value = "foo", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getFoo() {
return foo;
}

@JsonProperty("foo")
@JsonProperty(value = "foo", access = JsonProperty.Access.READ_ONLY)
public void setFoo(@Nullable String foo) {
this.foo = foo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ public NameDto snakeCase(@Nullable Integer snakeCase) {
* @return snakeCase
*/

@JsonProperty("snake_case")
@JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer getSnakeCase() {
return snakeCase;
}

@JsonProperty("snake_case")
@JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public void setSnakeCase(@Nullable Integer snakeCase) {
this.snakeCase = snakeCase;
}
Expand Down Expand Up @@ -109,12 +109,12 @@ public NameDto _123number(@Nullable Integer _123number) {
* @return _123number
*/

@JsonProperty("123Number")
@JsonProperty(value = "123Number", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer get123number() {
return _123number;
}

@JsonProperty("123Number")
@JsonProperty(value = "123Number", access = JsonProperty.Access.READ_ONLY)
public void set123number(@Nullable Integer _123number) {
this._123number = _123number;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ public ReadOnlyFirstDto bar(@Nullable String bar) {
* @return bar
*/

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
}

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public void setBar(@Nullable String bar) {
this.bar = bar;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ public HasOnlyReadOnlyDto bar(@Nullable String bar) {
*/

@Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
}

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public void setBar(@Nullable String bar) {
this.bar = bar;
}
Expand All @@ -63,12 +63,12 @@ public HasOnlyReadOnlyDto foo(@Nullable String foo) {
*/

@Schema(name = "foo", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("foo")
@JsonProperty(value = "foo", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getFoo() {
return foo;
}

@JsonProperty("foo")
@JsonProperty(value = "foo", access = JsonProperty.Access.READ_ONLY)
public void setFoo(@Nullable String foo) {
this.foo = foo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ public NameDto snakeCase(@Nullable Integer snakeCase) {
*/

@Schema(name = "snake_case", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("snake_case")
@JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer getSnakeCase() {
return snakeCase;
}

@JsonProperty("snake_case")
@JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public void setSnakeCase(@Nullable Integer snakeCase) {
this.snakeCase = snakeCase;
}
Expand Down Expand Up @@ -123,12 +123,12 @@ public NameDto _123Number(@Nullable Integer _123Number) {
*/

@Schema(name = "123Number", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("123Number")
@JsonProperty(value = "123Number", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer get123Number() {
return _123Number;
}

@JsonProperty("123Number")
@JsonProperty(value = "123Number", access = JsonProperty.Access.READ_ONLY)
public void set123Number(@Nullable Integer _123Number) {
this._123Number = _123Number;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ public ReadOnlyFirstDto bar(@Nullable String bar) {
*/

@Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
}

@JsonProperty("bar")
@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public void setBar(@Nullable String bar) {
this.bar = bar;
}
Expand Down
Loading