diff --git a/modules/swagger-parser-v2-converter/src/main/java/io/swagger/v3/parser/converter/SwaggerConverter.java b/modules/swagger-parser-v2-converter/src/main/java/io/swagger/v3/parser/converter/SwaggerConverter.java index 019aeaf6fd..449a9e3a6f 100644 --- a/modules/swagger-parser-v2-converter/src/main/java/io/swagger/v3/parser/converter/SwaggerConverter.java +++ b/modules/swagger-parser-v2-converter/src/main/java/io/swagger/v3/parser/converter/SwaggerConverter.java @@ -786,7 +786,9 @@ private Schema convert(SerializableParameter sp) { if (sp instanceof AbstractSerializableParameter) { AbstractSerializableParameter ap = (AbstractSerializableParameter) sp; - schema.setDefault(ap.getDefault()); + if (ap.getDefault() != null) { + schema.setDefault(ap.getDefault()); + } } return schema; } @@ -1163,7 +1165,9 @@ public Parameter convert(io.swagger.models.parameters.Parameter v2Parameter) { if (sp instanceof AbstractSerializableParameter) { AbstractSerializableParameter ap = (AbstractSerializableParameter) sp; - schema.setDefault(ap.getDefault()); + if (ap.getDefault() != null) { + schema.setDefault(ap.getDefault()); + } } } diff --git a/modules/swagger-parser-v2-converter/src/test/java/io/swagger/parser/test/V2ConverterNullHandlingTest.java b/modules/swagger-parser-v2-converter/src/test/java/io/swagger/parser/test/V2ConverterNullHandlingTest.java new file mode 100644 index 0000000000..8c84c31bb2 --- /dev/null +++ b/modules/swagger-parser-v2-converter/src/test/java/io/swagger/parser/test/V2ConverterNullHandlingTest.java @@ -0,0 +1,311 @@ +package io.swagger.parser.test; + +import io.swagger.v3.core.util.Json; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.parser.OpenAPIV3Parser; +import io.swagger.v3.parser.converter.SwaggerConverter; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +/** + * Tests for null handling in Swagger V2 to OpenAPI V3 conversion + */ +public class V2ConverterNullHandlingTest { + + @Test + public void testV2ParameterWithoutDefaultNotSetInV3() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " parameters:\n" + + " - name: param1\n" + + " in: query\n" + + " type: string\n" + + " responses:\n" + + " '200':\n" + + " description: OK\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + System.out.println(Json.pretty(openAPI)); + + java.util.List params = openAPI.getPaths().get("/test").getGet().getParameters(); + assertEquals(params.size(), 1); + + Schema schema = params.get(0).getSchema(); + assertNotNull(schema); + assertNull(schema.getDefault(), "Default should be null"); + assertFalse(schema.getDefaultSetFlag(), "Default should not be set"); + } + + @Test + public void testV2ParameterWithDefaultPreservedInV3() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " parameters:\n" + + " - name: param1\n" + + " in: query\n" + + " type: string\n" + + " default: 'test'\n" + + " responses:\n" + + " '200':\n" + + " description: OK\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + System.out.println(Json.pretty(openAPI)); + java.util.List params = openAPI.getPaths().get("/test").getGet().getParameters(); + assertEquals(params.size(), 1); + + Schema schema = params.get(0).getSchema(); + assertNotNull(schema); + assertEquals(schema.getDefault(), "test", "Default should be 'test'"); + assertTrue(schema.getDefaultSetFlag(), "Default should be set"); + } + + @Test + public void testV2BodyParameterWithoutDefaultNotSetInV3() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " post:\n" + + " parameters:\n" + + " - name: body\n" + + " in: body\n" + + " schema:\n" + + " type: object\n" + + " properties:\n" + + " name:\n" + + " type: string\n" + + " responses:\n" + + " '200':\n" + + " description: OK\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + System.out.println(Json.pretty(openAPI)); + + io.swagger.v3.oas.models.parameters.RequestBody requestBody = openAPI.getPaths().get("/test").getPost().getRequestBody(); + assertNotNull(requestBody); + assertNotNull(requestBody.getContent()); + assertFalse(requestBody.getContent().isEmpty()); + + String mediaType = requestBody.getContent().containsKey("application/json") ? "application/json" : "*/*"; + Schema schema = requestBody.getContent().get(mediaType).getSchema(); + assertNotNull(schema); + assertNull(schema.getDefault(), "Default should be null"); + assertFalse(schema.getDefaultSetFlag(), "Default should not be set"); + } + + @Test + public void testV2DefinitionWithoutDefaultNotSetInV3() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "definitions:\n" + + " TestModel:\n" + + " type: object\n" + + " properties:\n" + + " name:\n" + + " type: string\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + System.out.println(Json.pretty(openAPI)); + + Schema schema = openAPI.getComponents().getSchemas().get("TestModel"); + assertNotNull(schema); + assertNull(schema.getDefault(), "Default should be null"); + assertFalse(schema.getDefaultSetFlag(), "Default should not be set"); + } + + @Test + public void testV2PropertyWithDefaultPreservedInV3() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "definitions:\n" + + " TestModel:\n" + + " type: object\n" + + " properties:\n" + + " status:\n" + + " type: string\n" + + " default: 'active'\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + System.out.println(Json.pretty(openAPI)); + + Schema schema = openAPI.getComponents().getSchemas().get("TestModel"); + assertNotNull(schema); + + Schema statusProp = (Schema) schema.getProperties().get("status"); + assertNotNull(statusProp); + assertEquals(statusProp.getDefault(), "active", "Default should be 'active'"); + assertTrue(statusProp.getDefaultSetFlag(), "Default should be set"); + } + + @Test + public void testV2ArrayItemsWithoutDefaultNotSetInV3() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " parameters:\n" + + " - name: items\n" + + " in: query\n" + + " type: array\n" + + " items:\n" + + " type: string\n" + + " responses:\n" + + " '200':\n" + + " description: OK\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + System.out.println(Json.pretty(openAPI)); + + java.util.List params = openAPI.getPaths().get("/test").getGet().getParameters(); + assertEquals(params.size(), 1); + + Schema schema = params.get(0).getSchema(); + assertNotNull(schema); + assertTrue(schema instanceof io.swagger.v3.oas.models.media.ArraySchema); + + io.swagger.v3.oas.models.media.ArraySchema arraySchema = + (io.swagger.v3.oas.models.media.ArraySchema) schema; + assertNull(arraySchema.getDefault(), "Default should be null"); + assertFalse(arraySchema.getDefaultSetFlag(), "Default should not be set"); + } + + @Test + public void testV2ExamplePreservedInV3() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "definitions:\n" + + " TestModel:\n" + + " type: object\n" + + " example:\n" + + " name: 'test'\n" + + " properties:\n" + + " name:\n" + + " type: string\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + Schema schema = openAPI.getComponents().getSchemas().get("TestModel"); + assertNotNull(schema); + assertNotNull(schema.getExample(), "Example should be set"); + } + + @Test + public void testV2PropertyWithoutExampleNotSetInV3() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "definitions:\n" + + " TestModel:\n" + + " type: object\n" + + " properties:\n" + + " name:\n" + + " type: string\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + Schema schema = openAPI.getComponents().getSchemas().get("TestModel"); + assertNotNull(schema); + + Schema nameProp = (Schema) schema.getProperties().get("name"); + assertNotNull(nameProp); + assertNull(nameProp.getExample(), "Example should be null"); + assertFalse(nameProp.getExampleSetFlag(), "Example should not be set"); + } + + @Test + public void testV2IntegerParameterWithDefault() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " parameters:\n" + + " - name: limit\n" + + " in: query\n" + + " type: integer\n" + + " default: 10\n" + + " responses:\n" + + " '200':\n" + + " description: OK\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + java.util.List params = openAPI.getPaths().get("/test").getGet().getParameters(); + assertEquals(params.size(), 1); + + Schema schema = params.get(0).getSchema(); + assertNotNull(schema); + assertEquals(schema.getDefault(), 10, "Default should be 10"); + assertTrue(schema.getDefaultSetFlag(), "Default should be set"); + } + + @Test + public void testV2BooleanParameterWithDefault() { + String v2Yaml = "swagger: '2.0'\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " parameters:\n" + + " - name: verbose\n" + + " in: query\n" + + " type: boolean\n" + + " default: false\n" + + " responses:\n" + + " '200':\n" + + " description: OK\n"; + + OpenAPI openAPI = new SwaggerConverter().readContents(v2Yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + java.util.List params = openAPI.getPaths().get("/test").getGet().getParameters(); + assertEquals(params.size(), 1); + + Schema schema = params.get(0).getSchema(); + assertNotNull(schema); + assertEquals(schema.getDefault(), false, "Default should be false"); + assertTrue(schema.getDefaultSetFlag(), "Default should be set"); + } +} diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java index 35edf978b1..421822dd3b 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java @@ -2994,8 +2994,6 @@ at the moment path passed as string (basePath) from upper components can be both if (defaultObject != null) { schema.setDefault(defaultObject); } - } else { - schema.setDefault(null); } Map extensions = getExtensions(node); diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/ResolverFully.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/ResolverFully.java index 3f3e19ec3b..777c7742f0 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/ResolverFully.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/ResolverFully.java @@ -437,7 +437,10 @@ public Schema resolveSchema(Schema schema) { aggregateSchemaCombinators(composedSchema, combinedModel, composedSchema.getAnyOf(), examples, defaultValues); } if (defaultValues.size() == 1) { - combinedModel.setDefault(defaultValues.iterator().next()); + Object defaultValue = defaultValues.iterator().next(); + if (defaultValue != null) { + combinedModel.setDefault(defaultValue); + } } if (schema.getExample() != null) { @@ -600,10 +603,11 @@ private void aggregateSchemaCombinators(ComposedSchema sourceSchema, Schema targ if (resolved.getExample() != null) { examples.add(resolved.getExample()); } - if (sourceSchema.getDefault() != null && resolved.getDefault() == null) - defaultValues.add(sourceSchema.getDefault()); - else + if (resolved.getDefault() != null) { defaultValues.add(resolved.getDefault()); + } else if (sourceSchema.getDefault() != null) { + defaultValues.add(sourceSchema.getDefault()); + } if (resolved.getExtensions() != null) { Map extensions = resolved.getExtensions(); diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NullHandlingTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NullHandlingTest.java new file mode 100644 index 0000000000..4992fa1ff9 --- /dev/null +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NullHandlingTest.java @@ -0,0 +1,475 @@ +package io.swagger.v3.parser.test; + +import io.swagger.v3.core.util.Json; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.examples.Example; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.parser.OpenAPIV3Parser; +import io.swagger.v3.parser.core.models.ParseOptions; +import io.swagger.v3.parser.core.models.SwaggerParseResult; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +/** + * Comprehensive tests for null handling after swagger-core changes. + * Tests verify the distinction between "field not set" and "field explicitly set to null". + */ +public class NullHandlingTest { + + @Test + public void testSchemaDefaultExplicitlyNull() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " NullableSchema:\n" + + " type: string\n" + + " nullable: true\n" + + " default: null\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + Schema schema = openAPI.getComponents().getSchemas().get("NullableSchema"); + assertNotNull(schema); + assertNull(schema.getDefault(), "Default should be null"); + assertTrue(schema.getDefaultSetFlag(), "Default set flag should be true"); + } + + @Test + public void testSchemaDefaultNotSet() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " NoDefaultSchema:\n" + + " type: string\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + Schema schema = openAPI.getComponents().getSchemas().get("NoDefaultSchema"); + assertNotNull(schema); + assertNull(schema.getDefault(), "Default should be null"); + assertFalse(schema.getDefaultSetFlag(), "Default set flag should be false"); + } + + @Test + public void testSchemaExampleExplicitlyNull() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " NullExampleSchema:\n" + + " type: string\n" + + " example: null\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + + Schema schema = openAPI.getComponents().getSchemas().get("NullExampleSchema"); + assertNotNull(schema); + assertNull(schema.getExample(), "Example should be null"); + assertTrue(schema.getExampleSetFlag(), "Example set flag should be true"); + } + + @Test + public void testSchemaExampleNotSet() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " NoExampleSchema:\n" + + " type: string\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + + Schema schema = openAPI.getComponents().getSchemas().get("NoExampleSchema"); + assertNotNull(schema); + assertNull(schema.getExample(), "Example should be null"); + assertFalse(schema.getExampleSetFlag(), "Example set flag should be false"); + } + + @Test + public void testExampleValueExplicitlyNull() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " examples:\n" + + " NullExample:\n" + + " value: null\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + + Example example = openAPI.getComponents().getExamples().get("NullExample"); + assertNotNull(example); + assertNull(example.getValue(), "Value should be null"); + assertTrue(example.getValueSetFlag(), "Value set flag should be true"); + } + + @Test + public void testExampleValueNotSet() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " examples:\n" + + " NoValueExample:\n" + + " summary: Example without value\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + + Example example = openAPI.getComponents().getExamples().get("NoValueExample"); + assertNotNull(example); + assertNull(example.getValue(), "Value should be null"); + assertFalse(example.getValueSetFlag(), "Value set flag should be false"); + } + + @Test(description = "Test allOf with source default preserves it when not resolving combinators") + public void testAllOfSourceDefaultPreservedWithoutResolvingCombinators() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " BaseSchema:\n" + + " type: object\n" + + " properties:\n" + + " name:\n" + + " type: string\n" + + " ExtendedSchema:\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/BaseSchema'\n" + + " default:\n" + + " name: 'default'\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(false); + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, options).getOpenAPI(); + assertNotNull(openAPI); + + Schema extended = openAPI.getComponents().getSchemas().get("ExtendedSchema"); + assertNotNull(extended); + assertNotNull(extended.getDefault(), "Default should be preserved when not resolving combinators"); + assertTrue(extended.getDefaultSetFlag(), "Default should be set"); + } + + @Test(description = "Test anyOf does not propagate null example") + public void testAnyOfNullExampleNotPropagated() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " Schema1:\n" + + " type: string\n" + + " Schema2:\n" + + " type: number\n" + + " CombinedSchema:\n" + + " anyOf:\n" + + " - $ref: '#/components/schemas/Schema1'\n" + + " - $ref: '#/components/schemas/Schema2'\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(true); + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, options).getOpenAPI(); + assertNotNull(openAPI); + + Schema combined = openAPI.getComponents().getSchemas().get("CombinedSchema"); + assertNotNull(combined); + assertNull(combined.getExample(), "Example should be null"); + assertFalse(combined.getExampleSetFlag(), "Example should not be set"); + } + + @Test(description = "Test oneOf preserves explicit null example from source") + public void testOneOfExplicitNullExamplePreserved() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " Schema1:\n" + + " type: string\n" + + " Schema2:\n" + + " type: number\n" + + " CombinedSchema:\n" + + " oneOf:\n" + + " - $ref: '#/components/schemas/Schema1'\n" + + " - $ref: '#/components/schemas/Schema2'\n" + + " example: null\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(true); + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, options).getOpenAPI(); + assertNotNull(openAPI); + + Schema combined = openAPI.getComponents().getSchemas().get("CombinedSchema"); + assertNotNull(combined); + assertNull(combined.getExample(), "Example should be null"); + assertTrue(combined.getExampleSetFlag(), "Example should be explicitly set"); + } + + @Test(description = "Test property example null vs not set") + public void testPropertyExampleNullVsNotSet() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " TestSchema:\n" + + " type: object\n" + + " properties:\n" + + " withNullExample:\n" + + " type: string\n" + + " example: null\n" + + " withoutExample:\n" + + " type: string\n" + + " withExample:\n" + + " type: string\n" + + " example: 'test'\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + Schema testSchema = openAPI.getComponents().getSchemas().get("TestSchema"); + assertNotNull(testSchema); + + Schema withNull = (Schema) testSchema.getProperties().get("withNullExample"); + assertNull(withNull.getExample()); + assertTrue(withNull.getExampleSetFlag(), "Should be explicitly set to null"); + + Schema without = (Schema) testSchema.getProperties().get("withoutExample"); + assertNull(without.getExample()); + assertFalse(without.getExampleSetFlag(), "Should not be set"); + + Schema withExample = (Schema) testSchema.getProperties().get("withExample"); + assertEquals(withExample.getExample(), "test"); + assertTrue(withExample.getExampleSetFlag(), "Should be set"); + } + + @Test(description = "Test parameter schema example null vs not set") + public void testParameterSchemaExampleNullVsNotSet() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " parameters:\n" + + " - name: nullExample\n" + + " in: query\n" + + " schema:\n" + + " type: string\n" + + " example: null\n" + + " - name: noExample\n" + + " in: query\n" + + " schema:\n" + + " type: string\n" + + " responses:\n" + + " '200':\n" + + " description: OK\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + java.util.List params = openAPI.getPaths().get("/test").getGet().getParameters(); + assertEquals(params.size(), 2); + + Schema nullSchema = params.get(0).getSchema(); + assertNull(nullSchema.getExample()); + assertTrue(nullSchema.getExampleSetFlag(), "Should be explicitly set to null"); + + Schema noExampleSchema = params.get(1).getSchema(); + assertNull(noExampleSchema.getExample()); + assertFalse(noExampleSchema.getExampleSetFlag(), "Should not be set"); + } + + @Test(description = "Test media type example null vs not set") + public void testMediaTypeExampleNullVsNotSet() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " post:\n" + + " requestBody:\n" + + " content:\n" + + " application/json:\n" + + " schema:\n" + + " type: object\n" + + " example: null\n" + + " application/xml:\n" + + " schema:\n" + + " type: object\n" + + " responses:\n" + + " '200':\n" + + " description: OK\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + io.swagger.v3.oas.models.media.Content content = openAPI.getPaths().get("/test").getPost().getRequestBody().getContent(); + + io.swagger.v3.oas.models.media.MediaType jsonMedia = content.get("application/json"); + assertNull(jsonMedia.getExample()); + assertTrue(jsonMedia.getExampleSetFlag(), "Should be explicitly set to null"); + + io.swagger.v3.oas.models.media.MediaType xmlMedia = content.get("application/xml"); + assertNull(xmlMedia.getExample()); + assertFalse(xmlMedia.getExampleSetFlag(), "Should not be set"); + } + + @Test(description = "Test header schema example null vs not set") + public void testHeaderSchemaExampleNullVsNotSet() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: OK\n" + + " headers:\n" + + " X-Null-Example:\n" + + " schema:\n" + + " type: string\n" + + " example: null\n" + + " X-No-Example:\n" + + " schema:\n" + + " type: string\n"; + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, null).getOpenAPI(); + assertNotNull(openAPI); + + java.util.Map headers = openAPI.getPaths().get("/test").getGet().getResponses().get("200").getHeaders(); + + Schema nullSchema = headers.get("X-Null-Example").getSchema(); + assertNull(nullSchema.getExample()); + assertTrue(nullSchema.getExampleSetFlag(), "Should be explicitly set to null"); + + Schema noExampleSchema = headers.get("X-No-Example").getSchema(); + assertNull(noExampleSchema.getExample()); + assertFalse(noExampleSchema.getExampleSetFlag(), "Should not be set"); + } + + @Test(description = "Test multiple defaults in allOf - should not set any if different") + public void testAllOfMultipleDifferentDefaults() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " Schema1:\n" + + " type: object\n" + + " default:\n" + + " value: 1\n" + + " properties:\n" + + " value:\n" + + " type: integer\n" + + " Schema2:\n" + + " type: object\n" + + " default:\n" + + " value: 2\n" + + " properties:\n" + + " value:\n" + + " type: integer\n" + + " CombinedSchema:\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/Schema1'\n" + + " - $ref: '#/components/schemas/Schema2'\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(true); + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, options).getOpenAPI(); + assertNotNull(openAPI); + + Schema combined = openAPI.getComponents().getSchemas().get("CombinedSchema"); + assertNotNull(combined); + // When multiple different defaults exist, none should be set + assertNull(combined.getDefault(), "Default should not be set when multiple different defaults exist"); + } + + @Test(description = "Test resolveFully without resolveCombinators preserves defaults") + public void testResolveFullyWithoutCombinators() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " BaseSchema:\n" + + " type: object\n" + + " default:\n" + + " name: 'test'\n" + + " properties:\n" + + " name:\n" + + " type: string\n" + + " ExtendedSchema:\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/BaseSchema'\n" + + " default:\n" + + " name: 'extended'\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(false); + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(yaml, null, options).getOpenAPI(); + assertNotNull(openAPI); + + + Schema extended = openAPI.getComponents().getSchemas().get("ExtendedSchema"); + assertNotNull(extended); + assertNotNull(extended.getDefault(), "Default should be preserved"); + // Default is stored as a JsonNode internally + assertTrue(extended.getDefault() instanceof com.fasterxml.jackson.databind.node.ObjectNode); + } +} diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverNullHandlingTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverNullHandlingTest.java new file mode 100644 index 0000000000..6ddf2fa7db --- /dev/null +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverNullHandlingTest.java @@ -0,0 +1,387 @@ +package io.swagger.v3.parser.test; + +import io.swagger.v3.core.util.Json; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.parser.OpenAPIV3Parser; +import io.swagger.v3.parser.core.models.ParseOptions; +import io.swagger.v3.parser.core.models.SwaggerParseResult; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Map; + +import static org.testng.Assert.*; + +/** + * Tests for null handling in OpenAPI resolver with references and schema resolution. + * These tests verify that example: null and default: null are properly handled + * during the resolution process, including with $ref references. + */ +public class OpenAPIResolverNullHandlingTest { + + @Test + public void testSchemaWithExplicitNullExample() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: Success\n" + + " content:\n" + + " application/json:\n" + + " schema:\n" + + " $ref: '#/components/schemas/TestSchema'\n" + + "components:\n" + + " schemas:\n" + + " TestSchema:\n" + + " type: object\n" + + " example: null\n" + + " properties:\n" + + " id:\n" + + " type: string\n" + + " value:\n" + + " type: string\n" + + " example: null\n"; + + ParseOptions options = new ParseOptions(); + options.setResolve(true); + options.setResolveFully(true); + + SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options); + OpenAPI openAPI = result.getOpenAPI(); + + assertNotNull(openAPI); + Schema testSchema = openAPI.getComponents().getSchemas().get("TestSchema"); + assertNotNull(testSchema); + assertNull(testSchema.getExample()); + + Schema valueProperty = (Schema) testSchema.getProperties().get("value"); + assertNotNull(valueProperty); + assertNull(valueProperty.getExample()); + + Schema responseSchema = openAPI.getPaths().get("/test").getGet() + .getResponses().get("200").getContent().get("application/json").getSchema(); + assertNull(responseSchema.getExample()); + } + + @Test + public void testSchemaWithExplicitNullDefault() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " post:\n" + + " requestBody:\n" + + " content:\n" + + " application/json:\n" + + " schema:\n" + + " $ref: '#/components/schemas/TestSchema'\n" + + " responses:\n" + + " '200':\n" + + " description: Success\n" + + "components:\n" + + " schemas:\n" + + " TestSchema:\n" + + " type: object\n" + + " default: null\n" + + " properties:\n" + + " status:\n" + + " type: string\n" + + " default: null\n" + + " count:\n" + + " type: integer\n" + + " default: null\n"; + + ParseOptions options = new ParseOptions(); + options.setResolve(true); + options.setResolveFully(true); + + SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options); + OpenAPI openAPI = result.getOpenAPI(); + + + assertNotNull(openAPI); + Schema testSchema = openAPI.getComponents().getSchemas().get("TestSchema"); + assertNotNull(testSchema); + assertNull(testSchema.getDefault()); + + Schema statusProperty = (Schema) testSchema.getProperties().get("status"); + assertNotNull(statusProperty); + assertNull(statusProperty.getDefault()); + + Schema countProperty = (Schema) testSchema.getProperties().get("count"); + assertNotNull(countProperty); + assertNull(countProperty.getDefault()); + + Schema requestBodySchema = openAPI.getPaths().get("/test").getPost() + .getRequestBody().getContent().get("application/json").getSchema(); + assertNull(requestBodySchema.getDefault()); + } + + @Test + public void testAllOfWithNullExampleAndDefault() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " BaseSchema:\n" + + " type: object\n" + + " example: null\n" + + " default: null\n" + + " properties:\n" + + " id:\n" + + " type: string\n" + + " ExtendedSchema:\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/BaseSchema'\n" + + " - type: object\n" + + " properties:\n" + + " name:\n" + + " type: string\n" + + " example: null\n" + + " default: null\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(true); + + SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options); + OpenAPI openAPI = result.getOpenAPI(); + + assertNotNull(openAPI); + Schema extendedSchema = openAPI.getComponents().getSchemas().get("ExtendedSchema"); + assertNotNull(extendedSchema); + + assertNull(extendedSchema.getExample()); + assertNull(extendedSchema.getDefault()); + + if (extendedSchema.getProperties() != null) { + Schema nameProperty = (Schema) extendedSchema.getProperties().get("name"); + if (nameProperty != null) { + assertNull(nameProperty.getExample()); + assertNull(nameProperty.getDefault()); + } + } + } + + @Test + public void testParameterWithNullExampleAndDefault() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /test:\n" + + " get:\n" + + " parameters:\n" + + " - name: testParam\n" + + " in: query\n" + + " schema:\n" + + " type: string\n" + + " example: null\n" + + " default: null\n" + + " - $ref: '#/components/parameters/RefParam'\n" + + " responses:\n" + + " '200':\n" + + " description: Success\n" + + "components:\n" + + " parameters:\n" + + " RefParam:\n" + + " name: refParam\n" + + " in: query\n" + + " schema:\n" + + " type: integer\n" + + " example: null\n" + + " default: null\n"; + + ParseOptions options = new ParseOptions(); + options.setResolve(true); + options.setResolveFully(true); + + SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options); + OpenAPI openAPI = result.getOpenAPI(); + + assertNotNull(openAPI); + List parameters = openAPI.getPaths().get("/test").getGet().getParameters(); + assertNotNull(parameters); + assertEquals(2, parameters.size()); + + Parameter testParam = parameters.get(0); + assertNotNull(testParam.getSchema()); + assertNull(testParam.getSchema().getExample()); + assertNull(testParam.getSchema().getDefault()); + + Parameter refParam = parameters.get(1); + assertNotNull(refParam.getSchema()); + assertNull(refParam.getSchema().getExample()); + assertNull(refParam.getSchema().getDefault()); + } + + + @Test + public void testNullDefaultNotPropagatedInAllOf() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " BaseSchema:\n" + + " type: object\n" + + " properties:\n" + + " id:\n" + + " type: string\n" + + " ExtendedSchema:\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/BaseSchema'\n" + + " - type: object\n" + + " properties:\n" + + " name:\n" + + " type: string\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(true); + + SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options); + OpenAPI openAPI = result.getOpenAPI(); + + assertNotNull(openAPI); + Schema extendedSchema = openAPI.getComponents().getSchemas().get("ExtendedSchema"); + assertNotNull(extendedSchema); + + assertNull(extendedSchema.getDefault()); + + + if (extendedSchema.getProperties() != null) { + Schema idProperty = (Schema) extendedSchema.getProperties().get("id"); + Schema nameProperty = (Schema) extendedSchema.getProperties().get("name"); + if (idProperty != null) { + assertNull(idProperty.getDefault()); + } + if (nameProperty != null) { + assertNull(nameProperty.getDefault()); + } + } + + } + + + @Test + public void testSourceDefaultWhenResolvedIsNull() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " BaseSchema:\n" + + " type: object\n" + + " properties:\n" + + " count:\n" + + " type: integer\n" + + " ExtendedSchema:\n" + + " allOf:\n" + + " - $ref: '#/components/schemas/BaseSchema'\n" + + " default:\n" + + " count: 10\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(true); + + SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options); + OpenAPI openAPI = result.getOpenAPI(); + + assertNotNull(openAPI); + Schema extendedSchema = openAPI.getComponents().getSchemas().get("ExtendedSchema"); + assertNotNull(extendedSchema); + + assertNotNull(extendedSchema.getDefault()); + } + + @Test + public void testOneOfNullDefaultNotPropagated() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " StringSchema:\n" + + " type: string\n" + + " NumberSchema:\n" + + " type: number\n" + + " UnionSchema:\n" + + " oneOf:\n" + + " - $ref: '#/components/schemas/StringSchema'\n" + + " - $ref: '#/components/schemas/NumberSchema'\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(false); + + SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options); + OpenAPI openAPI = result.getOpenAPI(); + + assertNotNull(openAPI); + Schema unionSchema = openAPI.getComponents().getSchemas().get("UnionSchema"); + assertNotNull(unionSchema); + + assertNull(unionSchema.getDefault()); + } + + @Test + public void testAnyOfNonNullDefaultPreserved() { + String yaml = "openapi: 3.0.0\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}\n" + + "components:\n" + + " schemas:\n" + + " Schema1:\n" + + " type: object\n" + + " properties:\n" + + " prop1:\n" + + " type: string\n" + + " Schema2:\n" + + " type: object\n" + + " properties:\n" + + " prop2:\n" + + " type: integer\n" + + " CombinedSchema:\n" + + " anyOf:\n" + + " - $ref: '#/components/schemas/Schema1'\n" + + " - $ref: '#/components/schemas/Schema2'\n" + + " default:\n" + + " prop1: 'test'\n"; + + ParseOptions options = new ParseOptions(); + options.setResolveFully(true); + options.setResolveCombinators(false); + + SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options); + OpenAPI openAPI = result.getOpenAPI(); + + assertNotNull(openAPI); + Schema combinedSchema = openAPI.getComponents().getSchemas().get("CombinedSchema"); + assertNotNull(combinedSchema); + + assertNotNull(combinedSchema.getDefault()); + } +} diff --git a/pom.xml b/pom.xml index ff044276fd..150bf646b5 100644 --- a/pom.xml +++ b/pom.xml @@ -294,6 +294,16 @@ + + io.swagger.core.v3 + swagger-core + ${swagger-core-version} + + + io.swagger.core.v3 + swagger-models + ${swagger-core-version} + org.yaml snakeyaml @@ -365,7 +375,7 @@ 1.0.75 2.20.0 2.0.9 - 2.2.37 + 2.2.44 1.6.16 4.13.2 7.11.0