diff --git a/CI/circle_parallel.sh b/CI/circle_parallel.sh index 83d48b12aeb1..b004e6e8b80c 100755 --- a/CI/circle_parallel.sh +++ b/CI/circle_parallel.sh @@ -21,12 +21,8 @@ if [ "$NODE_INDEX" = "1" ]; then elif [ "$NODE_INDEX" = "2" ]; then echo "Running node $NODE_INDEX to test cpp-restsdk" - # install cpprestsdk - sudo apt-get install libcpprest-dev - wget "https://github.com/aminya/setup-cpp/releases/download/v0.37.0/setup-cpp-x64-linux" - chmod +x ./setup-cpp-x64-linux - sudo ./setup-cpp-x64-linux --compiler llvm --cmake true --ninja true - source ~/.cpprc # activate cpp environment variables + # install cpprestsdk and C++ build tools via apt (avoids setup-cpp's PPA/GPG key fetch) + sudo apt-get install -y libcpprest-dev clang cmake (cd samples/client/petstore/cpp-restsdk/client && mvn integration-test) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java index f2909042bbba..f79dc954e119 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -980,6 +980,18 @@ public Schema normalizeSchema(Schema schema, Set visitedSchemas) { return schema; } else if (ModelUtils.hasProperties(schema)) { + // OAS 3.1: if the type array includes "null", extract it and set nullable:true + // on the parent schema before normalizing its child properties. + // We intentionally do NOT call the full processNormalize31Spec here because + // that method can replace a JsonSchema with properties (but no explicit type) + // with an empty schema, discarding all properties. + if (getRule(NORMALIZE_31SPEC) && schema.getTypes() != null && schema.getTypes().contains("null")) { + schema.setNullable(true); + schema.getTypes().remove("null"); + if (schema.getTypes().size() == 1) { + schema.setType(String.valueOf(schema.getTypes().iterator().next())); + } + } normalizeProperties(schema, visitedSchemas); } else if (schema.getAdditionalProperties() instanceof Schema) { // map normalizeMapSchema(schema); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java index 53cacea15650..a512e736e29e 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java @@ -1896,4 +1896,62 @@ public void testLooseNullDefinitions() { ModelUtils.looseNullDefinitions = false; } + /** + * Verify that a schema defined as type:[object,"null"] WITH properties (OAS 3.1 style) + * is correctly normalized so that nullable:true is set on the schema itself. + * Regression test for https://github.com/OpenAPITools/openapi-generator/issues/24139 + */ + @Test + public void testIssue24139NullableObjectWithPropertiesGetsNullableTrue() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/issue_24139.yaml"); + + // Before normalization: NestedNullable has types=[object,null], nullable is not yet set + Schema nestedNullableBefore = openAPI.getComponents().getSchemas().get("NestedNullable"); + assertNotNull(nestedNullableBefore); + assertNotNull(nestedNullableBefore.getProperties()); + + Map options = new HashMap<>(); + options.put("NORMALIZE_31SPEC", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + // After normalization: nullable must be true and type must be "object" + Schema nestedNullableAfter = openAPI.getComponents().getSchemas().get("NestedNullable"); + assertNotNull(nestedNullableAfter); + assertEquals(nestedNullableAfter.getNullable(), Boolean.TRUE, + "NestedNullable with type:[object,\"null\"] should have nullable:true after normalization"); + assertEquals(nestedNullableAfter.getType(), "object"); + assertNotNull(nestedNullableAfter.getProperties(), + "NestedNullable properties must be preserved after normalization"); + } + + /** + * Regression test: an OAS 3.1 schema with properties but NO explicit type declaration + * must keep its properties after NORMALIZE_31SPEC normalization. + * Regression for a potential regression introduced by the fix for issue 24139. + */ + @Test + public void testIssue24139ImpliedObjectSchemaKeepsProperties() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/issue_24139.yaml"); + + Schema impliedBefore = openAPI.getComponents().getSchemas().get("ImpliedObject"); + assertNotNull(impliedBefore); + assertNotNull(impliedBefore.getProperties()); + + Map options = new HashMap<>(); + options.put("NORMALIZE_31SPEC", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + Schema impliedAfter = openAPI.getComponents().getSchemas().get("ImpliedObject"); + assertNotNull(impliedAfter); + assertNotNull(impliedAfter.getProperties(), + "ImpliedObject (no explicit type, just properties) must keep its properties after normalization"); + assertNotNull(impliedAfter.getProperties().get("name"), + "ImpliedObject.name property must be preserved after normalization"); + assertNull(impliedAfter.getNullable(), + "ImpliedObject must not be marked nullable (no null type was declared)"); + } + } + diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java index 7fbc5ceea289..fb53bafa3ad0 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java @@ -6813,4 +6813,36 @@ public void paramJsonPropertyAnnotationWithDigitStartingPropertyName() throws IO "@param:JsonProperty(\"2nd_field\")\n @get:JsonProperty(\"2nd_field\") val `2ndField`" ); } + + + /** + * Regression test for https://github.com/OpenAPITools/openapi-generator/issues/24139 + * A property that $ref's an OAS 3.1 schema with type:[object,"null"] is nullable and must + * NOT receive @field:JsonSetter(nulls = Nulls.FAIL). + */ + @Test(description = "issue 24139: nullable $ref (type:[object,null]) must not get @JsonSetter(nulls = Nulls.FAIL)") + public void testIssue24139NullableRefNoJsonSetterNullsFail() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put("useBeanValidation", true); + additionalProperties.put("openApiNullable", "true"); + + Map files = generateFromContract( + "src/test/resources/3_1/issue_24139.yaml", + additionalProperties + ); + + File itemFile = files.get("Item.kt"); + assertThat(itemFile).isNotNull(); + + // nestedNullable: $ref to NestedNullable (type:[object,"null"]) — nullable, no @JsonSetter(nulls = Nulls.FAIL) + assertFileNotContains(itemFile.toPath(), "nestedNullable: NestedNullable"); + // The field must NOT have @JsonSetter(nulls = Nulls.FAIL) because the referenced schema is nullable + String content = org.apache.commons.io.FileUtils.readFileToString(itemFile, StandardCharsets.UTF_8); + // Extract the nestedNullable field block and verify annotation absence + Assert.assertFalse( + content.contains("@field:JsonSetter(nulls = Nulls.FAIL)\n @param:JsonProperty(\"nestedNullable\")") || + content.contains("@field:JsonSetter(nulls = Nulls.FAIL)\n @get:JsonProperty(\"nestedNullable\")"), + "nestedNullable ($ref to nullable schema) must not have @JsonSetter(nulls = Nulls.FAIL)" + ); + } } diff --git a/modules/openapi-generator/src/test/resources/3_1/issue_24139.yaml b/modules/openapi-generator/src/test/resources/3_1/issue_24139.yaml new file mode 100644 index 000000000000..69910a01bb06 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/issue_24139.yaml @@ -0,0 +1,37 @@ +openapi: 3.1.1 +info: + title: Test issue 24139 + version: 1.0.0 + +components: + schemas: + NestedNullable: + type: [ object, "null" ] + properties: + value: + type: [ number, "null" ] + Nested: + type: object + properties: + value: + type: [ number, "null" ] + # OAS 3.1 allows object schemas with properties but no explicit type declaration. + # This must not lose its properties after normalization. + ImpliedObject: + properties: + name: + type: string + + Item: + type: object + properties: + nestedNullable: + $ref: '#/components/schemas/NestedNullable' + + nestedNullable2: + anyOf: + - $ref: '#/components/schemas/Nested' + - type: 'null' + + nested: + $ref: '#/components/schemas/Nested' diff --git a/samples/client/petstore/cpp-restsdk/client/pom.xml b/samples/client/petstore/cpp-restsdk/client/pom.xml index 6c5f5ed24c96..5287c089dc84 100644 --- a/samples/client/petstore/cpp-restsdk/client/pom.xml +++ b/samples/client/petstore/cpp-restsdk/client/pom.xml @@ -35,6 +35,9 @@ cmake + . + -DCMAKE_C_COMPILER=clang + -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-I/usr/local/opt/openssl/include" -DCMAKE_MODULE_LINKER_FLAGS="-L/usr/local/opt/openssl/lib" -DCMAKE_BUILD_TYPE=Debug