diff --git a/core-tests/e2e-tests/spring/spring-rest-bb/src/main/resources/static/openapi-dtonull.json b/core-tests/e2e-tests/spring/spring-rest-bb/src/main/resources/static/openapi-dtonull.json new file mode 100644 index 0000000000..d4f09baeeb --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-bb/src/main/resources/static/openapi-dtonull.json @@ -0,0 +1,58 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "Generated server url" + } + ], + "paths": { + "/api/bbdtonull/items": { + "post": { + "tags": [ + "dto-null-application" + ], + "operationId": "post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DtoNullDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "DtoNullDto": { + "type": "object", + "properties": { + "x": { + "type": ["integer", "null"], + "format": "int32" + } + } + } + } + } +} \ No newline at end of file diff --git a/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/com/foo/rest/examples/bb/dtonull/BBDtoNullController.kt b/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/com/foo/rest/examples/bb/dtonull/BBDtoNullController.kt index 6606740ce8..1c1cb1aaba 100644 --- a/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/com/foo/rest/examples/bb/dtonull/BBDtoNullController.kt +++ b/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/com/foo/rest/examples/bb/dtonull/BBDtoNullController.kt @@ -1,5 +1,16 @@ package com.foo.rest.examples.bb.dtonull import com.foo.rest.examples.bb.SpringController +import org.evomaster.client.java.controller.problem.ProblemInfo +import org.evomaster.client.java.controller.problem.RestProblem -class BBDtoNullController : SpringController(DtoNullApplication::class.java) \ No newline at end of file +class BBDtoNullController : SpringController(DtoNullApplication::class.java){ + + override fun getProblemInfo(): ProblemInfo { + return RestProblem( + //the one generated by old Spring is wrong + "http://localhost:$sutPort/openapi-dtonull.json", null + ) + } + +} \ No newline at end of file diff --git a/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/dtonull/BBDtoNullEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/dtonull/BBDtoNullEMTest.kt index ad5b24b4ff..186421a6c0 100644 --- a/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/dtonull/BBDtoNullEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/dtonull/BBDtoNullEMTest.kt @@ -34,22 +34,22 @@ class BBDtoNullEMTest : SpringTestBase() { executeAndEvaluateBBTest( outputFormat, "dtonull", - 100, + 1000, 3, - listOf("UNDEFINED", - //FIXME currently we do not handle NULL :( -// "NULL", - "POSITIVE","NEGATIVE") + listOf("UNDEFINED", "NULL", "POSITIVE","NEGATIVE") ){ args: MutableList -> - setOption(args, "dtoForRequestPayload", "true") + setOption(args, "bbSwaggerUrl", "$baseUrlOfSut/openapi-dtonull.json") + + //TODO need to fix/extend DTO handling before activating it here +// setOption(args, "dtoForRequestPayload", "true") + setOption(args, "dtoForRequestPayload", "false") val solution = initAndRun(args) assertTrue(solution.individuals.size >= 1) assertHasAtLeastOne(solution, HttpVerb.POST, 400, "/api/bbdtonull/items", "UNDEFINED") - //FIXME currently we do not handle NULL :( - //assertHasAtLeastOne(solution, HttpVerb.POST, 409, "/api/bbdtonull/items", "NULL") + assertHasAtLeastOne(solution, HttpVerb.POST, 409, "/api/bbdtonull/items", "NULL") assertHasAtLeastOne(solution, HttpVerb.POST, 200, "/api/bbdtonull/items", "POSITIVE") assertHasAtLeastOne(solution, HttpVerb.POST, 201, "/api/bbdtonull/items", "NEGATIVE") } diff --git a/core/src/main/kotlin/org/evomaster/core/output/formatter/OutputFormatter.kt b/core/src/main/kotlin/org/evomaster/core/output/formatter/OutputFormatter.kt index 4897105f6d..9daad11fa9 100644 --- a/core/src/main/kotlin/org/evomaster/core/output/formatter/OutputFormatter.kt +++ b/core/src/main/kotlin/org/evomaster/core/output/formatter/OutputFormatter.kt @@ -1,6 +1,7 @@ package org.evomaster.core.output.formatter import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.google.gson.GsonBuilder import com.google.gson.JsonParser @@ -34,8 +35,14 @@ open abstract class OutputFormatter (val name: String) { } val JSON_FORMATTER = object : OutputFormatter("JSON_FORMATTER"){ - val gson = GsonBuilder().setPrettyPrinting().create() + /* + GSON does not follow standard for JSON. + Should not be used for validation. + https://stackoverflow.com/questions/43233898/how-to-check-if-json-is-valid-in-java-using-gson + */ val objectMapper = ObjectMapper() + //also Jackson by default is happy to accept garbage :( + .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) override fun isValid(content: String): Boolean{ @@ -45,22 +52,15 @@ open abstract class OutputFormatter (val name: String) { } catch (e: JsonProcessingException) { false } - /* - GSON does not follow standard for JSON. - Should not be used for validation. - https://stackoverflow.com/questions/43233898/how-to-check-if-json-is-valid-in-java-using-gson - */ -// return try{ -// gson.fromJson(content, Object::class.java) -// true -// }catch (e : JsonSyntaxException ) { -// false -// } } override fun getFormatted(content: String): String{ if(this.isValid(content)){ - return gson.toJson(JsonParser.parseString(content)) + return objectMapper.readTree(content) + .toPrettyString() + //on Windows, Jackson "might" use CRLF, + //which can be problematic + .replace("\r\n", "\n") } throw MismatchedFormatException(this, content) } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt index bf5caeb8fa..3cc76ead92 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt @@ -51,6 +51,7 @@ import org.evomaster.core.search.gene.regex.RegexGene import org.evomaster.core.search.gene.string.Base64StringGene import org.evomaster.core.search.gene.string.StringGene import org.evomaster.core.search.gene.utils.GeneUtils +import org.evomaster.core.search.gene.wrapper.NullableGene import org.slf4j.Logger import org.slf4j.LoggerFactory import java.net.URI @@ -764,11 +765,16 @@ object RestActionBuilderV3 { } private fun possiblyOptional(gene: Gene, required: Boolean?): Gene { - if (required != true) { return OptionalGene(gene.name, gene).also { GeneUtils.preventCycles(it) } } + return gene + } + private fun possiblyNullable(gene: Gene, nullable: Boolean) : Gene{ + if(nullable) { + return NullableGene(gene.name, gene).also { GeneUtils.preventCycles(it) } + } return gene } @@ -789,7 +795,44 @@ object RestActionBuilderV3 { return createObjectFromReference(name, schema.`$ref`, schemaHolder,currentSchema, history, options, examples, messages) } + /* + Nullability is different between 3.0.0 and 3.1.0. + Also, it makes sense ONLY for body payloads... as it is not possible to express null values + in a URL (eg query and path), as those have ambiguous interpretation, eg, + something like /api?x or /api?x= would in most cases treated as an empty string "". + */ + val nullable_3_0 = (schema.nullable != null && schema.nullable) + val nullable_3_1 = schema.types?.contains("null") ?: false + val nullable = nullable_3_0 || nullable_3_1 + + val gene = getNonNullGene( + schema, + name, + options, + messages, + isInPath, + examples, + schemaHolder, + currentSchema, + history, + referenceClassDef + ) + + return possiblyNullable(gene, nullable) + } + private fun getNonNullGene( + schema: Schema<*>, + name: String, + options: Options, + messages: MutableList, + isInPath: Boolean, + examples: List>, + schemaHolder: RestSchema, + currentSchema: SchemaOpenAPI, + history: Deque, + referenceClassDef: String? + ): Gene { /* https://github.com/OAI/OpenAPI-Specification/blob/3.0.1/versions/3.0.1.md#dataTypeFormat @@ -807,7 +850,8 @@ object RestActionBuilderV3 { password string password Used to hint UIs the input needs to be obscured. */ - val type = schema.type?:schema.types?.firstOrNull() + + val type = schema.type ?: schema.types?.firstOrNull { it != "null" } val format = schema.format if (schema.enum?.isNotEmpty() == true) { @@ -835,26 +879,28 @@ object RestActionBuilderV3 { */ "integer" -> { if (format == "int64") { - val data : MutableList = schema.enum - .map{ if(it is String) it.toLong() else it as Long} + val data: MutableList = schema.enum + .map { if (it is String) it.toLong() else it as Long } .toMutableList() return EnumGene(name, (data).apply { add(42L) }) } - val data : MutableList = schema.enum - .map{ if(it is String) it.toInt() else it as Int} + val data: MutableList = schema.enum + .map { if (it is String) it.toInt() else it as Int } .toMutableList() return EnumGene(name, data.apply { add(42) }) } + "number" -> { //if (format == "double" || format == "float") { //TODO: Is it always casted as Double even for Float??? Need test - val data : MutableList = schema.enum - .map{ if(it is String) it.toDouble() else it as Double} + val data: MutableList = schema.enum + .map { if (it is String) it.toDouble() else it as Double } .toMutableList() return EnumGene(name, data.apply { add(42.0) }) } + else -> messages.add("Cannot handle enum of type: $type") } } @@ -863,16 +909,80 @@ object RestActionBuilderV3 { //first check for "optional" format when (format?.lowercase()) { "char" -> return buildStringGeneForChar(name, isInPath) - "int8","int16","int32" -> return createNonObjectGeneWithSchemaConstraints(schema, name, IntegerGene::class.java, options, null, isInPath, examples,format, messages)//IntegerGene(name) - "int64" -> return createNonObjectGeneWithSchemaConstraints(schema, name, LongGene::class.java, options, null, isInPath, examples, messages = messages) //LongGene(name) - "double" -> return createNonObjectGeneWithSchemaConstraints(schema, name, DoubleGene::class.java, options, null, isInPath, examples, messages = messages)//DoubleGene(name) - "float" -> return createNonObjectGeneWithSchemaConstraints(schema, name, FloatGene::class.java, options, null, isInPath, examples, messages = messages)//FloatGene(name) - "password" -> return createNonObjectGeneWithSchemaConstraints(schema, name, StringGene::class.java, options, null, isInPath, examples, messages = messages)//StringGene(name) //nothing special to do, it is just a hint - "binary" -> return createNonObjectGeneWithSchemaConstraints(schema, name, StringGene::class.java, options, null, isInPath, examples, messages = messages)//StringGene(name) //does it need to be treated specially? - "byte" -> return createNonObjectGeneWithSchemaConstraints(schema, name, Base64StringGene::class.java, options, null, isInPath, examples, messages = messages)//Base64StringGene(name) + "int8", "int16", "int32" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + IntegerGene::class.java, + options, + null, + isInPath, + examples, + format, + messages + )//IntegerGene(name) + "int64" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + LongGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + ) //LongGene(name) + "double" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + DoubleGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + )//DoubleGene(name) + "float" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + FloatGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + )//FloatGene(name) + "password" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + StringGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + )//StringGene(name) //nothing special to do, it is just a hint + "binary" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + StringGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + )//StringGene(name) //does it need to be treated specially? + "byte" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + Base64StringGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + )//Base64StringGene(name) "date", "local-date" -> return DateGene(name, onlyValidDates = !options.invalidData) "date-time", "local-date-time" -> { - val f = if(format?.lowercase() == "date-time"){ + val f = if (format?.lowercase() == "date-time") { FormatForDatesAndTimes.RFC3339 } else { FormatForDatesAndTimes.ISO_LOCAL @@ -884,6 +994,7 @@ object RestActionBuilderV3 { time = TimeGene("time", onlyValidTimes = !options.invalidData, format = f) ) } + else -> if (format != null) { messages.add("Unhandled format '$format' for '$name'") } @@ -894,15 +1005,51 @@ object RestActionBuilderV3 { the JSON Schema definition */ when (type?.lowercase()) { - "integer" -> return createNonObjectGeneWithSchemaConstraints(schema, name, IntegerGene::class.java, options, null, isInPath, examples, messages = messages)//IntegerGene(name) - "number" -> return createNonObjectGeneWithSchemaConstraints(schema, name, DoubleGene::class.java, options, null, isInPath, examples, messages = messages)//DoubleGene(name) + "integer" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + IntegerGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + )//IntegerGene(name) + "number" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + DoubleGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + )//DoubleGene(name) "boolean" -> return BooleanGene(name) "string" -> { return if (schema.pattern == null) { - createNonObjectGeneWithSchemaConstraints(schema, name, StringGene::class.java, options, null, isInPath, examples, messages = messages) //StringGene(name) + createNonObjectGeneWithSchemaConstraints( + schema, + name, + StringGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + ) //StringGene(name) } else { try { - createNonObjectGeneWithSchemaConstraints(schema, name, RegexGene::class.java, options, null, isInPath, examples, messages = messages) + createNonObjectGeneWithSchemaConstraints( + schema, + name, + RegexGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + ) } catch (e: Exception) { /* TODO: if the Regex is syntactically invalid, we should warn @@ -912,17 +1059,28 @@ object RestActionBuilderV3 { When 100% support, then tell user that it is his/her fault */ LoggingUtil.uniqueWarn(log, "Cannot handle regex: ${schema.pattern}") - createNonObjectGeneWithSchemaConstraints(schema, name, StringGene::class.java, options, null, isInPath, examples, messages = messages)//StringGene(name) + createNonObjectGeneWithSchemaConstraints( + schema, + name, + StringGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + )//StringGene(name) } } } + "array" -> { if (schema is ArraySchema || schema is JsonSchema) { val arrayType: Schema<*> = if (schema.items == null) { LoggingUtil.uniqueWarn( log, "Array type '$name' is missing mandatory field 'items' to define its type." + - " Defaulting to 'string'") + " Defaulting to 'string'" + ) Schema().also { it.type = "string" } } else { schema.items @@ -931,11 +1089,29 @@ object RestActionBuilderV3 { // Use the XML name from schema.xml.name (the name of the array element in XML) // if available, otherwise fallback to name + "_item" val itemName = schema.xml?.name ?: (name + "_item") - val template = getGene(itemName, arrayType, schemaHolder,currentSchema, history, referenceClassDef = null, options = options, messages = messages)//Could still have an empty [] + val template = getGene( + itemName, + arrayType, + schemaHolder, + currentSchema, + history, + referenceClassDef = null, + options = options, + messages = messages + )//Could still have an empty [] //if (template is CycleObjectGene) { //return CycleObjectGene(" ${template.name}") //} - return createNonObjectGeneWithSchemaConstraints(schema, name, ArrayGene::class.java, options, template, isInPath, examples, messages = messages)//ArrayGene(name, template) + return createNonObjectGeneWithSchemaConstraints( + schema, + name, + ArrayGene::class.java, + options, + template, + isInPath, + examples, + messages = messages + )//ArrayGene(name, template) } else { LoggingUtil.uniqueWarn(log, "Invalid 'array' definition for '$name'") } @@ -973,13 +1149,32 @@ object RestActionBuilderV3 { additionalFields = null, attributeNames = attributeNames ) - }else{ - return createObjectGene(name, schema, schemaHolder,currentSchema, history, referenceClassDef, options, examples, messages) + } else { + return createObjectGene( + name, + schema, + schemaHolder, + currentSchema, + history, + referenceClassDef, + options, + examples, + messages + ) } } //TODO file is a hack. I want to find a more elegant way of dealing with it (BMR) //FIXME is this even a standard type??? - "file" -> return createNonObjectGeneWithSchemaConstraints(schema, name, StringGene::class.java, options, null, isInPath, examples, messages = messages) //StringGene(name) + "file" -> return createNonObjectGeneWithSchemaConstraints( + schema, + name, + StringGene::class.java, + options, + null, + isInPath, + examples, + messages = messages + ) //StringGene(name) } if ((name == "body" || referenceClassDef != null) && schema.properties?.isNotEmpty() == true) { @@ -987,13 +1182,24 @@ object RestActionBuilderV3 { name == "body": This could happen when parsing a body-payload as formData referenceClassDef != null : this could happen when parsing a reference of a constraint (eg, anyOf) of the additionalProperties */ - return createObjectGene(name, schema, schemaHolder,currentSchema, history, referenceClassDef, options, examples, messages) + return createObjectGene( + name, + schema, + schemaHolder, + currentSchema, + history, + referenceClassDef, + options, + examples, + messages + ) } if (type == null && format == null) { return createGeneWithUnderSpecificTypeAndSchemaConstraints( - schema, name, schemaHolder,currentSchema, history, referenceClassDef, - options, null, isInPath, examples, messages) + schema, name, schemaHolder, currentSchema, history, referenceClassDef, + options, null, isInPath, examples, messages + ) //createNonObjectGeneWithSchemaConstraints(schema, name, StringGene::class.java, enableConstraintHandling) //StringGene(name) } diff --git a/core/src/test/kotlin/org/evomaster/core/output/formatter/OutputFormatterTest.kt b/core/src/test/kotlin/org/evomaster/core/output/formatter/OutputFormatterTest.kt index 9eb1769e64..825d4befe7 100644 --- a/core/src/test/kotlin/org/evomaster/core/output/formatter/OutputFormatterTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/output/formatter/OutputFormatterTest.kt @@ -116,10 +116,10 @@ class OutputFormatterTest { val formatted = OutputFormatter.JSON_FORMATTER.getFormatted(json) val expected = """ { - "id": 4821943963580588583, - "name": "n4QtYI", - "rdId": 937, - "value": 859 + "id" : 4821943963580588583, + "name" : "n4QtYI", + "rdId" : 937, + "value" : 859 } """.trimIndent() assertEquals(expected, formatted) diff --git a/core/src/test/kotlin/org/evomaster/core/problem/graphql/GQLActionTest.kt b/core/src/test/kotlin/org/evomaster/core/problem/graphql/GQLActionTest.kt index 6d28e7e6ef..6d1f65153a 100644 --- a/core/src/test/kotlin/org/evomaster/core/problem/graphql/GQLActionTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/problem/graphql/GQLActionTest.kt @@ -21,12 +21,15 @@ class GQLActionTest { val expected = """ { - "query": " mutation{ add (foo : \"foo\\\"\") } " + "query": " mutation{ add (foo : \"foo\\\"\") } ", + "variables":null } """.trimIndent() + .let{ OutputFormatter.JSON_FORMATTER.getFormatted(it)} assertNotNull(body) - assertEquals(expected, OutputFormatter.JSON_FORMATTER.getFormatted(body!!.entity)) + val result = OutputFormatter.JSON_FORMATTER.getFormatted(body!!.entity) + assertEquals(expected, result) } -} \ No newline at end of file +} diff --git a/core/src/test/kotlin/org/evomaster/core/problem/rest/RestActionBuilderV3Test.kt b/core/src/test/kotlin/org/evomaster/core/problem/rest/RestActionBuilderV3Test.kt index 03803138d1..e87a305434 100644 --- a/core/src/test/kotlin/org/evomaster/core/problem/rest/RestActionBuilderV3Test.kt +++ b/core/src/test/kotlin/org/evomaster/core/problem/rest/RestActionBuilderV3Test.kt @@ -29,6 +29,7 @@ import org.evomaster.core.search.gene.wrapper.ChoiceGene import org.evomaster.core.search.gene.wrapper.OptionalGene import org.evomaster.core.search.gene.placeholder.CycleObjectGene import org.evomaster.core.search.gene.string.StringGene +import org.evomaster.core.search.gene.wrapper.NullableGene import org.evomaster.core.search.service.Randomness import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach @@ -2193,4 +2194,46 @@ class RestActionBuilderV3Test{ } } } + + @Test + fun testNullable_optional_3_0(){ + val map = loadAndAssertActions("/swagger/artificial/migration_3_0_to_3_1/nullable_optional_3_0.yaml", 1, true) + testNullable(map, true) + } + + @Test + fun testNullable_required_3_0(){ + val map = loadAndAssertActions("/swagger/artificial/migration_3_0_to_3_1/nullable_required_3_0.yaml", 1, true) + testNullable(map, false) + } + + @Test + fun testNullable_optional_3_1(){ + val map = loadAndAssertActions("/swagger/artificial/migration_3_0_to_3_1/nullable_optional_3_1.yaml", 1, true) + testNullable(map, true) + } + + @Test + fun testNullable_required_3_1(){ + val map = loadAndAssertActions("/swagger/artificial/migration_3_0_to_3_1/nullable_required_3_1.yaml", 1, true) + testNullable(map, false) + } + + private fun testNullable(map: MutableMap, optional: Boolean) { + val get = map["POST:/api"] as RestCallAction + assertEquals(1, get.parameters.size) + + val body = get.parameters.filterIsInstance().first() + val payload = body.primaryGene() as ObjectGene + val x = payload.getField("x")!! + + assertNotNull(x.getWrappedGene(StringGene::class.java)) + + if(optional) { + assertNotNull(x.getWrappedGene(OptionalGene::class.java)) + } else { + assertNull(x.getWrappedGene(OptionalGene::class.java)) + } + assertNotNull(x.getWrappedGene(NullableGene::class.java)) + } } diff --git a/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_optional_3_0.yaml b/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_optional_3_0.yaml new file mode 100644 index 0000000000..fd3225960f --- /dev/null +++ b/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_optional_3_0.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.0 +info: + title: Minimal API + version: 1.0.0 +paths: + /api: + post: + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + x: + type: string + nullable: true + required: [] + responses: + '200': + description: OK \ No newline at end of file diff --git a/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_optional_3_1.yaml b/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_optional_3_1.yaml new file mode 100644 index 0000000000..a9ccdd6a53 --- /dev/null +++ b/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_optional_3_1.yaml @@ -0,0 +1,20 @@ +openapi: 3.1.0 +info: + title: Minimal API + version: 1.0.0 +paths: + /api: + post: + requestBody: + required: true + content: + application/json: + schema: + required: [] + type: object + properties: + x: + type: ["string", "null"] + responses: + '200': + description: OK \ No newline at end of file diff --git a/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_required_3_0.yaml b/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_required_3_0.yaml new file mode 100644 index 0000000000..6bf0f6c0f5 --- /dev/null +++ b/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_required_3_0.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.0 +info: + title: Minimal API + version: 1.0.0 +paths: + /api: + post: + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + x: + type: string + nullable: true + required: ["x"] + responses: + '200': + description: OK \ No newline at end of file diff --git a/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_required_3_1.yaml b/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_required_3_1.yaml new file mode 100644 index 0000000000..c9ef06fc95 --- /dev/null +++ b/core/src/test/resources/swagger/artificial/migration_3_0_to_3_1/nullable_required_3_1.yaml @@ -0,0 +1,20 @@ +openapi: 3.1.0 +info: + title: Minimal API + version: 1.0.0 +paths: + /api: + post: + requestBody: + required: true + content: + application/json: + schema: + required: ["x"] + type: object + properties: + x: + type: ["null", "string"] + responses: + '200': + description: OK \ No newline at end of file