-
-
Notifications
You must be signed in to change notification settings - Fork 7.1k
Description
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator (example)?
- Have you tested with the latest master to confirm the issue still exists?
- Have you searched for related issues/PRs?
- What's the actual output vs expected output?
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
For our OpenAPI spec using the 3.1.0 specification, we make heavy use of the additionalProperties: false
keyword on most of our objects to make validation stricter, both while validating our examples and in the generated server code. By adding they keyword, we ensure we don't get false negatives while validating examples with non-required properties with a typo in the key, and make issues in usage of the APIs clearer.
However, when this keyword is combined with an object with nullable properties, the generated models are invalid (see the example below). I've tested both the Typescript-Angular client generator, the Java OKHTTP client generator and the Java spring-boot API spec generator, and each of them shows the same behaviour: the nullable property is inlined to a new, invalid object resulting in broken code.
openapi-generator version
Tested with version 7.9.0 and 7.10.0, both show the same behaviour.
OpenAPI declaration file content or url
# filename: nullable-properties-with-additional-properties-false.yaml
openapi: 3.1.0
info:
title: ""
version: 1.0.0
components:
schemas:
# For reference, an object without additionalProperties: false, but with nullable properties
SampleObject:
properties:
someString:
anyOf:
- type: string
- type: 'null'
# For reference, an object with additionalProperties: false, but without any nullable properties
ReferenceObject:
additionalProperties: false
properties:
someString:
type: string
# The broken case: an object with additionalProperties: false and nullable properties
SampleObjectWithAdditionalFalse:
additionalProperties: false
properties:
someString:
anyOf:
- type: string
- type: 'null'
Generation Details
Verified with the following configs, and no further settings. Run with java -jar openapi-generator-cli-7.10.0.jar generate --config <config-file-as-shown-in-examples>.yaml
and no further customization. I will add additional samples in comments for other generators.
Spring Generator
generatorName: spring
outputDir: ./spring-boot
inputSpec: nullable-properties-with-additional-properties-false.yaml
Generated code below. Note the reference objects to show the expected behaviour:
- For the simplest reference object, a String field with a NOT_REQUIRED annotation.
- For the object
// Example objects leave out their imports, the equals(), hashCode(), toString() and toIntendedString() methods for
some brevity.
/**
* ReferenceObject
*/
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-12-01T14:17:54.855204+01:00[Europe/Amsterdam]", comments = "Generator version: 7.10.0")
public class ReferenceObject {
private String someString;
public ReferenceObject someString(String someString) {
this.someString = someString;
return this;
}
/**
* Get someString
* @return someString
*/
@Schema(name = "someString", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("someString")
public String getSomeString() {
return someString;
}
public void setSomeString(String someString) {
this.someString = someString;
}
}
/**
* ObjectWithAdditionalFalse
*/
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-12-01T14:17:54.855204+01:00[Europe/Amsterdam]", comments = "Generator version: 7.10.0")
public class ObjectWithAdditionalFalse {
private JsonNullable<String> someString = JsonNullable.<String>undefined();
public ObjectWithAdditionalFalse someString(String someString) {
this.someString = JsonNullable.of(someString);
return this;
}
/**
* Get someString
* @return someString
*/
@Schema(name = "someString", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("someString")
public JsonNullable<String> getSomeString() {
return someString;
}
public void setSomeString(JsonNullable<String> someString) {
this.someString = someString;
}
}
/**
* ObjectWithNullableProperties
*/
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-12-01T14:17:54.855204+01:00[Europe/Amsterdam]", comments = "Generator version: 7.10.0")
public class ObjectWithNullableProperties {
private JsonNullable<String> someString = JsonNullable.<String>undefined();
public ObjectWithNullableProperties someString(String someString) {
this.someString = JsonNullable.of(someString);
return this;
}
/**
* Get someString
* @return someString
*/
@Schema(name = "someString", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("someString")
public JsonNullable<String> getSomeString() {
return someString;
}
public void setSomeString(JsonNullable<String> someString) {
this.someString = someString;
}
}
/**
* BrokenObject
*/
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-12-01T14:17:54.855204+01:00[Europe/Amsterdam]", comments = "Generator version: 7.10.0")
public class BrokenObject {
private BrokenObjectSomeString someString;
public BrokenObject someString(BrokenObjectSomeString someString) {
this.someString = someString;
return this;
}
/**
* Get someString
* @return someString
*/
@Valid
@Schema(name = "someString", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("someString")
public BrokenObjectSomeString getSomeString() {
return someString;
}
public void setSomeString(BrokenObjectSomeString someString) {
this.someString = someString;
}
/**
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*/
private Map<String, Object> additionalProperties;
/**
* Set the additional (undeclared) property with the specified name and value.
* If the property does not already exist, create it otherwise replace it.
*/
@JsonAnySetter
public BrokenObject putAdditionalProperty(String key, Object value) {
if (this.additionalProperties == null) {
this.additionalProperties = new HashMap<String, Object>();
}
this.additionalProperties.put(key, value);
return this;
}
/**
* Return the additional (undeclared) property.
*/
@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
}
/**
* Return the additional (undeclared) property with the specified name.
*/
public Object getAdditionalProperty(String key) {
if (this.additionalProperties == null) {
return null;
}
return this.additionalProperties.get(key);
}
}
/**
* BrokenObjectSomeString
*/
@JsonTypeName("SampleObjectWithAdditionalFalse_someString")
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-12-01T14:17:54.855204+01:00[Europe/Amsterdam]", comments = "Generator version: 7.10.0")
public class BrokenObjectSomeString {
// This object only has the equals(), hashCode(), toString() and toIntendedString() methods.
}
Note that I've also tried the config below, which seems to make practically no difference - except that the incorrectly introduced wrapper object also gets additionalProperties if set to false:
generatorName: spring
outputDir: ./spring-boot
inputSpec: nullable-properties-with-additional-properties-false.yaml
additionalProperties:
disallowAdditionalPropertiesIfNotPresent: true
Resulting in the following BrokenObjectSomeString.java
:
/**
* BrokenObjectSomeString
*/
@JsonTypeName("BrokenObject_someString")
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2024-12-01T14:35:50.542378+01:00[Europe/Amsterdam]", comments = "Generator version: 7.10.0")
public class BrokenObjectSomeString {
/**
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*/
private Map<String, Object> additionalProperties;
// Along with all other fields and methods expected of a model which allows additionalProperties. Note that the underlying type was a String, not an object!
}
Steps to reproduce
Create a file with the contents as above - or just any OpenAPI 3.1.0 spec where an object has both additionalProperties: false
and any nullable field, done by adding a oneOf
or anyOf
where one option is the actual type of the field, and the other is { "type": "null" }
.
Run a generate task with any of the mentioned generators (likely others as well).
Related issues/PRs
None that I can find. Can supply one if I find a solution.
Suggest a fix
Researching one, though I already want to have the issue filed for reference.