From 85847d341f2929470496081e1adb0b4a943ff75a Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Wed, 18 May 2022 13:23:36 +0400 Subject: [PATCH 01/34] OneOf, AllOf, AnyOf implemented and supports objects --- .../th2/codec/openapi/OpenApiCodec.kt | 86 +++++++++++------- .../th2/codec/openapi/utils/OpenApiUtils.kt | 32 ++++--- .../th2/codec/openapi/writer/SchemaWriter.kt | 91 ++++++++++++------- .../openapi/writer/visitors/SchemaVisitor.kt | 1 + .../openapi/writer/visitors/VisitorFactory.kt | 45 +++++++-- .../visitors/json/DecodeJsonArrayVisitor.kt | 2 + .../visitors/json/DecodeJsonObjectVisitor.kt | 5 + .../visitors/json/EncodeJsonArrayVisitor.kt | 2 + .../visitors/json/EncodeJsonObjectVisitor.kt | 5 + .../openapi/ValidationDictionaryTests.kt | 6 +- .../json/encode/JsonObjectEncodeTests.kt | 39 ++++++++ .../dictionaries/valid/valid-dictionary.yml | 22 +++++ 12 files changed, 247 insertions(+), 89 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index e291248..e5555aa 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -23,10 +23,12 @@ import com.exactpro.th2.codec.openapi.schemacontainer.RequestContainer import com.exactpro.th2.codec.openapi.schemacontainer.ResponseContainer import com.exactpro.th2.codec.openapi.throwable.DecodeException import com.exactpro.th2.codec.openapi.throwable.EncodeException +import com.exactpro.th2.codec.openapi.utils.containingFormatOrNull import com.exactpro.th2.codec.openapi.utils.extractType +import com.exactpro.th2.codec.openapi.utils.getByMethod import com.exactpro.th2.codec.openapi.utils.getEndPoint import com.exactpro.th2.codec.openapi.utils.getMethods -import com.exactpro.th2.codec.openapi.utils.getSchema +import com.exactpro.th2.codec.openapi.utils.validateForType import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.VisitorFactory import com.exactpro.th2.common.grpc.MessageGroup @@ -77,7 +79,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin // Request methodValue.requestBody?.content?.forEach { (typeKey, typeValue) -> mapForName[combineName(pathKey, methodKey, typeKey)] = RequestContainer( - pattern, methodKey, dictionary.getEndPoint(typeValue.schema), typeKey, resultParams + pattern, methodKey, dictionary.getEndPoint(typeValue.schema).validateForType(), typeKey, resultParams ) if (methodValue.requestBody.required /*nullable*/ == false) { @@ -91,7 +93,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin methodValue.responses?.forEach { (responseKey, responseValue) -> responseValue.content?.forEach { (typeKey, typeValue) -> mapForName[combineName(pathKey, methodKey, responseKey, typeKey)] = ResponseContainer( - pattern, methodKey, responseKey, dictionary.getEndPoint(typeValue.schema), typeKey, resultParams + pattern, methodKey, responseKey, dictionary.getEndPoint(typeValue.schema).validateForType(), typeKey, resultParams ) } ?: run { mapForName[combineName(pathKey, methodKey, responseKey)] = ResponseContainer(pattern, methodKey, responseKey, null, null, resultParams) @@ -150,9 +152,8 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin } LOGGER.debug { "Start of message encoding: ${message.messageType}" } - checkNotNull(messageSchema.type) {"Type of schema [${messageSchema.name}] wasn't filled"} - val visitor = VisitorFactory.createEncodeVisitor(container.bodyFormat!!, messageSchema.type, message) + val visitor = VisitorFactory.createEncodeVisitor(container.bodyFormat!!, messageSchema, message, dictionary) schemaWriter.traverse(visitor, messageSchema) val result = visitor.getResult() @@ -198,57 +199,72 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin private fun decodeBody(header: Message, rawMessage: RawMessage): Message { val body = rawMessage.body + val (messageType, container) = searchContainer(header, rawMessage.metadata) + val schema = checkNotNull(container.body) { "Container: $messageType did not contain schema body" } + val format = checkNotNull(container.bodyFormat) { "Container: $messageType did not contain schema bodyFormat" } - val bodyFormat = header.getList(HEADERS_FIELD) + val visitor = VisitorFactory.createDecodeVisitor(format, schema, body, dictionary) + schemaWriter.traverse(visitor, schema) + return visitor.getResult().apply { + if(rawMessage.hasParentEventId()) parentEventId = rawMessage.parentEventId + sessionAlias = rawMessage.sessionAlias + this.messageType = messageType + metadataBuilder.apply { + id = rawMessage.metadata.id + timestamp = metadata.timestamp + protocol = rawMessage.metadata.protocol + putAllProperties(rawMessage.metadata.propertiesMap) + } + }.build() + } + + private fun searchContainer(header: Message, rawMetadata: RawMessageMetadata): Pair { + val headerFormat = header.getList(HEADERS_FIELD) ?.first { it.messageValue.getString("name") == "Content-Type" } ?.messageValue ?.getString("value") ?.extractType() ?: "null" - val uri: String val method: String + val schemaFormat: String var code = "" - val pairFound: Pair + val messageType: String + + val patternToPathItem: Pair - val messageSchema = when (header.messageType) { + when (header.messageType) { RESPONSE_MESSAGE -> { - uri = requireNotNull(rawMessage.metadata.propertiesMap[URI_PROPERTY]) { "URI property in metadata from response is required" } - method = requireNotNull(rawMessage.metadata.propertiesMap[METHOD_PROPERTY]?.lowercase()) { "Method property in metadata from response is required" } + val uri = requireNotNull(rawMetadata.propertiesMap[URI_PROPERTY]) { "URI property in metadata from response is required" } + method = requireNotNull(rawMetadata.propertiesMap[METHOD_PROPERTY]?.lowercase()) { "Method property in metadata from response is required" } code = requireNotNull(header.getString(STATUS_CODE_FIELD)) { "Code status field required inside of http response message" } - pairFound = checkNotNull(patternToPathItem.firstOrNull { it.first.matches(uri) }) { "Cannot find path-item by uri: $uri" } - dictionary.getEndPoint(pairFound.second.getSchema(method, code, bodyFormat)) + patternToPathItem = checkNotNull(this.patternToPathItem.firstOrNull { it.first.matches(uri) }) { "Cannot find path-item by uri: $uri" } + val content = patternToPathItem.second.getByMethod(method)?.responses?.get(code)?.content + schemaFormat = checkNotNull(content?.containingFormatOrNull(headerFormat)) { + "Schema Response [${patternToPathItem.first.pattern}] with method: [$method] and code: [$code] did not contain type $headerFormat" + } + messageType = combineName(patternToPathItem.first.pattern, method, code, schemaFormat) } REQUEST_MESSAGE -> { - uri = requireNotNull(header.getString(URI_FIELD)) { "URI field in request is required" } + val uri = requireNotNull(header.getString(URI_FIELD)) { "URI field in request is required" } method = requireNotNull(header.getString(METHOD_FIELD)) { "Method field in request is required" } - pairFound = checkNotNull(patternToPathItem.firstOrNull { it.first.matches(uri) }) { "Cannot find path-item by uri: $uri" } - dictionary.getEndPoint(pairFound.second.getSchema(method, null, bodyFormat)) + patternToPathItem = checkNotNull(this.patternToPathItem.firstOrNull { it.first.matches(uri) }) { "Cannot find path-item by uri: $uri" } + val content = patternToPathItem.second.getByMethod(method)?.requestBody?.content + schemaFormat = checkNotNull(content?.containingFormatOrNull(headerFormat)) { + "Schema Response [${patternToPathItem.first.pattern}] with method: [$method] did not contain type $headerFormat" + } + messageType = combineName(patternToPathItem.first.pattern, method, code, schemaFormat) } - else -> error("Unsupported message type: ${header.messageType}") + else -> error("Unsupported header message type: ${header.messageType}") } - checkNotNull(messageSchema.type) { "Type of schema [${messageSchema.name}] wasn't filled" } - - val type = combineName(pairFound.first.pattern, method, code, bodyFormat) + val container = checkNotNull(typeToSchema[messageType]) { "Container for path: [${patternToPathItem.first.pattern}] with method: [$method], code: [$code] and type [$schemaFormat] wasn't found" } - LOGGER.debug { "Schema for ${header.messageType} with type-name: $type was found" } + LOGGER.debug { "Container for ${header.messageType} with messageType: $messageType was found" } - val visitor = VisitorFactory.createDecodeVisitor(bodyFormat, messageSchema.type, body) - schemaWriter.traverse(visitor, messageSchema) - return visitor.getResult().apply { - if(rawMessage.hasParentEventId()) parentEventId = rawMessage.parentEventId - sessionAlias = rawMessage.sessionAlias - this.messageType = type - metadataBuilder.apply { - id = rawMessage.metadata.id - timestamp = metadata.timestamp - protocol = rawMessage.metadata.protocol - putAllProperties(rawMessage.metadata.propertiesMap) - } - }.build() + return messageType to container } @@ -308,7 +324,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin } } - fun combineName(vararg steps: String) = steps.asSequence().flatMap { it.split(COMBINER_REGEX) }.joinToString("") { it.lowercase().capitalize() } + fun combineName(vararg steps: String) = steps.asSequence().flatMap { it.replace("*","Any").split(COMBINER_REGEX) }.joinToString("") { it.lowercase().capitalize() } companion object { private val LOGGER = KotlinLogging.logger { } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt index d29ed39..0cce39f 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt @@ -19,10 +19,16 @@ package com.exactpro.th2.codec.openapi.utils import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.Operation import io.swagger.v3.oas.models.PathItem +import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.Content +import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.parser.models.RefType private val METHODS = listOf("get", "put", "delete", "post") +const val JSON_FORMAT = "application/json" +const val ANY_FORMAT = "*/*" fun PathItem.getByMethod(method: String) = when (method.lowercase()) { "get" -> get @@ -42,9 +48,9 @@ fun OpenAPI.findByRef(ref: String): Schema<*>? { } else error("Unsupported ref type: $ref") } -fun OpenAPI.getEndPoint(schema: Schema<*>): Schema<*> = when (schema.`$ref`) { +fun OpenAPI.getEndPoint(schema: Schema<*>): Schema<*> = when(schema.`$ref`) { null -> schema - else -> findByRef(schema.`$ref`) ?: error("Unsupported schema, no reference was found: ${schema.`$ref`}") + else -> findByRef(schema.`$ref`) ?: error("Unsupported Object schema, no reference was found: ${schema.`$ref`}") } enum class JsonSchemaTypes(val type: String) { @@ -63,15 +69,19 @@ fun Schema<*>.checkEnum(value: T?, name: String) { fun String.extractType() = substringBefore(';').trim() -fun PathItem.getSchema(method: String, code: String?, bodyFormat: String): Schema<*> { - val methodRootSchema = this.getByMethod(method) +fun Content.containingFormatOrNull(httpHeader: String) = when { + httpHeader == JSON_FORMAT && this.contains(ANY_FORMAT) -> ANY_FORMAT + this.contains(httpHeader) -> httpHeader + else -> null +} - val schema = when (code) { - null -> methodRootSchema?.requestBody?.content?.get(bodyFormat)?.schema - else -> methodRootSchema?.responses?.get(code)?.content?.get(bodyFormat)?.schema +fun Schema<*>.validateForType(): Schema<*> { + if (!this.isComposed()) { + checkNotNull(this.type) { "Type of schema [${this.name}] wasn't filled" } } + return this +} - return checkNotNull(schema) { - "Schema with method: [$method], code: [$code] and type [$bodyFormat] wasn't found" - } -} \ No newline at end of file +fun Schema<*>.isComposed() = this is ComposedSchema +fun Schema<*>.isObject() = this is ObjectSchema +fun Schema<*>.isArray() = this is ArraySchema \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 094b5fc..08fcd2e 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -21,12 +21,13 @@ import com.exactpro.th2.codec.openapi.utils.getEndPoint import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.Schema -import io.swagger.v3.parser.util.SchemaTypeUtil.BOOLEAN_TYPE -import io.swagger.v3.parser.util.SchemaTypeUtil.INTEGER_TYPE -import io.swagger.v3.parser.util.SchemaTypeUtil.NUMBER_TYPE -import io.swagger.v3.parser.util.SchemaTypeUtil.OBJECT_TYPE -import io.swagger.v3.parser.util.SchemaTypeUtil.STRING_TYPE +import io.swagger.v3.oas.models.media.StringSchema import java.math.BigDecimal @@ -36,11 +37,21 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val failOnU schemaVisitor: SchemaVisitor<*, *>, msgStructure: Schema<*> ) { + val schema = openApi.getEndPoint(msgStructure) - when (schema.type) { - ARRAY_TYPE -> processProperty(schema, schemaVisitor, ARRAY_TYPE) - OBJECT_TYPE -> { + when { + schema is ComposedSchema -> { + when { + !schema.anyOf.isNullOrEmpty() -> processAnyOf(schema.anyOf, schemaVisitor) + !schema.oneOf.isNullOrEmpty() -> processOneOf(schema.oneOf, schemaVisitor) + !schema.allOf.isNullOrEmpty() -> processAllOf(schema.allOf, schemaVisitor) + } + } + schema is ArraySchema -> { + processArrayProperty(schema, schemaVisitor, ARRAY_TYPE) + } + schema is ObjectSchema -> { requireNotNull(schema.properties) {"Properties in object are required: $schema"} if (failOnUndefined) { schemaVisitor.getUndefinedFields(schema.properties.keys)?.let { @@ -55,26 +66,45 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val failOnU } } + private fun processAllOf(property: List>, visitor: SchemaVisitor<*, *>) { + property.forEach { + traverse(visitor, it) + } + } + + private fun processAnyOf(property: List>, visitor: SchemaVisitor<*, *>) { + val validSchemes = property.filter(visitor::checkAgainst) + check(validSchemes.isNotEmpty()) { "Message wasn't valid for any of shames from 'AnyOf' list: ${property.joinToString(", ") { it.`$ref` }}" } + validSchemes.forEach { + traverse(visitor, it) + } + } + + private fun processOneOf(property: List>, visitor: SchemaVisitor<*, *>) { + val validSchemes = property.filter(visitor::checkAgainst) + check(validSchemes.size == 1) { "Message was valid for more than one shame from 'OneOf' list: ${property.joinToString(", ") { it.`$ref` }}" } + traverse(visitor, validSchemes[0]) + } + private fun processProperty(property: Schema<*>, visitor: SchemaVisitor<*, *>, name: String, required: Boolean = false) { runCatching { - when(property.type) { - ARRAY_TYPE -> processArrayProperty(property as ArraySchema, visitor, name, required) - INTEGER_TYPE -> when (property.format) { + when(property) { + is ArraySchema -> processArrayProperty(property, visitor, name, required) + is StringSchema -> visitor.visit(name, property.default, property, required) + is IntegerSchema -> when (property.format) { "int64" -> visitor.visit(name, property.default as? Long, property, required) null, "", "int32" -> visitor.visit(name, property.default as? Int, property, required) - else -> error("Unsupported format of '$INTEGER_TYPE' property $name: ${property.format}") + else -> error("Unsupported format of '${IntegerSchema::class.simpleName}' property $name: ${property.format}") } - BOOLEAN_TYPE -> visitor.visit(name, property.default as? Boolean, property, required) - NUMBER_TYPE -> when (property.format) { - "float" -> visitor.visit(name, property.default as? Float, property, required) - "double" -> visitor.visit(name, property.default as? Double, property, required) - "-" -> visitor.visit(name, property.default as? BigDecimal, property, required) - null, "" -> visitor.visit(name, property.default as? String, property, required) - else -> error("Unsupported format of '$NUMBER_TYPE' property $name: ${property.format}") + is NumberSchema -> when (property.format) { + "float" ->visitor.visit(name, property.default?.toFloat(), property, required) + "double" -> visitor.visit(name, property.default?.toDouble(), property, required) + null, "" -> visitor.visit(name, property.default?.toString(), property, required) + else -> visitor.visit(name, property.default, property, required) } - STRING_TYPE -> visitor.visit(name, property.default as? String, property, required) - OBJECT_TYPE -> visitor.visit(name, property.default as? Schema<*>, property, required, this) - else -> error("Unsupported type of property") + is BooleanSchema -> visitor.visit(name, property.default, property, required) + is ObjectSchema -> visitor.visit(name, property.default as? Schema<*>, property, required, this) + else -> error("Unsupported class of property: ${property::class.simpleName}") } }.onFailure { throw CodecException("Cannot parse field [$name] inside of schema with type ${property.type}", it) @@ -85,22 +115,21 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val failOnU @Suppress("UNCHECKED_CAST") private fun processArrayProperty(property: ArraySchema, visitor: SchemaVisitor<*, *>, name: String, required: Boolean = false) { runCatching { - when(property.items.type) { - INTEGER_TYPE -> when (property.items.format) { + when(property.items) { + is IntegerSchema -> when (property.items.format) { "int64" -> visitor.visitLongCollection(name, property.default as? List, property, required) null, "", "int32" -> visitor.visitIntegerCollection(name, property.default as? List, property, required) - else -> error("Unsupported format of '$INTEGER_TYPE' property: ${property.format}") + else -> error("Unsupported format of '${IntegerSchema::class.simpleName}' property: ${property.format}") } - BOOLEAN_TYPE -> visitor.visitBooleanCollection(name, property.default as? List, property, required) - NUMBER_TYPE -> when (property.items.format) { + is BooleanSchema -> visitor.visitBooleanCollection(name, property.default as? List, property, required) + is NumberSchema-> when (property.items.format) { "float" -> visitor.visitFloatCollection(name, property.default as? List, property, required) "double" -> visitor.visitDoubleCollection(name, property.default as? List, property, required) null, "" -> visitor.visitStringCollection(name, property.default as? List, property, required) - "-" -> visitor.visitBigDecimalCollection(name, property.default as? List, property, required) - else -> error("Unsupported format of '$NUMBER_TYPE' property: ${property.format}") + else -> visitor.visitBigDecimalCollection(name, property.default as? List, property, required) } - STRING_TYPE -> visitor.visitStringCollection(name, property.default as? List, property, required) - OBJECT_TYPE -> visitor.visitObjectCollection(name, property.default as? List, property, required, this) + is StringSchema -> visitor.visitStringCollection(name, property.default as? List, property, required) + is ObjectSchema -> visitor.visitObjectCollection(name, property.default as? List, property, required, this) else -> error("Unsupported type of property") } }.onFailure { diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt index f60d750..4651a76 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt @@ -42,6 +42,7 @@ sealed class SchemaVisitor { abstract fun visitLongCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false) abstract fun visitObjectCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false, schemaWriter: SchemaWriter) abstract fun getUndefinedFields(fields: MutableSet): Set? + abstract fun checkAgainst(message: Schema<*>): Boolean abstract fun getResult(): ToType diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt index 9797eab..4a86d28 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt @@ -17,39 +17,68 @@ package com.exactpro.th2.codec.openapi.writer.visitors import com.exactpro.th2.codec.openapi.utils.JsonSchemaTypes +import com.exactpro.th2.codec.openapi.utils.getEndPoint import com.exactpro.th2.codec.openapi.writer.visitors.json.DecodeJsonArrayVisitor import com.exactpro.th2.codec.openapi.writer.visitors.json.DecodeJsonObjectVisitor import com.exactpro.th2.codec.openapi.writer.visitors.json.EncodeJsonArrayVisitor import com.exactpro.th2.codec.openapi.writer.visitors.json.EncodeJsonObjectVisitor import com.exactpro.th2.common.grpc.Message import com.google.protobuf.ByteString +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.Schema object VisitorFactory { private const val JSON_FORMAT = "application/json" - fun createEncodeVisitor(format: String, type: String, message: Message): SchemaVisitor.EncodeVisitor<*> { + + fun createEncodeVisitor(format: String, schema: Schema<*>, message: Message, dictionary: OpenAPI): SchemaVisitor.EncodeVisitor<*> { when (format) { JSON_FORMAT -> { - return when (JsonSchemaTypes.getType(type)) { + return when (schema.defineType(dictionary)) { JsonSchemaTypes.ARRAY -> EncodeJsonArrayVisitor(message) JsonSchemaTypes.OBJECT -> EncodeJsonObjectVisitor(message) - else -> error("Unsupported type of json schema [$type], array or object required") } } - else -> error("Unsupported format of message $format") + else -> error("Unsupported format of message $format for encode") } } - fun createDecodeVisitor(format: String, type: String, data: ByteString): SchemaVisitor.DecodeVisitor<*> { + fun createDecodeVisitor(format: String, schema: Schema<*>, data: ByteString, dictionary: OpenAPI): SchemaVisitor.DecodeVisitor<*> { when (format) { JSON_FORMAT -> { - return when (JsonSchemaTypes.getType(type)) { + return when (schema.defineType(dictionary)) { JsonSchemaTypes.ARRAY -> DecodeJsonArrayVisitor(data.toStringUtf8()) JsonSchemaTypes.OBJECT -> DecodeJsonObjectVisitor(data.toStringUtf8()) - else -> error("Unsupported type of json schema [$type], array or object required") } } - else -> error("Unsupported format of message $format") + else -> error("Unsupported format of message $format for decode") + } + } + + private fun Schema<*>.defineType(dictionary: OpenAPI): JsonSchemaTypes { + return when (this) { + is ArraySchema -> JsonSchemaTypes.ARRAY + is ObjectSchema -> JsonSchemaTypes.OBJECT + is ComposedSchema -> { + val schemas = when { + !this.anyOf.isNullOrEmpty() -> this.anyOf + !this.oneOf.isNullOrEmpty() -> this.oneOf + !this.allOf.isNullOrEmpty() -> this.allOf + else -> error("Unsupported state of composed schema, [ anyOf | oneOf | allOf ] list is empty") + } + when (dictionary.getEndPoint(schemas.first())) { + is ArraySchema -> JsonSchemaTypes.ARRAY + is ObjectSchema -> JsonSchemaTypes.OBJECT + is ComposedSchema -> error("Unsupported level of abstraction, cannot parse composed scheme inside of composed scheme") + //TODO: More than one level of abstraction isn't impossible, need to implement later, this is fast try of realization + else -> error("Unsupported type of first element from composed schema [${schemas.first()::class.simpleName}], array or object required") + } + } + else -> error("Unsupported type of json schema [${this::class.simpleName}], array or object required") } } + } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt index f753b86..1d13594 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt @@ -95,6 +95,8 @@ class DecodeJsonArrayVisitor(override val from: ArrayNode) : DecodeVisitor): Boolean = error("Unsupported checkAgainst for arrays") + override fun getUndefinedFields(fields: MutableSet): Nothing? = null override fun getResult(): Message.Builder = rootMessage diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 4ddffdd..b0764db 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -156,4 +156,9 @@ class DecodeJsonObjectVisitor(override val from: ObjectNode) : DecodeVisitor): Boolean { + val fieldNames = from.fieldNames().asSequence() + return message.required.filterNot { it in fieldNames }.isEmpty() + } } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt index 0a7fd03..3f90c3a 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt @@ -79,6 +79,8 @@ class EncodeJsonArrayVisitor(override val from: Message) : EncodeVisitor): Nothing? = null + override fun checkAgainst(message: Schema<*>): Boolean = error("Unsupported checkAgainst for arrays") + override fun getResult(): ByteString { return if (rootNode.isEmpty) { ByteString.EMPTY diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 205395d..62b13b7 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -151,6 +151,11 @@ class EncodeJsonObjectVisitor(override val from: Message) : EncodeVisitor): Boolean { + val fieldNames = from.fieldsMap.keys + return message.required.filterNot { it in fieldNames }.isEmpty() + } + private companion object { val mapper = ObjectMapper().apply { nodeFactory = JsonNodeFactory.withExactBigDecimals(true) diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/ValidationDictionaryTests.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/ValidationDictionaryTests.kt index 2593112..edd1265 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/ValidationDictionaryTests.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/ValidationDictionaryTests.kt @@ -49,11 +49,9 @@ class ValidationDictionaryTests { if (dictionary.name.endsWith(YAML_FORMAT) || dictionary.name.endsWith(JSON_FORMAT)) { val factory = OpenApiCodecFactory().apply { init(object : IPipelineCodecContext { - override fun get(alias: DictionaryAlias): InputStream = TODO("Not yet implemented") - + override fun get(alias: DictionaryAlias): InputStream = error("") override fun get(type: DictionaryType): InputStream = dictionary.inputStream() - - override fun getDictionaryAliases(): Set = TODO("Not yet implemented") + override fun getDictionaryAliases(): Set = error("") }) } diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/encode/JsonObjectEncodeTests.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/encode/JsonObjectEncodeTests.kt index 84fb222..7ace09c 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/encode/JsonObjectEncodeTests.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/encode/JsonObjectEncodeTests.kt @@ -18,6 +18,7 @@ package com.exactpro.th2.codec.openapi.json.encode import com.exactpro.th2.codec.openapi.OpenApiCodec import com.exactpro.th2.codec.openapi.OpenApiCodecSettings +import com.exactpro.th2.codec.openapi.throwable.EncodeException import com.exactpro.th2.common.message.addField import com.fasterxml.jackson.databind.ObjectMapper import com.exactpro.th2.codec.openapi.utils.getResourceAsText @@ -62,6 +63,44 @@ class JsonObjectEncodeTests { } } + @Test + fun `json encode response oneOf`() { + val codec = OpenApiCodec(openAPI, settings) + val firstOneOf = codec.testEncode("/test", "get", "300", "application/json", "json") { + addField("publicKey", "1234567") + addField("testEnabled", true) + addField("testStatus", "FAILED") + } + + mapper.readTree(firstOneOf!!.body.toStringUtf8()).let { json -> + Assertions.assertEquals(3, json.size()) + Assertions.assertEquals("1234567", json.get("publicKey").asText()) + Assertions.assertTrue(json.get("testEnabled").asBoolean()) + Assertions.assertEquals("FAILED", json.get("testStatus").asText()) + Assertions.assertEquals(null, json.get("nullField")?.asText()) + } + + val secondOneOf = codec.testEncode("/test", "get", "300", "application/json", "json") { + addField("oneOfInteger", "1234567") + addField("oneOfEnabled", false) + } + + mapper.readTree(secondOneOf!!.body.toStringUtf8()).let { json -> + Assertions.assertEquals(2, json.size()) + Assertions.assertEquals("1234567", json.get("oneOfInteger").asText()) + Assertions.assertFalse(json.get("oneOfEnabled").asBoolean()) + } + + Assertions.assertThrows(EncodeException::class.java) { + codec.testEncode("/test", "get", "300", "application/json", "json") { + addField("oneOfInteger", "1234567") + addField("oneOfEnabled", false) + addField("publicKey", "1234567") + addField("testEnabled", true) + } + } + } + private companion object { val settings = OpenApiCodecSettings() val openAPI: OpenAPI = OpenAPIParser().readContents(getResourceAsText("dictionaries/valid/valid-dictionary.yml"), null, settings.dictionaryParseOption).openAPI diff --git a/src/test/resources/dictionaries/valid/valid-dictionary.yml b/src/test/resources/dictionaries/valid/valid-dictionary.yml index 9c543da..c2d3494 100644 --- a/src/test/resources/dictionaries/valid/valid-dictionary.yml +++ b/src/test/resources/dictionaries/valid/valid-dictionary.yml @@ -19,6 +19,14 @@ paths: schema: $ref: "#/components/schemas/TestObject" responses: + "300": + description: One Of Test + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/TestObject" + - $ref: "#/components/schemas/TestObjectOneOf" "200": description: OK content: @@ -180,6 +188,20 @@ components: example: false testStatus: $ref: "#/components/schemas/TestStatus" + TestObjectOneOf: + type: object + description: | + Defines the test object. + required: + - oneOfEnabled + - oneOfInteger + properties: + oneOfInteger: + type: integer + example: 123 + oneOfEnabled: + type: boolean + example: false TestStatus: type: string description: | From 7cb65f2cc41b78882099cc35f6f1767518154343 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Wed, 18 May 2022 13:56:42 +0400 Subject: [PATCH 02/34] decode test added, error fixed --- .../th2/codec/openapi/OpenApiCodec.kt | 2 +- .../th2/codec/openapi/writer/SchemaWriter.kt | 15 +++--- .../visitors/json/DecodeJsonObjectVisitor.kt | 39 ++++++++++---- .../json/decode/JsonObjectDecodeTests.kt | 53 +++++++++++++++++++ .../dictionaries/valid/object-json-tests.yml | 22 ++++++++ 5 files changed, 110 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index e5555aa..88a49ac 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -190,7 +190,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin builder += runCatching { decodeBody(message, messages[1].rawMessage!!) }.getOrElse { - throw DecodeException("Cannot parse body of http message", it) + throw DecodeException("Cannot decode body of http message", it) } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 08fcd2e..f51e0ee 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -37,21 +37,18 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val failOnU schemaVisitor: SchemaVisitor<*, *>, msgStructure: Schema<*> ) { - - val schema = openApi.getEndPoint(msgStructure) - - when { - schema is ComposedSchema -> { + when (val schema = openApi.getEndPoint(msgStructure)) { + is ComposedSchema -> { when { !schema.anyOf.isNullOrEmpty() -> processAnyOf(schema.anyOf, schemaVisitor) !schema.oneOf.isNullOrEmpty() -> processOneOf(schema.oneOf, schemaVisitor) !schema.allOf.isNullOrEmpty() -> processAllOf(schema.allOf, schemaVisitor) } } - schema is ArraySchema -> { + is ArraySchema -> { processArrayProperty(schema, schemaVisitor, ARRAY_TYPE) } - schema is ObjectSchema -> { + is ObjectSchema -> { requireNotNull(schema.properties) {"Properties in object are required: $schema"} if (failOnUndefined) { schemaVisitor.getUndefinedFields(schema.properties.keys)?.let { @@ -82,7 +79,7 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val failOnU private fun processOneOf(property: List>, visitor: SchemaVisitor<*, *>) { val validSchemes = property.filter(visitor::checkAgainst) - check(validSchemes.size == 1) { "Message was valid for more than one shame from 'OneOf' list: ${property.joinToString(", ") { it.`$ref` }}" } + check(validSchemes.size == 1) { "Message was valid for more than one scheme from 'OneOf' list: ${property.joinToString(", ") { it.`$ref`?: it.type }}" } traverse(visitor, validSchemes[0]) } @@ -107,7 +104,7 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val failOnU else -> error("Unsupported class of property: ${property::class.simpleName}") } }.onFailure { - throw CodecException("Cannot parse field [$name] inside of schema with type ${property.type}", it) + throw CodecException("Cannot parse field [$name]:${property.type}", it) } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index b0764db..2ef0d41 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -46,53 +46,70 @@ class DecodeJsonObjectVisitor(override val from: ObjectNode) : DecodeVisitor?, fldStruct: Schema<*>, required: Boolean, schemaWriter: SchemaWriter) { - from.getField(fieldName, required)?.let { - val visitor = DecodeJsonObjectVisitor(it.validateAsObject()) + from.getField(fieldName, required)?.let { node -> + val visitor = DecodeJsonObjectVisitor(node.validateAsObject()) schemaWriter.traverse(visitor, fldStruct) - rootMessage.addFields(fieldName, visitor.rootMessage.build()) + (visitor.rootMessage.build() ?: defaultValue)?.let { + rootMessage.addFields(fieldName, it) + } } } override fun visit(fieldName: String, defaultValue: String?, fldStruct: Schema<*>, required: Boolean) { val value = from.getField(fieldName, required)?.asText() fldStruct.checkEnum(value, fieldName) - rootMessage.addFields(fieldName, value ?: defaultValue) + (value ?: defaultValue)?.let { + rootMessage.addFields(fieldName, it) + } } override fun visit(fieldName: String, defaultValue: Boolean?, fldStruct: Schema<*>, required: Boolean) { val value = from.getField(fieldName, required)?.validateAsBoolean() fldStruct.checkEnum(value, fieldName) - rootMessage.addFields(fieldName, value ?: defaultValue) + (value ?: defaultValue)?.let { + rootMessage.addFields(fieldName, it) + } } override fun visit(fieldName: String, defaultValue: Int?, fldStruct: Schema<*>, required: Boolean) { val value = from.getField(fieldName, required)?.validateAsInteger() fldStruct.checkEnum(value, fieldName) - rootMessage.addFields(fieldName, value ?: defaultValue) + (value ?: defaultValue)?.let { + rootMessage.addFields(fieldName, it) + } } override fun visit(fieldName: String, defaultValue: Float?, fldStruct: Schema<*>, required: Boolean) { val value = from.getField(fieldName, required)?.validateAsFloat() fldStruct.checkEnum(value, fieldName) - rootMessage.addFields(fieldName, value ?: defaultValue) + (value ?: defaultValue)?.let { + rootMessage.addFields(fieldName, it) + } } override fun visit(fieldName: String, defaultValue: Double?, fldStruct: Schema<*>, required: Boolean) { val value = from.getField(fieldName, required)?.validateAsDouble() fldStruct.checkEnum(value, fieldName) - rootMessage.addFields(fieldName, value ?: defaultValue) + (value ?: defaultValue)?.let { + rootMessage.addFields(fieldName, it) + } } override fun visit(fieldName: String, defaultValue: BigDecimal?, fldStruct: Schema<*>, required: Boolean) { val value = from.getField(fieldName, required)?.validateAsBigDecimal() fldStruct.checkEnum(value, fieldName) - rootMessage.addFields(fieldName, value?.toPlainString() ?: defaultValue) + (value?.toPlainString() ?: defaultValue)?.let { + rootMessage.addFields(fieldName, it) + } + } override fun visit(fieldName: String, defaultValue: Long?, fldStruct: Schema<*>, required: Boolean) { val value = from.getField(fieldName, required)?.validateAsLong() fldStruct.checkEnum(value, fieldName) - rootMessage.addFields(fieldName, value ?: defaultValue) + (value ?: defaultValue)?.let { + rootMessage.addFields(fieldName, it) + } } override fun visitBooleanCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) { @@ -158,7 +175,7 @@ class DecodeJsonObjectVisitor(override val from: ObjectNode) : DecodeVisitor): Boolean { - val fieldNames = from.fieldNames().asSequence() + val fieldNames = from.fieldNames().asSequence().toList() return message.required.filterNot { it in fieldNames }.isEmpty() } } \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonObjectDecodeTests.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonObjectDecodeTests.kt index fd227a7..1c8eb26 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonObjectDecodeTests.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonObjectDecodeTests.kt @@ -18,6 +18,7 @@ package com.exactpro.th2.codec.openapi.json.decode import com.exactpro.th2.codec.openapi.OpenApiCodec import com.exactpro.th2.codec.openapi.OpenApiCodecSettings +import com.exactpro.th2.codec.openapi.throwable.DecodeException import com.exactpro.th2.common.message.getString import com.exactpro.th2.codec.openapi.utils.getResourceAsText import io.swagger.parser.OpenAPIParser @@ -25,6 +26,7 @@ import io.swagger.v3.oas.models.OpenAPI import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import com.exactpro.th2.codec.openapi.utils.testDecode +import com.exactpro.th2.common.message.getInt import com.exactpro.th2.common.message.messageType class JsonObjectDecodeTests { @@ -73,6 +75,57 @@ class JsonObjectDecodeTests { Assertions.assertEquals("FAILED", decodedResult.getString("testStatus")) } + @Test + fun `json decode response oneOf`() { + val codec = OpenApiCodec(openAPI, settings) + val firstOneOf = codec.testDecode( + "/test", + "GET", + "300", + "application/json", + """{ + "publicKey" : "1234567", + "testEnabled" : true, + "testStatus" : "FAILED" + }""".trimIndent())!! + + Assertions.assertEquals("TestGet300ApplicationJson", firstOneOf.messageType) + Assertions.assertEquals(3, firstOneOf.fieldsMap.size) + Assertions.assertEquals("1234567", firstOneOf.getString("publicKey")) + Assertions.assertEquals(true, firstOneOf.getString("testEnabled").toBoolean()) + Assertions.assertEquals("FAILED", firstOneOf.getString("testStatus")) + + val secondOneOf = codec.testDecode( + "/test", + "get", + "300", + "application/json", + """{ + "oneOfInteger" : 1234567, + "oneOfEnabled" : true + }""".trimIndent())!! + + Assertions.assertEquals("TestGet300ApplicationJson", secondOneOf.messageType) + Assertions.assertEquals(2, secondOneOf.fieldsMap.size) + Assertions.assertEquals(1234567, secondOneOf.getInt("oneOfInteger")) + Assertions.assertEquals(true, secondOneOf.getString("oneOfEnabled").toBoolean()) + + Assertions.assertThrows(DecodeException::class.java) { + codec.testDecode( + "/test", + "get", + "300", + "application/json", + """{ + "oneOfInteger" : 1234567, + "oneOfEnabled" : true, + "publicKey" : "1234567", + "testEnabled" : true, + "testStatus" : "FAILED" + }""".trimIndent())!! + } + } + private companion object { val settings = OpenApiCodecSettings() val openAPI: OpenAPI = OpenAPIParser().readContents(getResourceAsText("dictionaries/valid/object-json-tests.yml"), null, settings.dictionaryParseOption).openAPI diff --git a/src/test/resources/dictionaries/valid/object-json-tests.yml b/src/test/resources/dictionaries/valid/object-json-tests.yml index 56af9b5..aa7a770 100644 --- a/src/test/resources/dictionaries/valid/object-json-tests.yml +++ b/src/test/resources/dictionaries/valid/object-json-tests.yml @@ -19,6 +19,14 @@ paths: schema: $ref: "#/components/schemas/TestObject" responses: + "300": + description: One Of Test + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/TestObject" + - $ref: "#/components/schemas/TestObjectOneOf" "200": description: OK content: @@ -28,6 +36,20 @@ paths: components: schemas: + TestObjectOneOf: + type: object + description: | + Defines the test object. + required: + - oneOfEnabled + - oneOfInteger + properties: + oneOfInteger: + type: integer + example: 123 + oneOfEnabled: + type: boolean + example: false TestStatus: type: string description: | From f520ee8e464fa48ae9b9479c065021b81dca6306 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Thu, 19 May 2022 15:40:31 +0400 Subject: [PATCH 03/34] new reworked visitors update started -> interface reworked and encode json object created --- .../writer/visitors/UpdatedSchemaVisitor.kt | 29 +++++ .../json/updated/DecodeJsonObjectVisitor.kt | 5 + .../json/updated/EncodeJsonObjectVisitor.kt | 114 ++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt create mode 100644 src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt create mode 100644 src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt new file mode 100644 index 0000000..5784200 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt @@ -0,0 +1,29 @@ +package com.exactpro.th2.codec.openapi.writer.visitors + +import com.exactpro.th2.common.grpc.Message +import com.google.protobuf.ByteString +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.StringSchema + +sealed class UpdatedSchemaVisitor { + abstract val openAPI: OpenAPI + abstract val from: FromType + abstract fun getResult(): ToType + abstract fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, checkUndefined: Boolean = true) + abstract fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) + + abstract class EncodeVisitor : UpdatedSchemaVisitor() + + abstract class DecodeVisitor : UpdatedSchemaVisitor() +} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt new file mode 100644 index 0000000..1754f68 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt @@ -0,0 +1,5 @@ +package com.exactpro.th2.codec.openapi.writer.visitors.json.updated + +class DecodeJsonObjectVisitor { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt new file mode 100644 index 0000000..8531474 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt @@ -0,0 +1,114 @@ +package com.exactpro.th2.codec.openapi.writer.visitors.json.updated + +import com.exactpro.th2.codec.openapi.utils.checkEnum +import com.exactpro.th2.codec.openapi.utils.getBoolean +import com.exactpro.th2.codec.openapi.utils.getField +import com.exactpro.th2.codec.openapi.utils.putAll +import com.exactpro.th2.codec.openapi.writer.visitors.UpdatedSchemaVisitor +import com.exactpro.th2.common.grpc.Message +import com.exactpro.th2.common.grpc.Value +import com.exactpro.th2.common.value.getBigDecimal +import com.exactpro.th2.common.value.getList +import com.exactpro.th2.common.value.getLong +import com.exactpro.th2.common.value.getMessage +import com.exactpro.th2.common.value.getString +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.node.JsonNodeFactory +import com.fasterxml.jackson.databind.node.ObjectNode +import com.google.protobuf.ByteString +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.Schema +import io.swagger.v3.oas.models.media.StringSchema +import java.math.BigDecimal + +class EncodeJsonObjectVisitor(override val from: Message, override val openAPI: OpenAPI) : UpdatedSchemaVisitor.EncodeVisitor() { + private val rootNode: ObjectNode = mapper.createObjectNode() + + override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) { + from.getField(fieldName, required)?.getMessage()?.let { message -> + val visitor = EncodeJsonObjectVisitor(message, openAPI) + for (entry in fldStruct.properties) { + when (val propertySchema = entry.value) { + is NumberSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is IntegerSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is BooleanSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is StringSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is ArraySchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is ObjectSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is ComposedSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + else -> error("Unsupported type of schema [${propertySchema}] for object visitor - visit object schema") + } + } + rootNode.set(fieldName, visitor.rootNode) + } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } + } + + override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean) { + val itemSchema = fldStruct.items + + from.getField(fieldName, required)?.getList()?.let { listOfValues -> + when (itemSchema) { + is NumberSchema -> rootNode.putArray(fieldName).putAll(listOfValues) + is IntegerSchema -> rootNode.putArray(fieldName).putAll(listOfValues) + is BooleanSchema -> rootNode.putArray(fieldName).putAll(listOfValues) + is StringSchema -> rootNode.putArray(fieldName).putAll(listOfValues) + is ObjectSchema -> { + val listOfNodes = mutableListOf() + listOfValues.forEach { + EncodeJsonObjectVisitor(checkNotNull(it.getMessage()) {" Value from list [$fieldName] must be message"}, openAPI).let { visitor -> + visitor.visit("message", itemSchema, true) + visitor.rootNode.get("message")?.run { + listOfNodes.add(this as ObjectNode) + } + } + } + } + else -> error("Unsupported items type: ${fldStruct.items::class.java}") + } + } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } + } + + override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { + TODO("Not yet implemented") + } + + override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getBigDecimal) { + rootNode.put(fieldName, it) + } + + override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getLong) { + rootNode.put(fieldName, it.toLong()) + } + + override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getString) { + rootNode.put(fieldName, it) + } + + override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getBoolean) { + rootNode.put(fieldName, it) + } + + private inline fun visitPrimitive(fieldName: String, fieldValue: Value?, fldStruct: Schema, convert: (Value) -> T, put: (T) -> Unit) { + fieldValue?.run(convert)?.let { value -> + fldStruct.checkEnum(value, fieldName) + put(value) + } ?: fldStruct.default?.run(put) + } + + override fun getResult(): ByteString = ByteString.copyFrom(rootNode.toString().toByteArray()) + + fun getNode() = rootNode + + private companion object { + val mapper = ObjectMapper().apply { + nodeFactory = JsonNodeFactory.withExactBigDecimals(true) + } + } + +} \ No newline at end of file From ae36654b84e7aed1f7c7bbccd196d0fba91e01c4 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Thu, 19 May 2022 17:01:53 +0400 Subject: [PATCH 04/34] decode json object visitor created --- .../th2/codec/openapi/utils/OpenApiUtils.kt | 1 + .../th2/codec/openapi/writer/SchemaWriter.kt | 4 +- .../json/updated/DecodeJsonObjectVisitor.kt | 59 ++++++++++++++++++- .../json/updated/EncodeJsonArrayVisitor.kt | 22 +++++++ .../json/updated/EncodeJsonObjectVisitor.kt | 2 +- 5 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt index 0cce39f..0f0619f 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt @@ -27,6 +27,7 @@ import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.parser.models.RefType private val METHODS = listOf("get", "put", "delete", "post") +const val ARRAY_TYPE = "array" const val JSON_FORMAT = "application/json" const val ANY_FORMAT = "*/*" diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index f51e0ee..1092ca8 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -17,6 +17,7 @@ package com.exactpro.th2.codec.openapi.writer import com.exactpro.th2.codec.CodecException +import com.exactpro.th2.codec.openapi.utils.ARRAY_TYPE import com.exactpro.th2.codec.openapi.utils.getEndPoint import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor import io.swagger.v3.oas.models.OpenAPI @@ -134,7 +135,4 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val failOnU } } - companion object { - const val ARRAY_TYPE = "array" - } } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt index 1754f68..0293104 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt @@ -1,5 +1,62 @@ package com.exactpro.th2.codec.openapi.writer.visitors.json.updated -class DecodeJsonObjectVisitor { +import com.exactpro.th2.codec.openapi.utils.checkEnum +import com.exactpro.th2.codec.openapi.utils.getField +import com.exactpro.th2.codec.openapi.utils.validateAsBigDecimal +import com.exactpro.th2.codec.openapi.utils.validateAsBoolean +import com.exactpro.th2.codec.openapi.utils.validateAsLong +import com.exactpro.th2.codec.openapi.writer.visitors.UpdatedSchemaVisitor +import com.exactpro.th2.common.grpc.Message +import com.exactpro.th2.common.message.addFields +import com.exactpro.th2.common.message.message +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.node.JsonNodeFactory +import com.fasterxml.jackson.databind.node.ObjectNode +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.Schema +import io.swagger.v3.oas.models.media.StringSchema +class DecodeJsonObjectVisitor(override val from: ObjectNode, override val openAPI: OpenAPI) : UpdatedSchemaVisitor.DecodeVisitor() { + private val rootMessage = message() + + constructor(jsonString: String, openAPI: OpenAPI) : this(mapper.readTree(jsonString) as ObjectNode, openAPI) + + override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, checkUndefined: Boolean) { + TODO("Not yet implemented") + } + + override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean) { + TODO("Not yet implemented") + } + + override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { + TODO("Not yet implemented") + } + + override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required)?.validateAsBigDecimal(), fldStruct) + override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required)?.validateAsLong(), fldStruct) + override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required)?.asText(), fldStruct) + override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required)?.validateAsBoolean(), fldStruct) + + private fun visitPrimitive(fieldName: String, value: T?, fldStruct: Schema) { + (value?.also { fldStruct.checkEnum(it, fieldName) } ?: fldStruct.default)?.let { + rootMessage.addFields(fieldName, it) + } + } + + override fun getResult(): Message.Builder { + TODO("Not yet implemented") + } + + private companion object { + val mapper = ObjectMapper().apply { + nodeFactory = JsonNodeFactory.withExactBigDecimals(true) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt new file mode 100644 index 0000000..df91821 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt @@ -0,0 +1,22 @@ +package com.exactpro.th2.codec.openapi.writer.visitors.json.updated + +import com.exactpro.th2.codec.openapi.utils.ARRAY_TYPE +import com.exactpro.th2.common.grpc.Message +import com.google.protobuf.ByteString +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.StringSchema + +class EncodeJsonArrayVisitor(from: Message, openAPI: OpenAPI) : EncodeJsonObjectVisitor(from, openAPI) { + override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun getResult(): ByteString = ByteString.copyFrom(getNode().get(ARRAY_TYPE).toString().toByteArray()) +} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt index 8531474..33fbee8 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt @@ -27,7 +27,7 @@ import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import java.math.BigDecimal -class EncodeJsonObjectVisitor(override val from: Message, override val openAPI: OpenAPI) : UpdatedSchemaVisitor.EncodeVisitor() { +open class EncodeJsonObjectVisitor(override val from: Message, override val openAPI: OpenAPI) : UpdatedSchemaVisitor.EncodeVisitor() { private val rootNode: ObjectNode = mapper.createObjectNode() override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) { From 73315e7fb9c5b1cd62c156d6fd5446c08e5bf7a5 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Thu, 19 May 2022 17:02:49 +0400 Subject: [PATCH 05/34] test import fixed --- .../th2/codec/openapi/json/decode/JsonArrayDecodeTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonArrayDecodeTests.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonArrayDecodeTests.kt index 4551695..86423c7 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonArrayDecodeTests.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonArrayDecodeTests.kt @@ -18,7 +18,7 @@ package com.exactpro.th2.codec.openapi.json.decode import com.exactpro.th2.codec.openapi.OpenApiCodec import com.exactpro.th2.codec.openapi.OpenApiCodecSettings -import com.exactpro.th2.codec.openapi.writer.SchemaWriter.Companion.ARRAY_TYPE +import com.exactpro.th2.codec.openapi.utils.ARRAY_TYPE import com.exactpro.th2.common.assertList import com.exactpro.th2.common.value.toValue import com.exactpro.th2.codec.openapi.utils.getResourceAsText From c7fa3b63c10fbdf332c5d8ae999aadcec8a0c2a5 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Thu, 19 May 2022 17:12:30 +0400 Subject: [PATCH 06/34] undefined fields throw --- .../visitors/json/updated/EncodeJsonObjectVisitor.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt index 33fbee8..5e6dbdd 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt @@ -45,6 +45,13 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open else -> error("Unsupported type of schema [${propertySchema}] for object visitor - visit object schema") } } + if (throwUndefined) { + val propertyNames = fldStruct.properties.keys + val undefinedFields = from.fieldsMap.keys.filter { !propertyNames.contains(it) } + if (undefinedFields.isNotEmpty()) { + error("Found undefined fields: " + undefinedFields.joinToString(", ")) + } + } rootNode.set(fieldName, visitor.rootNode) } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } } From b1b5dc2d13e48ddc9bb0b695b95982557e32c798 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Thu, 19 May 2022 17:56:25 +0400 Subject: [PATCH 07/34] array of objects implemented for encode and decode json visitors --- .../writer/visitors/UpdatedSchemaVisitor.kt | 2 +- .../json/updated/DecodeJsonObjectVisitor.kt | 54 +++++++++++++++++-- .../json/updated/EncodeJsonObjectVisitor.kt | 3 +- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt index 5784200..dd6721d 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt @@ -15,7 +15,7 @@ sealed class UpdatedSchemaVisitor { abstract val openAPI: OpenAPI abstract val from: FromType abstract fun getResult(): ToType - abstract fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, checkUndefined: Boolean = true) + abstract fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean = true) abstract fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt index 0293104..cfd2fe5 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt @@ -2,12 +2,16 @@ package com.exactpro.th2.codec.openapi.writer.visitors.json.updated import com.exactpro.th2.codec.openapi.utils.checkEnum import com.exactpro.th2.codec.openapi.utils.getField +import com.exactpro.th2.codec.openapi.utils.getRequiredArray import com.exactpro.th2.codec.openapi.utils.validateAsBigDecimal import com.exactpro.th2.codec.openapi.utils.validateAsBoolean import com.exactpro.th2.codec.openapi.utils.validateAsLong +import com.exactpro.th2.codec.openapi.utils.validateAsObject import com.exactpro.th2.codec.openapi.writer.visitors.UpdatedSchemaVisitor import com.exactpro.th2.common.grpc.Message +import com.exactpro.th2.common.message.addField import com.exactpro.th2.common.message.addFields +import com.exactpro.th2.common.message.getMessage import com.exactpro.th2.common.message.message import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.JsonNodeFactory @@ -27,12 +31,54 @@ class DecodeJsonObjectVisitor(override val from: ObjectNode, override val openAP constructor(jsonString: String, openAPI: OpenAPI) : this(mapper.readTree(jsonString) as ObjectNode, openAPI) - override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, checkUndefined: Boolean) { - TODO("Not yet implemented") + override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) { + from.getField(fieldName, required)?.let { message -> + val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), openAPI) + for (entry in fldStruct.properties) { + when (val propertySchema = entry.value) { + is NumberSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is IntegerSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is BooleanSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is StringSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is ArraySchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is ObjectSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + is ComposedSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) + else -> error("Unsupported type of schema [${propertySchema}] for object visitor - visit object schema") + } + } + if (throwUndefined) { + val propertyNames = fldStruct.properties.keys + val undefinedFields = from.fieldNames().asSequence().filter { !propertyNames.contains(it) } + if (undefinedFields.count() > 0) { + error("Found undefined fields: " + undefinedFields.joinToString(", ")) + } + } + rootMessage.addField(fieldName, visitor.rootMessage) + } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } } override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean) { - TODO("Not yet implemented") + val itemSchema = fldStruct.items + + from.getRequiredArray(fieldName, required)?.let { arrayNode -> + when (itemSchema) { + is NumberSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBigDecimal() }) + is IntegerSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsLong() }) + is BooleanSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBoolean() }) + is StringSchema -> rootMessage.addField(fieldName, arrayNode.map { it.asText() }) + is ObjectSchema -> rootMessage.addField(fieldName, arrayNode.run { + val listOfMessages = mutableListOf() + arrayNode.forEach { + DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) {" Value from list [$fieldName] must be message"}, openAPI).let { visitor -> + visitor.visit("message", itemSchema, true) + visitor.rootMessage.getMessage("message")?.run(listOfMessages::add) + } + } + listOfMessages + }) + else -> error("Unsupported items type: ${fldStruct.items::class.java}") + } + } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } } override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { @@ -54,6 +100,8 @@ class DecodeJsonObjectVisitor(override val from: ObjectNode, override val openAP TODO("Not yet implemented") } + fun getMessage() = rootMessage + private companion object { val mapper = ObjectMapper().apply { nodeFactory = JsonNodeFactory.withExactBigDecimals(true) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt index 5e6dbdd..8dabff0 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt @@ -65,7 +65,7 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open is IntegerSchema -> rootNode.putArray(fieldName).putAll(listOfValues) is BooleanSchema -> rootNode.putArray(fieldName).putAll(listOfValues) is StringSchema -> rootNode.putArray(fieldName).putAll(listOfValues) - is ObjectSchema -> { + is ObjectSchema -> rootNode.putArray(fieldName).run { val listOfNodes = mutableListOf() listOfValues.forEach { EncodeJsonObjectVisitor(checkNotNull(it.getMessage()) {" Value from list [$fieldName] must be message"}, openAPI).let { visitor -> @@ -75,6 +75,7 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } } } + listOfNodes.forEach { add(it) } } else -> error("Unsupported items type: ${fldStruct.items::class.java}") } From 2946cff61d24afa32b5dde52091eab496223ca3d Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Fri, 20 May 2022 12:06:16 +0400 Subject: [PATCH 08/34] just first element of array --- .../writer/visitors/json/updated/EncodeJsonArrayVisitor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt index df91821..0457dc7 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt @@ -18,5 +18,5 @@ class EncodeJsonArrayVisitor(from: Message, openAPI: OpenAPI) : EncodeJsonObject override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun getResult(): ByteString = ByteString.copyFrom(getNode().get(ARRAY_TYPE).toString().toByteArray()) + override fun getResult(): ByteString = ByteString.copyFrom(getNode().get(0).toString().toByteArray()) } \ No newline at end of file From 811ac726b99ddebd526282a3a953c235513b131e Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 14:32:49 +0400 Subject: [PATCH 09/34] all tests resolved --- .../th2/codec/openapi/OpenApiCodec.kt | 2 +- .../th2/codec/openapi/utils/OpenApiUtils.kt | 4 +- .../th2/codec/openapi/writer/SchemaWriter.kt | 125 +++-------- .../openapi/writer/visitors/SchemaVisitor.kt | 78 +++---- .../writer/visitors/UpdatedSchemaVisitor.kt | 29 --- .../openapi/writer/visitors/VisitorFactory.kt | 8 +- .../visitors/json/DecodeJsonArrayVisitor.kt | 144 ++++-------- .../visitors/json/DecodeJsonObjectVisitor.kt | 210 ++++++------------ .../visitors/json/EncodeJsonArrayVisitor.kt | 109 ++------- .../visitors/json/EncodeJsonObjectVisitor.kt | 189 ++++++---------- .../json/updated/DecodeJsonObjectVisitor.kt | 110 --------- .../json/updated/EncodeJsonArrayVisitor.kt | 22 -- .../json/updated/EncodeJsonObjectVisitor.kt | 122 ---------- .../json/visitor/decode/JsonArrayTest.kt | 63 +++--- .../json/visitor/decode/JsonObjectTest.kt | 72 +++--- .../json/visitor/encode/JsonArrayTest.kt | 50 ++--- .../json/visitor/encode/JsonObjectTest.kt | 71 +++--- .../codec/openapi/utils/SchemaTestUtils.kt | 9 +- 18 files changed, 411 insertions(+), 1006 deletions(-) delete mode 100644 src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt delete mode 100644 src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt delete mode 100644 src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt delete mode 100644 src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index 88a49ac..3567d61 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -200,7 +200,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin private fun decodeBody(header: Message, rawMessage: RawMessage): Message { val body = rawMessage.body val (messageType, container) = searchContainer(header, rawMessage.metadata) - val schema = checkNotNull(container.body) { "Container: $messageType did not contain schema body" } + val schema = dictionary.getEndPoint(checkNotNull(container.body) { "Container: $messageType did not contain schema body" }) val format = checkNotNull(container.bodyFormat) { "Container: $messageType did not contain schema bodyFormat" } val visitor = VisitorFactory.createDecodeVisitor(format, schema, body, dictionary) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt index 0f0619f..8d32952 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt @@ -85,4 +85,6 @@ fun Schema<*>.validateForType(): Schema<*> { fun Schema<*>.isComposed() = this is ComposedSchema fun Schema<*>.isObject() = this is ObjectSchema -fun Schema<*>.isArray() = this is ArraySchema \ No newline at end of file +fun Schema<*>.isArray() = this is ArraySchema + +fun Schema<*>.requiredContains(name: String) = this.required?.contains(name) ?: false \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 1092ca8..af1b3bd 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -16,9 +16,9 @@ package com.exactpro.th2.codec.openapi.writer -import com.exactpro.th2.codec.CodecException import com.exactpro.th2.codec.openapi.utils.ARRAY_TYPE import com.exactpro.th2.codec.openapi.utils.getEndPoint +import com.exactpro.th2.codec.openapi.utils.requiredContains import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema @@ -29,110 +29,49 @@ import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema -import java.math.BigDecimal +import java.lang.IllegalStateException -class SchemaWriter constructor(private val openApi: OpenAPI, private val failOnUndefined: Boolean = true) { +class SchemaWriter constructor(private val openApi: OpenAPI, private val throwUndefined: Boolean = true) { fun traverse( - schemaVisitor: SchemaVisitor<*, *>, + visitor: SchemaVisitor<*, *>, msgStructure: Schema<*> ) { - when (val schema = openApi.getEndPoint(msgStructure)) { - is ComposedSchema -> { - when { - !schema.anyOf.isNullOrEmpty() -> processAnyOf(schema.anyOf, schemaVisitor) - !schema.oneOf.isNullOrEmpty() -> processOneOf(schema.oneOf, schemaVisitor) - !schema.allOf.isNullOrEmpty() -> processAllOf(schema.allOf, schemaVisitor) - } - } - is ArraySchema -> { - processArrayProperty(schema, schemaVisitor, ARRAY_TYPE) - } + when (msgStructure) { + is ArraySchema -> visitor.visit(ARRAY_TYPE, msgStructure, true) is ObjectSchema -> { - requireNotNull(schema.properties) {"Properties in object are required: $schema"} - if (failOnUndefined) { - schemaVisitor.getUndefinedFields(schema.properties.keys)?.let { - check(it.isEmpty()) { "Undefined fields were found inside of ${schema.name}: ${it.joinToString()}" } - } - } - - schema.properties.forEach { (name, property) -> - processProperty(openApi.getEndPoint(property), schemaVisitor, name, schema.required?.contains(name) ?: false) + if (throwUndefined) { + visitor.checkUndefined(msgStructure) } - } - } - } - - private fun processAllOf(property: List>, visitor: SchemaVisitor<*, *>) { - property.forEach { - traverse(visitor, it) - } - } - - private fun processAnyOf(property: List>, visitor: SchemaVisitor<*, *>) { - val validSchemes = property.filter(visitor::checkAgainst) - check(validSchemes.isNotEmpty()) { "Message wasn't valid for any of shames from 'AnyOf' list: ${property.joinToString(", ") { it.`$ref` }}" } - validSchemes.forEach { - traverse(visitor, it) - } - } - - private fun processOneOf(property: List>, visitor: SchemaVisitor<*, *>) { - val validSchemes = property.filter(visitor::checkAgainst) - check(validSchemes.size == 1) { "Message was valid for more than one scheme from 'OneOf' list: ${property.joinToString(", ") { it.`$ref`?: it.type }}" } - traverse(visitor, validSchemes[0]) - } - - private fun processProperty(property: Schema<*>, visitor: SchemaVisitor<*, *>, name: String, required: Boolean = false) { - runCatching { - when(property) { - is ArraySchema -> processArrayProperty(property, visitor, name, required) - is StringSchema -> visitor.visit(name, property.default, property, required) - is IntegerSchema -> when (property.format) { - "int64" -> visitor.visit(name, property.default as? Long, property, required) - null, "", "int32" -> visitor.visit(name, property.default as? Int, property, required) - else -> error("Unsupported format of '${IntegerSchema::class.simpleName}' property $name: ${property.format}") - } - is NumberSchema -> when (property.format) { - "float" ->visitor.visit(name, property.default?.toFloat(), property, required) - "double" -> visitor.visit(name, property.default?.toDouble(), property, required) - null, "" -> visitor.visit(name, property.default?.toString(), property, required) - else -> visitor.visit(name, property.default, property, required) + msgStructure.properties.forEach { (name, property) -> + when(property) { + is ArraySchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is StringSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is IntegerSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is NumberSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is BooleanSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is ObjectSchema -> visitor.visit(name, property, msgStructure.requiredContains(name), throwUndefined) + is ComposedSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + else -> error("Unsupported class of property: ${property::class.simpleName}") + } } - is BooleanSchema -> visitor.visit(name, property.default, property, required) - is ObjectSchema -> visitor.visit(name, property.default as? Schema<*>, property, required, this) - else -> error("Unsupported class of property: ${property::class.simpleName}") } - }.onFailure { - throw CodecException("Cannot parse field [$name]:${property.type}", it) - } - - } - - @Suppress("UNCHECKED_CAST") - private fun processArrayProperty(property: ArraySchema, visitor: SchemaVisitor<*, *>, name: String, required: Boolean = false) { - runCatching { - when(property.items) { - is IntegerSchema -> when (property.items.format) { - "int64" -> visitor.visitLongCollection(name, property.default as? List, property, required) - null, "", "int32" -> visitor.visitIntegerCollection(name, property.default as? List, property, required) - else -> error("Unsupported format of '${IntegerSchema::class.simpleName}' property: ${property.format}") - } - is BooleanSchema -> visitor.visitBooleanCollection(name, property.default as? List, property, required) - is NumberSchema-> when (property.items.format) { - "float" -> visitor.visitFloatCollection(name, property.default as? List, property, required) - "double" -> visitor.visitDoubleCollection(name, property.default as? List, property, required) - null, "" -> visitor.visitStringCollection(name, property.default as? List, property, required) - else -> visitor.visitBigDecimalCollection(name, property.default as? List, property, required) + is ComposedSchema -> { + when { + !msgStructure.allOf.isNullOrEmpty() -> visitor.allOf(msgStructure.allOf.map { openApi.getEndPoint(it) } as List).forEach { + traverse(visitor, it) + } + !msgStructure.oneOf.isNullOrEmpty() -> visitor.oneOf(msgStructure.oneOf.map { openApi.getEndPoint(it) } as List).forEach { + traverse(visitor, it) + } + !msgStructure.anyOf.isNullOrEmpty() -> visitor.anyOf(msgStructure.anyOf.map { openApi.getEndPoint(it) } as List).forEach { + traverse(visitor, it) + } + else -> throw IllegalStateException("Composed schema was empty at allOf, oneOf, anyOf lists") } - is StringSchema -> visitor.visitStringCollection(name, property.default as? List, property, required) - is ObjectSchema -> visitor.visitObjectCollection(name, property.default as? List, property, required, this) - else -> error("Unsupported type of property") } - }.onFailure { - throw CodecException("Cannot parse array field [$name] inside of schema with type ${property.type}", it) + else -> throw UnsupportedOperationException("Traverse does not support anything except array and object schema") } } - } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt index 4651a76..86b2e92 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt @@ -1,54 +1,50 @@ -/* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.exactpro.th2.codec.openapi.writer.visitors -import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.common.grpc.Message import com.google.protobuf.ByteString +import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.Schema -import java.math.BigDecimal +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.StringSchema +import java.lang.IllegalStateException sealed class SchemaVisitor { + abstract val openAPI: OpenAPI abstract val from: FromType - abstract fun visit(fieldName: String, defaultValue: Schema<*>?, fldStruct: Schema<*>, required: Boolean = false, schemaWriter: SchemaWriter) - abstract fun visit(fieldName: String, defaultValue: String?, fldStruct: Schema<*>, required: Boolean = false) - abstract fun visit(fieldName: String, defaultValue: Boolean?, fldStruct: Schema<*>, required: Boolean = false) - abstract fun visit(fieldName: String, defaultValue: Int?, fldStruct: Schema<*>, required: Boolean = false) - abstract fun visit(fieldName: String, defaultValue: Float?, fldStruct: Schema<*>, required: Boolean = false) - abstract fun visit(fieldName: String, defaultValue: Double?, fldStruct: Schema<*>, required: Boolean = false) - abstract fun visit(fieldName: String, defaultValue: BigDecimal?, fldStruct: Schema<*>, required: Boolean = false) - abstract fun visit(fieldName: String, defaultValue: Long?, fldStruct: Schema<*>, required: Boolean = false) - abstract fun visitBooleanCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false) - abstract fun visitIntegerCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false) - abstract fun visitBigDecimalCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false) - abstract fun visitStringCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false) - abstract fun visitDoubleCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false) - abstract fun visitFloatCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false) - abstract fun visitLongCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false) - abstract fun visitObjectCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean = false, schemaWriter: SchemaWriter) - abstract fun getUndefinedFields(fields: MutableSet): Set? - abstract fun checkAgainst(message: Schema<*>): Boolean - abstract fun getResult(): ToType + abstract fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean = true) + abstract fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean = true) + abstract fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) + abstract fun checkUndefined(objectSchema: ObjectSchema) + abstract fun checkAgainst(fldStruct: ObjectSchema): Boolean + + fun oneOf(list: List): List = list.filter(this::checkAgainst).also { + if (it.size != 1) { + throw IllegalStateException("OneOf statement have ${it.size} valid schemas") + } + } + + fun anyOf(list: List): List = list.filter(this::checkAgainst).also{ + if (it.isEmpty()) { + throw IllegalStateException("AnyOf statement had zero valid schemas") + } + } + + fun allOf(list: List): List = list.filter(this::checkAgainst).also{ + if (list.size != it.size) { + throw IllegalStateException("AllOf statement have only ${it.size} valid schemas of ${list.size} available") + } + } abstract class EncodeVisitor : SchemaVisitor() abstract class DecodeVisitor : SchemaVisitor() -} - - +} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt deleted file mode 100644 index dd6721d..0000000 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/UpdatedSchemaVisitor.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.exactpro.th2.codec.openapi.writer.visitors - -import com.exactpro.th2.common.grpc.Message -import com.google.protobuf.ByteString -import io.swagger.v3.oas.models.OpenAPI -import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.BooleanSchema -import io.swagger.v3.oas.models.media.ComposedSchema -import io.swagger.v3.oas.models.media.IntegerSchema -import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema -import io.swagger.v3.oas.models.media.StringSchema - -sealed class UpdatedSchemaVisitor { - abstract val openAPI: OpenAPI - abstract val from: FromType - abstract fun getResult(): ToType - abstract fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean = true) - abstract fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean) - abstract fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) - abstract fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) - abstract fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) - abstract fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) - abstract fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) - - abstract class EncodeVisitor : UpdatedSchemaVisitor() - - abstract class DecodeVisitor : UpdatedSchemaVisitor() -} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt index 4a86d28..5f36aa0 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt @@ -38,8 +38,8 @@ object VisitorFactory { when (format) { JSON_FORMAT -> { return when (schema.defineType(dictionary)) { - JsonSchemaTypes.ARRAY -> EncodeJsonArrayVisitor(message) - JsonSchemaTypes.OBJECT -> EncodeJsonObjectVisitor(message) + JsonSchemaTypes.ARRAY -> EncodeJsonArrayVisitor(message, dictionary) + JsonSchemaTypes.OBJECT -> EncodeJsonObjectVisitor(message, dictionary) } } else -> error("Unsupported format of message $format for encode") @@ -50,8 +50,8 @@ object VisitorFactory { when (format) { JSON_FORMAT -> { return when (schema.defineType(dictionary)) { - JsonSchemaTypes.ARRAY -> DecodeJsonArrayVisitor(data.toStringUtf8()) - JsonSchemaTypes.OBJECT -> DecodeJsonObjectVisitor(data.toStringUtf8()) + JsonSchemaTypes.ARRAY -> DecodeJsonArrayVisitor(data.toStringUtf8(), dictionary) + JsonSchemaTypes.OBJECT -> DecodeJsonObjectVisitor(data.toStringUtf8(), dictionary) } } else -> error("Unsupported format of message $format for decode") diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt index 1d13594..ab72b2a 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt @@ -1,119 +1,71 @@ -/* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.exactpro.th2.codec.openapi.writer.visitors.json +import com.exactpro.th2.codec.openapi.utils.getEndPoint import com.exactpro.th2.codec.openapi.utils.validateAsBigDecimal import com.exactpro.th2.codec.openapi.utils.validateAsBoolean -import com.exactpro.th2.codec.openapi.utils.validateAsDouble -import com.exactpro.th2.codec.openapi.utils.validateAsFloat -import com.exactpro.th2.codec.openapi.utils.validateAsInteger import com.exactpro.th2.codec.openapi.utils.validateAsLong import com.exactpro.th2.codec.openapi.utils.validateAsObject import com.exactpro.th2.codec.openapi.writer.SchemaWriter -import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor.DecodeVisitor +import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor import com.exactpro.th2.common.grpc.Message import com.exactpro.th2.common.message.addField import com.exactpro.th2.common.message.message import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ArrayNode +import com.fasterxml.jackson.databind.node.JsonNodeFactory +import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.Schema -import java.math.BigDecimal - -class DecodeJsonArrayVisitor(override val from: ArrayNode) : DecodeVisitor() { - - constructor(jsonString: String) : this(mapper.readTree(jsonString) as ArrayNode) - - private val rootMessage = message() - - override fun visit(fieldName: String, defaultValue: Schema<*>?, fldStruct: Schema<*>, required: Boolean, schemaWriter: SchemaWriter) { - throw UnsupportedOperationException("Array visitor supports only collections") - } - - override fun visit(fieldName: String, defaultValue: String?, fldStruct: Schema<*>, required: Boolean) { - throw UnsupportedOperationException("Array visitor supports only collections") - } - - override fun visit(fieldName: String, defaultValue: Boolean?, fldStruct: Schema<*>, required: Boolean) { - throw UnsupportedOperationException("Array visitor supports only collections") - } - - override fun visit(fieldName: String, defaultValue: Int?, fldStruct: Schema<*>, required: Boolean) { - throw UnsupportedOperationException("Array visitor supports only collections") - } - - override fun visit(fieldName: String, defaultValue: Float?, fldStruct: Schema<*>, required: Boolean) { - throw UnsupportedOperationException("Array visitor supports only collections") - } - - override fun visit(fieldName: String, defaultValue: Double?, fldStruct: Schema<*>, required: Boolean) { - throw UnsupportedOperationException("Array visitor supports only collections") - } - - override fun visit(fieldName: String, defaultValue: Long?, fldStruct: Schema<*>, required: Boolean) { - throw UnsupportedOperationException("Array visitor supports only collections") - } - - override fun visit(fieldName: String, defaultValue: BigDecimal?, fldStruct: Schema<*>, required: Boolean) { - throw UnsupportedOperationException("Array visitor supports only collections") - } - - override fun visitBooleanCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootMessage.putListFrom(from, fieldName, defaultValue, required, JsonNode::validateAsBoolean) - - override fun visitIntegerCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootMessage.putListFrom(from, fieldName, defaultValue, required, JsonNode::validateAsInteger) - - override fun visitStringCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootMessage.putListFrom(from, fieldName, defaultValue, required, JsonNode::asText) - - override fun visitDoubleCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootMessage.putListFrom(from, fieldName, defaultValue, required, JsonNode::validateAsDouble) - - override fun visitFloatCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootMessage.putListFrom(from, fieldName, defaultValue, required, JsonNode::validateAsFloat) - - override fun visitLongCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootMessage.putListFrom(from, fieldName, defaultValue, required, JsonNode::validateAsLong) - - override fun visitBigDecimalCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootMessage.putListFrom(from, fieldName, defaultValue, required) { this.validateAsBigDecimal().toPlainString() } - - override fun visitObjectCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean, schemaWriter: SchemaWriter) { - rootMessage.addField(fieldName, from.map { - DecodeJsonObjectVisitor(it.validateAsObject()).apply { - schemaWriter.traverse(this, fldStruct.items) - }.getResult() - }) +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.StringSchema + +class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: OpenAPI) : SchemaVisitor.DecodeVisitor() { + + constructor(jsonString: String, openAPI: OpenAPI) : this(mapper.readTree(jsonString), openAPI) + + internal val rootMessage = message() + private val fromArray = from as ArrayNode + + override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean) { + when (val itemSchema = openAPI.getEndPoint(fldStruct.items)) { + is NumberSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsBigDecimal() }) + is IntegerSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsLong() }) + is BooleanSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsBoolean() }) + is StringSchema -> rootMessage.addField(fieldName, fromArray.map { it.asText() }) + is ObjectSchema -> rootMessage.addField(fieldName, mutableListOf().apply { + fromArray.forEach { + DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> + SchemaWriter(openAPI, throwUndefined).traverse(visitor, itemSchema) + visitor.rootMessage.build().run(this::add) + } + } + }) + is ComposedSchema -> { + TODO("Not yet implemented") + } + else -> error("Unsupported items type: ${fldStruct.items::class.java}") + } } - override fun checkAgainst(message: Schema<*>): Boolean = error("Unsupported checkAgainst for arrays") - - override fun getUndefinedFields(fields: MutableSet): Nothing? = null + override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun checkUndefined(objectSchema: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun checkAgainst(fldStruct: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") override fun getResult(): Message.Builder = rootMessage - private inline fun Message.Builder.putListFrom(node: ArrayNode, name: String, defaultValue: List?, required: Boolean, extract: JsonNode.() -> T) { - if (node.isEmpty) { - when { - required -> error("$name field is required but array node was empty") - !defaultValue.isNullOrEmpty() -> this.addField(name, defaultValue) - } - } else { - this.addField(name, node.map(extract)) - } - } private companion object { - val mapper = ObjectMapper() + val mapper = ObjectMapper().apply { + nodeFactory = JsonNodeFactory.withExactBigDecimals(true) + } } - } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 2ef0d41..fc07a6c 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -1,171 +1,90 @@ -/* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.exactpro.th2.codec.openapi.writer.visitors.json import com.exactpro.th2.codec.openapi.utils.checkEnum -import com.exactpro.th2.codec.openapi.utils.getRequiredArray +import com.exactpro.th2.codec.openapi.utils.getEndPoint import com.exactpro.th2.codec.openapi.utils.getField +import com.exactpro.th2.codec.openapi.utils.getRequiredArray import com.exactpro.th2.codec.openapi.utils.validateAsBigDecimal import com.exactpro.th2.codec.openapi.utils.validateAsBoolean -import com.exactpro.th2.codec.openapi.utils.validateAsDouble -import com.exactpro.th2.codec.openapi.utils.validateAsFloat -import com.exactpro.th2.codec.openapi.utils.validateAsInteger import com.exactpro.th2.codec.openapi.utils.validateAsLong import com.exactpro.th2.codec.openapi.utils.validateAsObject import com.exactpro.th2.codec.openapi.writer.SchemaWriter -import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor.DecodeVisitor +import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor import com.exactpro.th2.common.grpc.Message import com.exactpro.th2.common.message.addField import com.exactpro.th2.common.message.addFields import com.exactpro.th2.common.message.message +import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.fasterxml.jackson.databind.node.ObjectNode +import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.Schema -import java.math.BigDecimal - -class DecodeJsonObjectVisitor(override val from: ObjectNode) : DecodeVisitor() { - - private val rootMessage = message() - - constructor(jsonString: String) : this(mapper.readTree(jsonString) as ObjectNode) - - override fun visit(fieldName: String, defaultValue: Schema<*>?, fldStruct: Schema<*>, required: Boolean, schemaWriter: SchemaWriter) { - from.getField(fieldName, required)?.let { node -> - val visitor = DecodeJsonObjectVisitor(node.validateAsObject()) - schemaWriter.traverse(visitor, fldStruct) - (visitor.rootMessage.build() ?: defaultValue)?.let { - rootMessage.addFields(fieldName, it) +import io.swagger.v3.oas.models.media.StringSchema +import java.lang.IllegalStateException + +open class DecodeJsonObjectVisitor(override val from: JsonNode, override val openAPI: OpenAPI) : SchemaVisitor.DecodeVisitor() { + + constructor(jsonString: String, openAPI: OpenAPI) : this(mapper.readTree(jsonString), openAPI) + + internal val rootMessage = message() + private val fromObject = from as ObjectNode + + override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) { + fromObject.getField(fieldName, required)?.let { message -> + val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), openAPI) + val writer = SchemaWriter(openAPI, throwUndefined) + writer.traverse(visitor, fldStruct) + rootMessage.addField(fieldName, visitor.rootMessage) + } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } + } + + override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean) { + val itemSchema = openAPI.getEndPoint(fldStruct.items) + + fromObject.getRequiredArray(fieldName, required)?.let { arrayNode -> + when (itemSchema) { + is NumberSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBigDecimal() }) + is IntegerSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsLong() }) + is BooleanSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBoolean() }) + is StringSchema -> rootMessage.addField(fieldName, arrayNode.map { it.asText() }) + is ObjectSchema -> rootMessage.addField(fieldName, mutableListOf().apply { + arrayNode.forEach { + DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> + SchemaWriter(openAPI, throwUndefined).traverse(visitor, itemSchema) + visitor.rootMessage.build().run(this::add) + } + } + }) + is ComposedSchema -> { + TODO("Not yet implemented") + } + else -> error("Unsupported items type: ${fldStruct.items::class.java}") } - } + } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } } - override fun visit(fieldName: String, defaultValue: String?, fldStruct: Schema<*>, required: Boolean) { - val value = from.getField(fieldName, required)?.asText() - fldStruct.checkEnum(value, fieldName) - (value ?: defaultValue)?.let { - rootMessage.addFields(fieldName, it) - } + override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { + TODO("Not yet implemented") } - override fun visit(fieldName: String, defaultValue: Boolean?, fldStruct: Schema<*>, required: Boolean) { - val value = from.getField(fieldName, required)?.validateAsBoolean() - fldStruct.checkEnum(value, fieldName) - (value ?: defaultValue)?.let { - rootMessage.addFields(fieldName, it) - } - } + override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsBigDecimal(), fldStruct) + override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsLong(), fldStruct) + override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.asText(), fldStruct) + override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsBoolean(), fldStruct) - override fun visit(fieldName: String, defaultValue: Int?, fldStruct: Schema<*>, required: Boolean) { - val value = from.getField(fieldName, required)?.validateAsInteger() - fldStruct.checkEnum(value, fieldName) - (value ?: defaultValue)?.let { + private fun visitPrimitive(fieldName: String, value: T?, fldStruct: Schema) { + (value?.also { fldStruct.checkEnum(it, fieldName) } ?: fldStruct.default)?.let { rootMessage.addFields(fieldName, it) } } - override fun visit(fieldName: String, defaultValue: Float?, fldStruct: Schema<*>, required: Boolean) { - val value = from.getField(fieldName, required)?.validateAsFloat() - fldStruct.checkEnum(value, fieldName) - (value ?: defaultValue)?.let { - rootMessage.addFields(fieldName, it) - } - } - - override fun visit(fieldName: String, defaultValue: Double?, fldStruct: Schema<*>, required: Boolean) { - val value = from.getField(fieldName, required)?.validateAsDouble() - fldStruct.checkEnum(value, fieldName) - (value ?: defaultValue)?.let { - rootMessage.addFields(fieldName, it) - } - } - - override fun visit(fieldName: String, defaultValue: BigDecimal?, fldStruct: Schema<*>, required: Boolean) { - val value = from.getField(fieldName, required)?.validateAsBigDecimal() - fldStruct.checkEnum(value, fieldName) - (value?.toPlainString() ?: defaultValue)?.let { - rootMessage.addFields(fieldName, it) - } - - } - - override fun visit(fieldName: String, defaultValue: Long?, fldStruct: Schema<*>, required: Boolean) { - val value = from.getField(fieldName, required)?.validateAsLong() - fldStruct.checkEnum(value, fieldName) - (value ?: defaultValue)?.let { - rootMessage.addFields(fieldName, it) - } - } - - override fun visitBooleanCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) { - from.getRequiredArray(fieldName, required)?.let { array -> - rootMessage.addField(fieldName, array.map { it.validateAsBoolean() }) - } - } - - override fun visitIntegerCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) { - from.getRequiredArray(fieldName, required)?.let { array -> - rootMessage.addField(fieldName, array.map { it.validateAsInteger() }) - } - } - - override fun visitStringCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) { - from.getRequiredArray(fieldName, required)?.map { it.asText() }?.let { - rootMessage.addField(fieldName, it) - } - } - - override fun visitDoubleCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) { - from.getRequiredArray(fieldName, required)?.let { array -> - rootMessage.addField(fieldName, array.map { it.validateAsDouble() }) - } - } - - override fun visitFloatCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) { - from.getRequiredArray(fieldName, required)?.let { array -> - rootMessage.addField(fieldName, array.map { it.validateAsFloat() }) - } - } - - override fun visitLongCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) { - from.getRequiredArray(fieldName, required)?.let { array -> - rootMessage.addField(fieldName, array.map { it.validateAsLong() }) - } - } - - override fun visitBigDecimalCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) { - from.getRequiredArray(fieldName, required)?.let { array -> - rootMessage.addField(fieldName, array.map { it.validateAsBigDecimal().toPlainString() }) - } - } - - override fun visitObjectCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean, schemaWriter: SchemaWriter) { - from.getRequiredArray(fieldName, required)?.let { array -> - rootMessage.addField(fieldName, array.map { - DecodeJsonObjectVisitor(it.validateAsObject()).apply { - schemaWriter.traverse(this, fldStruct.items) - }.getResult() - }) - } - } - - override fun getUndefinedFields(fields: MutableSet): Set = from.fieldNames().asSequence().filterNot { it in fields }.toSet() - override fun getResult(): Message.Builder = rootMessage private companion object { @@ -174,8 +93,13 @@ class DecodeJsonObjectVisitor(override val from: ObjectNode) : DecodeVisitor): Boolean { - val fieldNames = from.fieldNames().asSequence().toList() - return message.required.filterNot { it in fieldNames }.isEmpty() + override fun checkUndefined(objectSchema: ObjectSchema) { + val names = objectSchema.properties.keys + val undefined = fromObject.fieldNames().asSequence().toList().filter { !names.contains(it) } + if (undefined.isNotEmpty()) { + throw IllegalStateException("Message have undefined fields: ${undefined.joinToString(", ")}") + } } + + override fun checkAgainst(fldStruct: ObjectSchema): Boolean = fromObject.fieldNames().asSequence().toList().containsAll(fldStruct.required) } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt index 3f90c3a..37d425f 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt @@ -1,95 +1,24 @@ -/* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.exactpro.th2.codec.openapi.writer.visitors.json -import com.exactpro.th2.codec.openapi.utils.getField -import com.exactpro.th2.codec.openapi.utils.putAll -import com.exactpro.th2.codec.openapi.writer.SchemaWriter -import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor.EncodeVisitor import com.exactpro.th2.common.grpc.Message -import com.exactpro.th2.common.message.toJson -import com.exactpro.th2.common.value.getList -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.node.ArrayNode import com.google.protobuf.ByteString -import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.Schema -import java.math.BigDecimal - -class EncodeJsonArrayVisitor(override val from: Message) : EncodeVisitor() { - - private val rootNode: ArrayNode = mapper.createArrayNode() - - companion object { - private var mapper = ObjectMapper() - } - - override fun visit(fieldName: String, defaultValue: Schema<*>?, fldStruct: Schema<*>, required: Boolean, schemaWriter: SchemaWriter) = throw UnsupportedOperationException("Array visitor supports only collections") - - override fun visit(fieldName: String, defaultValue: String?, fldStruct: Schema<*>, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - - override fun visit(fieldName: String, defaultValue: Boolean?, fldStruct: Schema<*>, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - - override fun visit(fieldName: String, defaultValue: Int?, fldStruct: Schema<*>, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - - override fun visit(fieldName: String, defaultValue: Float?, fldStruct: Schema<*>, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - - override fun visit(fieldName: String, defaultValue: Double?, fldStruct: Schema<*>, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - - override fun visit(fieldName: String, defaultValue: BigDecimal?, fldStruct: Schema<*>, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - - override fun visit(fieldName: String, defaultValue: Long?, fldStruct: Schema<*>, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - - override fun visitBooleanCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitIntegerCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitStringCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitDoubleCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitFloatCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitLongCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitBigDecimalCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitObjectCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean, schemaWriter: SchemaWriter) { - from.getField(fieldName, required)?.getList()?.map { - if (!it.hasMessageValue()) error("Cannot convert $fieldName=${it.toJson(true)} to json object") - EncodeJsonObjectVisitor(it.messageValue).apply { - schemaWriter.traverse(this, fldStruct.items) - }.getNode() - }?.forEach(rootNode::add) - } - - override fun getUndefinedFields(fields: MutableSet): Nothing? = null - - override fun checkAgainst(message: Schema<*>): Boolean = error("Unsupported checkAgainst for arrays") - - override fun getResult(): ByteString { - return if (rootNode.isEmpty) { - ByteString.EMPTY - } else { - ByteString.copyFrom(rootNode.toString().toByteArray()) - } - } - - private inline fun ArrayNode.putListFrom(message: Message, name: String, defaultValue: List?, required: Boolean) { - message.getField(name, required)?.getList()?.let { this.putAll(it) } ?: defaultValue?.let(::putAll) - } +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.StringSchema + +class EncodeJsonArrayVisitor(from: Message, openAPI: OpenAPI) : EncodeJsonObjectVisitor(from, openAPI) { + override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun checkUndefined(objectSchema: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun checkAgainst(fldStruct: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") + + override fun getResult(): ByteString = ByteString.copyFrom(rootNode.first().toString().toByteArray()) } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 62b13b7..e8485fa 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -1,31 +1,15 @@ -/* - * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.exactpro.th2.codec.openapi.writer.visitors.json import com.exactpro.th2.codec.openapi.utils.checkEnum +import com.exactpro.th2.codec.openapi.utils.getBoolean +import com.exactpro.th2.codec.openapi.utils.getEndPoint import com.exactpro.th2.codec.openapi.utils.getField import com.exactpro.th2.codec.openapi.utils.putAll import com.exactpro.th2.codec.openapi.writer.SchemaWriter -import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor.EncodeVisitor +import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor import com.exactpro.th2.common.grpc.Message -import com.exactpro.th2.common.message.toJson +import com.exactpro.th2.common.grpc.Value import com.exactpro.th2.common.value.getBigDecimal -import com.exactpro.th2.common.value.getDouble -import com.exactpro.th2.common.value.getInt import com.exactpro.th2.common.value.getList import com.exactpro.th2.common.value.getLong import com.exactpro.th2.common.value.getMessage @@ -34,132 +18,101 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.fasterxml.jackson.databind.node.ObjectNode import com.google.protobuf.ByteString +import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.Schema +import io.swagger.v3.oas.models.media.StringSchema +import java.lang.IllegalStateException import java.math.BigDecimal +open class EncodeJsonObjectVisitor(override val from: Message, override val openAPI: OpenAPI) : SchemaVisitor.EncodeVisitor() { + internal val rootNode: ObjectNode = mapper.createObjectNode() -class EncodeJsonObjectVisitor(override val from: Message) : EncodeVisitor() { - - private val rootNode: ObjectNode = mapper.createObjectNode() - - override fun visit(fieldName: String, defaultValue: Schema<*>?, fldStruct: Schema<*>, required: Boolean, schemaWriter: SchemaWriter) { - from.getField(fieldName, required)?.getMessage()?.let { nextMessage -> - val visitor = EncodeJsonObjectVisitor(nextMessage) - schemaWriter.traverse(visitor, fldStruct) + override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) { + from.getField(fieldName, required)?.getMessage()?.let { message -> + val visitor = EncodeJsonObjectVisitor(message, openAPI) + val writer = SchemaWriter(openAPI, throwUndefined) + writer.traverse(visitor, fldStruct) rootNode.set(fieldName, visitor.rootNode) - } + } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } } - override fun visit(fieldName: String, defaultValue: String?, fldStruct: Schema<*>, required: Boolean) { - from.getField(fieldName, required)?.getString()?.let { value -> - fldStruct.checkEnum(value, fieldName) - rootNode.put(fieldName, value) - } ?: defaultValue?.let { - rootNode.put(fieldName, defaultValue) - } + override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean) { + val itemSchema = openAPI.getEndPoint(fldStruct.items) + + from.getField(fieldName, required)?.getList()?.let { listOfValues -> + when (itemSchema) { + is NumberSchema -> rootNode.putArray(fieldName).putAll(listOfValues) + is IntegerSchema -> rootNode.putArray(fieldName).putAll(listOfValues) + is BooleanSchema -> rootNode.putArray(fieldName).putAll(listOfValues) + is StringSchema -> rootNode.putArray(fieldName).putAll(listOfValues) + is ObjectSchema -> rootNode.putArray(fieldName).apply { + val listOfNodes = mutableListOf() + val writer = SchemaWriter(openAPI, throwUndefined) + listOfValues.forEach { + EncodeJsonObjectVisitor(checkNotNull(it.getMessage()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> + writer.traverse(visitor, itemSchema) + visitor.rootNode.run(listOfNodes::add) + } + } + listOfNodes.forEach { add(it) } + } + is ComposedSchema -> { + TODO("Not yet implemented") + } + else -> error("Unsupported items type: ${fldStruct.items::class.java}") + } + } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } } - override fun visit(fieldName: String, defaultValue: Boolean?, fldStruct: Schema<*>, required: Boolean) { - from.getField(fieldName, required)?.getString()?.toBoolean()?.let { value -> - fldStruct.checkEnum(value, fieldName) - rootNode.put(fieldName, value) - } ?: defaultValue?.let { - rootNode.put(fieldName, defaultValue) - } + override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { + TODO("Not yet implemented") } - override fun visit(fieldName: String, defaultValue: Int?, fldStruct: Schema<*>, required: Boolean) { - from.getField(fieldName, required)?.getInt()?.let { value -> - fldStruct.checkEnum(value, fieldName) - rootNode.put(fieldName, value) - } ?: defaultValue?.let { - rootNode.put(fieldName, defaultValue) - } + override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getBigDecimal) { + rootNode.put(fieldName, it) } - override fun visit(fieldName: String, defaultValue: Float?, fldStruct: Schema<*>, required: Boolean) { - from.getField(fieldName, required)?.getString()?.toFloat()?.let { value -> - fldStruct.checkEnum(value, fieldName) - rootNode.put(fieldName, value) - } ?: defaultValue?.let { - rootNode.put(fieldName, defaultValue) - } + override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getLong) { + rootNode.put(fieldName, it.toLong()) } - override fun visit(fieldName: String, defaultValue: Double?, fldStruct: Schema<*>, required: Boolean) { - from.getField(fieldName, required)?.getDouble()?.let { value -> - fldStruct.checkEnum(value, fieldName) - rootNode.put(fieldName, value) - } ?: defaultValue?.let { - rootNode.put(fieldName, defaultValue) - } + override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getString) { + rootNode.put(fieldName, it) } - override fun visit(fieldName: String, defaultValue: Long?, fldStruct: Schema<*>, required: Boolean) { - from.getField(fieldName, required)?.getLong()?.let { value -> - fldStruct.checkEnum(value, fieldName) - rootNode.put(fieldName, value) - } ?: defaultValue?.let { - rootNode.put(fieldName, defaultValue) - } + override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getBoolean) { + rootNode.put(fieldName, it) } - override fun visit(fieldName: String, defaultValue: BigDecimal?, fldStruct: Schema<*>, required: Boolean) { - from.getField(fieldName, required)?.getBigDecimal()?.let { value -> - fldStruct.checkEnum(value, fieldName) - rootNode.put(fieldName, value) - } ?: defaultValue?.let { - rootNode.put(fieldName, defaultValue) - } - } + override fun checkAgainst(fldStruct: ObjectSchema): Boolean = from.fieldsMap.keys.containsAll(fldStruct.required) - override fun visitBooleanCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitIntegerCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitStringCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitDoubleCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitFloatCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitLongCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitBigDecimalCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean) = rootNode.putListFrom(from, fieldName, defaultValue, required) - - override fun visitObjectCollection(fieldName: String, defaultValue: List?, fldStruct: ArraySchema, required: Boolean, schemaWriter: SchemaWriter) { - from.getField(fieldName, required)?.getList()?.map { - if (!it.hasMessageValue()) error("Cannot convert $fieldName=${it.toJson(true)} to json object") - EncodeJsonObjectVisitor(it.messageValue).apply { - schemaWriter.traverse(this, fldStruct.items) - }.getNode() - }?.run(rootNode.putArray(fieldName)::addAll) + private inline fun visitPrimitive(fieldName: String, fieldValue: Value?, fldStruct: Schema, convert: (Value) -> T, put: (T) -> Unit) { + fieldValue?.run(convert)?.let { value -> + fldStruct.checkEnum(value, fieldName) + put(value) + } ?: fldStruct.default?.run(put) } - override fun getUndefinedFields(fields: MutableSet): Set = this.from.fieldsMap.keys - fields - override fun getResult(): ByteString = ByteString.copyFrom(rootNode.toString().toByteArray()) - fun getNode() = rootNode - - private inline fun ObjectNode.putListFrom(message: Message, name: String, defaultValue: List?, required: Boolean) { - message.getField(name, required)?.getList()?.let { values -> - this.putArray(name).putAll(values) - } ?: defaultValue?.let { list -> - this.putArray(name).putAll(list) - } - } - - override fun checkAgainst(message: Schema<*>): Boolean { - val fieldNames = from.fieldsMap.keys - return message.required.filterNot { it in fieldNames }.isEmpty() - } - private companion object { val mapper = ObjectMapper().apply { nodeFactory = JsonNodeFactory.withExactBigDecimals(true) } } -} + override fun checkUndefined(objectSchema: ObjectSchema) { + val names = objectSchema.properties.keys + val undefined = from.fieldsMap.keys.filter { !names.contains(it) } + if (undefined.isNotEmpty()) { + throw IllegalStateException("Message have undefined fields: ${undefined.joinToString(", ")}") + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt deleted file mode 100644 index cfd2fe5..0000000 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/DecodeJsonObjectVisitor.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.exactpro.th2.codec.openapi.writer.visitors.json.updated - -import com.exactpro.th2.codec.openapi.utils.checkEnum -import com.exactpro.th2.codec.openapi.utils.getField -import com.exactpro.th2.codec.openapi.utils.getRequiredArray -import com.exactpro.th2.codec.openapi.utils.validateAsBigDecimal -import com.exactpro.th2.codec.openapi.utils.validateAsBoolean -import com.exactpro.th2.codec.openapi.utils.validateAsLong -import com.exactpro.th2.codec.openapi.utils.validateAsObject -import com.exactpro.th2.codec.openapi.writer.visitors.UpdatedSchemaVisitor -import com.exactpro.th2.common.grpc.Message -import com.exactpro.th2.common.message.addField -import com.exactpro.th2.common.message.addFields -import com.exactpro.th2.common.message.getMessage -import com.exactpro.th2.common.message.message -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.node.JsonNodeFactory -import com.fasterxml.jackson.databind.node.ObjectNode -import io.swagger.v3.oas.models.OpenAPI -import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.BooleanSchema -import io.swagger.v3.oas.models.media.ComposedSchema -import io.swagger.v3.oas.models.media.IntegerSchema -import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema -import io.swagger.v3.oas.models.media.Schema -import io.swagger.v3.oas.models.media.StringSchema - -class DecodeJsonObjectVisitor(override val from: ObjectNode, override val openAPI: OpenAPI) : UpdatedSchemaVisitor.DecodeVisitor() { - private val rootMessage = message() - - constructor(jsonString: String, openAPI: OpenAPI) : this(mapper.readTree(jsonString) as ObjectNode, openAPI) - - override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) { - from.getField(fieldName, required)?.let { message -> - val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), openAPI) - for (entry in fldStruct.properties) { - when (val propertySchema = entry.value) { - is NumberSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is IntegerSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is BooleanSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is StringSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is ArraySchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is ObjectSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is ComposedSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - else -> error("Unsupported type of schema [${propertySchema}] for object visitor - visit object schema") - } - } - if (throwUndefined) { - val propertyNames = fldStruct.properties.keys - val undefinedFields = from.fieldNames().asSequence().filter { !propertyNames.contains(it) } - if (undefinedFields.count() > 0) { - error("Found undefined fields: " + undefinedFields.joinToString(", ")) - } - } - rootMessage.addField(fieldName, visitor.rootMessage) - } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } - } - - override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean) { - val itemSchema = fldStruct.items - - from.getRequiredArray(fieldName, required)?.let { arrayNode -> - when (itemSchema) { - is NumberSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBigDecimal() }) - is IntegerSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsLong() }) - is BooleanSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBoolean() }) - is StringSchema -> rootMessage.addField(fieldName, arrayNode.map { it.asText() }) - is ObjectSchema -> rootMessage.addField(fieldName, arrayNode.run { - val listOfMessages = mutableListOf() - arrayNode.forEach { - DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) {" Value from list [$fieldName] must be message"}, openAPI).let { visitor -> - visitor.visit("message", itemSchema, true) - visitor.rootMessage.getMessage("message")?.run(listOfMessages::add) - } - } - listOfMessages - }) - else -> error("Unsupported items type: ${fldStruct.items::class.java}") - } - } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } - } - - override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { - TODO("Not yet implemented") - } - - override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required)?.validateAsBigDecimal(), fldStruct) - override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required)?.validateAsLong(), fldStruct) - override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required)?.asText(), fldStruct) - override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required)?.validateAsBoolean(), fldStruct) - - private fun visitPrimitive(fieldName: String, value: T?, fldStruct: Schema) { - (value?.also { fldStruct.checkEnum(it, fieldName) } ?: fldStruct.default)?.let { - rootMessage.addFields(fieldName, it) - } - } - - override fun getResult(): Message.Builder { - TODO("Not yet implemented") - } - - fun getMessage() = rootMessage - - private companion object { - val mapper = ObjectMapper().apply { - nodeFactory = JsonNodeFactory.withExactBigDecimals(true) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt deleted file mode 100644 index 0457dc7..0000000 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonArrayVisitor.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.exactpro.th2.codec.openapi.writer.visitors.json.updated - -import com.exactpro.th2.codec.openapi.utils.ARRAY_TYPE -import com.exactpro.th2.common.grpc.Message -import com.google.protobuf.ByteString -import io.swagger.v3.oas.models.OpenAPI -import io.swagger.v3.oas.models.media.BooleanSchema -import io.swagger.v3.oas.models.media.ComposedSchema -import io.swagger.v3.oas.models.media.IntegerSchema -import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema -import io.swagger.v3.oas.models.media.StringSchema - -class EncodeJsonArrayVisitor(from: Message, openAPI: OpenAPI) : EncodeJsonObjectVisitor(from, openAPI) { - override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun getResult(): ByteString = ByteString.copyFrom(getNode().get(0).toString().toByteArray()) -} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt deleted file mode 100644 index 8dabff0..0000000 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/updated/EncodeJsonObjectVisitor.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.exactpro.th2.codec.openapi.writer.visitors.json.updated - -import com.exactpro.th2.codec.openapi.utils.checkEnum -import com.exactpro.th2.codec.openapi.utils.getBoolean -import com.exactpro.th2.codec.openapi.utils.getField -import com.exactpro.th2.codec.openapi.utils.putAll -import com.exactpro.th2.codec.openapi.writer.visitors.UpdatedSchemaVisitor -import com.exactpro.th2.common.grpc.Message -import com.exactpro.th2.common.grpc.Value -import com.exactpro.th2.common.value.getBigDecimal -import com.exactpro.th2.common.value.getList -import com.exactpro.th2.common.value.getLong -import com.exactpro.th2.common.value.getMessage -import com.exactpro.th2.common.value.getString -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.node.JsonNodeFactory -import com.fasterxml.jackson.databind.node.ObjectNode -import com.google.protobuf.ByteString -import io.swagger.v3.oas.models.OpenAPI -import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.BooleanSchema -import io.swagger.v3.oas.models.media.ComposedSchema -import io.swagger.v3.oas.models.media.IntegerSchema -import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema -import io.swagger.v3.oas.models.media.Schema -import io.swagger.v3.oas.models.media.StringSchema -import java.math.BigDecimal - -open class EncodeJsonObjectVisitor(override val from: Message, override val openAPI: OpenAPI) : UpdatedSchemaVisitor.EncodeVisitor() { - private val rootNode: ObjectNode = mapper.createObjectNode() - - override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) { - from.getField(fieldName, required)?.getMessage()?.let { message -> - val visitor = EncodeJsonObjectVisitor(message, openAPI) - for (entry in fldStruct.properties) { - when (val propertySchema = entry.value) { - is NumberSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is IntegerSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is BooleanSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is StringSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is ArraySchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is ObjectSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - is ComposedSchema -> visitor.visit(entry.key, propertySchema, fldStruct.required.contains(entry.key)) - else -> error("Unsupported type of schema [${propertySchema}] for object visitor - visit object schema") - } - } - if (throwUndefined) { - val propertyNames = fldStruct.properties.keys - val undefinedFields = from.fieldsMap.keys.filter { !propertyNames.contains(it) } - if (undefinedFields.isNotEmpty()) { - error("Found undefined fields: " + undefinedFields.joinToString(", ")) - } - } - rootNode.set(fieldName, visitor.rootNode) - } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } - } - - override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean) { - val itemSchema = fldStruct.items - - from.getField(fieldName, required)?.getList()?.let { listOfValues -> - when (itemSchema) { - is NumberSchema -> rootNode.putArray(fieldName).putAll(listOfValues) - is IntegerSchema -> rootNode.putArray(fieldName).putAll(listOfValues) - is BooleanSchema -> rootNode.putArray(fieldName).putAll(listOfValues) - is StringSchema -> rootNode.putArray(fieldName).putAll(listOfValues) - is ObjectSchema -> rootNode.putArray(fieldName).run { - val listOfNodes = mutableListOf() - listOfValues.forEach { - EncodeJsonObjectVisitor(checkNotNull(it.getMessage()) {" Value from list [$fieldName] must be message"}, openAPI).let { visitor -> - visitor.visit("message", itemSchema, true) - visitor.rootNode.get("message")?.run { - listOfNodes.add(this as ObjectNode) - } - } - } - listOfNodes.forEach { add(it) } - } - else -> error("Unsupported items type: ${fldStruct.items::class.java}") - } - } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } - } - - override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { - TODO("Not yet implemented") - } - - override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getBigDecimal) { - rootNode.put(fieldName, it) - } - - override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getLong) { - rootNode.put(fieldName, it.toLong()) - } - - override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getString) { - rootNode.put(fieldName, it) - } - - override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getBoolean) { - rootNode.put(fieldName, it) - } - - private inline fun visitPrimitive(fieldName: String, fieldValue: Value?, fldStruct: Schema, convert: (Value) -> T, put: (T) -> Unit) { - fieldValue?.run(convert)?.let { value -> - fldStruct.checkEnum(value, fieldName) - put(value) - } ?: fldStruct.default?.run(put) - } - - override fun getResult(): ByteString = ByteString.copyFrom(rootNode.toString().toByteArray()) - - fun getNode() = rootNode - - private companion object { - val mapper = ObjectMapper().apply { - nodeFactory = JsonNodeFactory.withExactBigDecimals(true) - } - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt index 6b69b3a..5126ca2 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt @@ -17,7 +17,6 @@ package com.exactpro.th2.codec.openapi.json.visitor.decode import com.exactpro.th2.codec.openapi.OpenApiCodecSettings -import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.json.DecodeJsonArrayVisitor import com.exactpro.th2.common.assertInt import com.exactpro.th2.common.assertList @@ -33,11 +32,13 @@ import com.exactpro.th2.codec.openapi.utils.getResourceAsText import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.Schema +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -import java.math.BigDecimal class JsonArrayTest { @@ -45,37 +46,25 @@ class JsonArrayTest { @Test fun `not supported decode`() { val node = mapper.createArrayNode() - val visitor = DecodeJsonArrayVisitor(node) + val visitor = DecodeJsonArrayVisitor(node, openAPI) Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? String, StringSchema(), true) + visitor.visit("", StringSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Int, StringSchema(), true) + visitor.visit("", IntegerSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Double, StringSchema(), true) + visitor.visit("", NumberSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Long, StringSchema(), true) + visitor.visit("", BooleanSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Float, StringSchema(), true) - } - - Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Boolean, StringSchema(), true) - } - - Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Schema<*>, StringSchema(), true, SchemaWriter(openAPI)) - } - - Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? BigDecimal, StringSchema(), true) + visitor.visit("", ObjectSchema(), true) } } @@ -123,8 +112,8 @@ class JsonArrayTest { }) } - val result = DecodeJsonArrayVisitor(jsonArrayNode).apply { - visitObjectCollection(fieldName, null, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true, SchemaWriter(openAPI)) + val result = DecodeJsonArrayVisitor(jsonArrayNode, openAPI).apply { + visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult() (result[fieldName]!!).let { listValue -> @@ -150,8 +139,8 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode) - visitor.visitStringCollection(fieldName, null, createArrayTestSchema("string"), true) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + visitor.visit(fieldName, createArrayTestSchema("string"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -162,8 +151,8 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode) - visitor.visitBooleanCollection(fieldName, null, createArrayTestSchema("boolean"), true) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + visitor.visit(fieldName, createArrayTestSchema("boolean"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -174,8 +163,8 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode) - visitor.visitIntegerCollection(fieldName, null, createArrayTestSchema("integer"), true) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + visitor.visit(fieldName, createArrayTestSchema("integer"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -186,8 +175,8 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode) - visitor.visitDoubleCollection(fieldName, null, createArrayTestSchema("number", "double"), true) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + visitor.visit(fieldName, createArrayTestSchema("number", "double"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -198,8 +187,8 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode) - visitor.visitFloatCollection(fieldName, null, createArrayTestSchema("number", "float"), true) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + visitor.visit(fieldName, createArrayTestSchema("number", "float"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -210,8 +199,8 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode) - visitor.visitLongCollection(fieldName, null, createArrayTestSchema("integer", "int64"), true) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + visitor.visit(fieldName, createArrayTestSchema("integer", "int64"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -222,8 +211,8 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode) - visitor.visitBigDecimalCollection(fieldName, null, createArrayTestSchema("integer", "int64"), true) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + visitor.visit(fieldName, createArrayTestSchema("integer", "int64"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt index 744bde5..23d5acc 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt @@ -17,7 +17,6 @@ package com.exactpro.th2.codec.openapi.json.visitor.decode import com.exactpro.th2.codec.openapi.OpenApiCodecSettings -import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.json.DecodeJsonObjectVisitor import com.exactpro.th2.common.assertDouble import com.exactpro.th2.common.assertInt @@ -35,9 +34,12 @@ import com.exactpro.th2.codec.openapi.utils.getResourceAsText import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.Schema +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Test -import java.math.BigDecimal @Suppress("CAST_NEVER_SUCCEEDS") class JsonObjectTest { @@ -73,8 +75,8 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { this.set(fieldName, objectValue) } - val result = DecodeJsonObjectVisitor(json).apply { - visit(fieldName, null as? Schema<*>, openAPI.components.schemas["ObjectTest"]!!, true, SchemaWriter(openAPI)) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, openAPI.components.schemas["ObjectTest"]!! as ObjectSchema, true) }.getResult() result[fieldName]!!.messageValue.let { bigMessage -> bigMessage.assertString(stringName, stringValue) @@ -93,8 +95,8 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json).apply { - visit(fieldName, null as? String, createTestSchema(simpleValue), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createTestSchema(simpleValue) as StringSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue) } @@ -106,8 +108,8 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json).apply { - visit(fieldName, null as? Boolean, createTestSchema(simpleValue), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createTestSchema(simpleValue) as BooleanSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) } @@ -119,8 +121,8 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json).apply { - visit(fieldName, null as? Int, createTestSchema(simpleValue), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createTestSchema(simpleValue) as IntegerSchema, true) }.getResult() result.build().assertInt(fieldName, simpleValue) } @@ -132,8 +134,8 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json).apply { - visit(fieldName, null as? Double, createTestSchema(simpleValue), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createTestSchema(simpleValue) as NumberSchema, true) }.getResult() result.build().assertDouble(fieldName, simpleValue) } @@ -145,8 +147,8 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json).apply { - visit(fieldName, null as? Float, createTestSchema(simpleValue), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createTestSchema(simpleValue) as NumberSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) } @@ -158,8 +160,8 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json).apply { - visit(fieldName, null as? Long, createTestSchema(simpleValue), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createTestSchema(simpleValue) as IntegerSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) } @@ -171,8 +173,8 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json).apply { - visit(fieldName, null as? BigDecimal, createTestSchema(simpleValue), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createTestSchema(simpleValue) as IntegerSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) } @@ -185,8 +187,8 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json).apply { - visitStringCollection(fieldName, null, createArrayTestSchema("string"), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) } @@ -199,8 +201,8 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json).apply { - visitBooleanCollection(fieldName, null, createArrayTestSchema("string"), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) } @@ -213,8 +215,8 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json).apply { - visitIntegerCollection(fieldName, null, createArrayTestSchema("integer"), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createArrayTestSchema("integer"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) } @@ -227,8 +229,8 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json).apply { - visitDoubleCollection(fieldName, null, createArrayTestSchema("string"), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) } @@ -241,8 +243,8 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json).apply { - visitFloatCollection(fieldName, null, createArrayTestSchema("string"), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) } @@ -255,8 +257,8 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json).apply { - visitLongCollection(fieldName, null, createArrayTestSchema("string"), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) } @@ -269,8 +271,8 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json).apply { - visitBigDecimalCollection(fieldName, null, createArrayTestSchema("string"), true) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) } @@ -323,8 +325,8 @@ class JsonObjectTest { this.set(fieldName, jsonArrayNode) } - val result = DecodeJsonObjectVisitor(json).apply { - visitObjectCollection(fieldName, null, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true, SchemaWriter(openAPI)) + val result = DecodeJsonObjectVisitor(json, openAPI).apply { + visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult() (result[fieldName]!!).let { listValue -> diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt index d01b7a8..4d9b09d 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt @@ -21,7 +21,6 @@ import com.exactpro.th2.codec.openapi.assertFloat import com.exactpro.th2.codec.openapi.assertInteger import com.exactpro.th2.codec.openapi.assertString import com.exactpro.th2.codec.openapi.OpenApiCodecSettings -import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.json.EncodeJsonArrayVisitor import com.exactpro.th2.common.grpc.ListValue import com.exactpro.th2.common.message.addField @@ -34,7 +33,6 @@ import com.exactpro.th2.codec.openapi.utils.getResourceAsText import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @@ -46,33 +44,33 @@ class JsonArrayTest { @Test fun `not supported encode`() { val message = message().build() - val visitor = EncodeJsonArrayVisitor(message) + val visitor = EncodeJsonArrayVisitor(message, openAPI) Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? String, StringSchema(), true) + visitor.visit("", StringSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Int, StringSchema(), true) + visitor.visit("", StringSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Double, StringSchema(), true) + visitor.visit("", StringSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Long, StringSchema(), true) + visitor.visit("", StringSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Float, StringSchema(), true) + visitor.visit("", StringSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Boolean, StringSchema(), true) + visitor.visit("", StringSchema(), true) } Assertions.assertThrows(UnsupportedOperationException::class.java) { - visitor.visit("", null as? Schema<*>, StringSchema(), true, SchemaWriter(openAPI)) + visitor.visit("", StringSchema(), true) } } @@ -111,8 +109,8 @@ class JsonArrayTest { val message = message().addField(fieldName, listValue).build() - val result = EncodeJsonArrayVisitor(message).apply { - visitObjectCollection(fieldName, null, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true, SchemaWriter(openAPI)) + val result = EncodeJsonArrayVisitor(message, openAPI).apply { + visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult() (mapper.readTree(result.toStringUtf8()) as ArrayNode).let { arrayNode -> @@ -134,9 +132,9 @@ class JsonArrayTest { fun `string array test encode`() { val fieldName = "stringField" val collection = listOf("stringValue1", "stringValue2", "stringValue3", "stringValue4") - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("string") - visitor.visitStringCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) collection.forEachIndexed { index, value -> Assertions.assertEquals(value, result.get(index).asText()) @@ -147,9 +145,9 @@ class JsonArrayTest { fun `boolean array test encode`() { val fieldName = "booleanField" val collection = listOf(true, false, false, true) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("boolean") - visitor.visitBooleanCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) collection.forEachIndexed { index, value -> Assertions.assertEquals(value, result.get(index).asBoolean()) @@ -160,9 +158,9 @@ class JsonArrayTest { fun `int array test encode`() { val fieldName = "intField" val collection = listOf(1, 2, 2, 4) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("integer") - visitor.visitIntegerCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) collection.forEachIndexed { index, value -> Assertions.assertEquals(value, result.get(index).asInt()) @@ -173,9 +171,9 @@ class JsonArrayTest { fun `float array test encode`() { val fieldName = "floatField" val collection = listOf(0.1f, 0.2f, 0.2f, 0.4f) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("number","float") - visitor.visitFloatCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) collection.forEachIndexed { index, value -> Assertions.assertEquals(value, result.get(index).asText().toFloat()) @@ -186,9 +184,9 @@ class JsonArrayTest { fun `double array test encode`() { val fieldName = "doubleField" val collection = listOf(0.1, 0.2, 0.2, 0.4) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("number", "double") - visitor.visitDoubleCollection(fieldName, null , schema, true) + visitor.visit(fieldName , schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) collection.forEachIndexed { index, value -> Assertions.assertEquals(value, result.get(index).asDouble()) @@ -199,9 +197,9 @@ class JsonArrayTest { fun `long array test encode`() { val fieldName = "longField" val collection = listOf(111111111L, 222222222L, 222222222L, 444444444L) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("integer", "int64") - visitor.visitLongCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) collection.forEachIndexed { index, value -> Assertions.assertEquals(value, result.get(index).asLong()) @@ -212,9 +210,9 @@ class JsonArrayTest { fun `big decimal array test encode`() { val fieldName = "decimalField" val collection = listOf(BigDecimal(100044000000), BigDecimal(100000030000), BigDecimal(100000001000), BigDecimal(100000022000)) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("number", "-") - visitor.visitLongCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) collection.forEachIndexed { index, value -> Assertions.assertEquals(value, result.get(index).asText().toBigDecimal()) diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt index 02afb98..f8097f3 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt @@ -21,7 +21,6 @@ import com.exactpro.th2.codec.openapi.assertFloat import com.exactpro.th2.codec.openapi.assertInteger import com.exactpro.th2.codec.openapi.assertString import com.exactpro.th2.codec.openapi.OpenApiCodecSettings -import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.json.EncodeJsonObjectVisitor import com.exactpro.th2.common.grpc.ListValue import com.exactpro.th2.common.message.addField @@ -35,7 +34,11 @@ import com.exactpro.th2.codec.openapi.utils.getResourceAsText import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.Schema +import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.NumberSchema +import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.math.BigDecimal @@ -70,8 +73,8 @@ class JsonObjectTest { addField(includedObject, includedObjectValue) }).build() - val result = EncodeJsonObjectVisitor(message).apply { - visit(fieldName, null as? Schema<*>, openAPI.components.schemas["ObjectTest"]!!, true, SchemaWriter(openAPI)) + val result = EncodeJsonObjectVisitor(message, openAPI).apply { + visit(fieldName, openAPI.components.schemas["ObjectTest"]!! as ObjectSchema, true) }.getResult().toStringUtf8() mapper.readTree(result).get(fieldName).let { objectNode -> @@ -88,9 +91,9 @@ class JsonObjectTest { fun `string test encode`() { val fieldName = "stringField" val simpleValue = "stringValue" - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) val schema = createTestSchema(simpleValue) - visitor.visit(fieldName, null as? String, schema, true) + visitor.visit(fieldName, schema as StringSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asText() Assertions.assertEquals(simpleValue, result) } @@ -99,9 +102,9 @@ class JsonObjectTest { fun `boolean test encode`() { val fieldName = "booleanField" val simpleValue = true - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) val schema = createTestSchema(simpleValue) - visitor.visit(fieldName, null as? Boolean, schema, true) + visitor.visit(fieldName, schema as BooleanSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asBoolean() Assertions.assertEquals(simpleValue, result) } @@ -110,9 +113,9 @@ class JsonObjectTest { fun `int test encode`() { val fieldName = "intField" val simpleValue = 123 - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) val schema = createTestSchema(simpleValue) - visitor.visit(fieldName, null as? Int, schema, true) + visitor.visit(fieldName, schema as IntegerSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asInt() Assertions.assertEquals(simpleValue, result) } @@ -121,9 +124,9 @@ class JsonObjectTest { fun `float test encode`() { val fieldName = "floatField" val simpleValue = 123.1f - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) val schema = createTestSchema(simpleValue) - visitor.visit(fieldName, null as? Float, schema, true) + visitor.visit(fieldName, schema as NumberSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asText()?.toFloat() Assertions.assertEquals(simpleValue, result) } @@ -132,9 +135,9 @@ class JsonObjectTest { fun `double test encode`() { val fieldName = "doubleField" val simpleValue = 123.1 - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) val schema = createTestSchema(simpleValue) - visitor.visit(fieldName, null as? Double, schema, true) + visitor.visit(fieldName, schema as NumberSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asDouble() Assertions.assertEquals(simpleValue, result) } @@ -143,9 +146,9 @@ class JsonObjectTest { fun `long test encode`() { val fieldName = "longField" val simpleValue = 123123L - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) val schema = createTestSchema(simpleValue) - visitor.visit(fieldName, null as? Long, schema, true) + visitor.visit(fieldName, schema as IntegerSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asLong() Assertions.assertEquals(simpleValue, result) } @@ -154,9 +157,9 @@ class JsonObjectTest { fun `big decimal test encode`() { val fieldName = "decimalField" val simpleValue = BigDecimal(100000000000) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) val schema = createTestSchema(simpleValue) - visitor.visit(fieldName, null as? BigDecimal, schema, true) + visitor.visit(fieldName, schema as NumberSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asText()?.toBigDecimal() Assertions.assertEquals(simpleValue, result) } @@ -165,9 +168,9 @@ class JsonObjectTest { fun `string array test encode`() { val fieldName = "stringField" val collection = listOf("stringValue1", "stringValue2", "stringValue3", "stringValue4") - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("string") - visitor.visitStringCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) Assertions.assertEquals(4, result.size()) collection.forEachIndexed { index, value -> @@ -179,9 +182,9 @@ class JsonObjectTest { fun `boolean array test encode`() { val fieldName = "booleanField" val collection = listOf(true, false, false, true) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("boolean") - visitor.visitBooleanCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) Assertions.assertEquals(4, result.size()) collection.forEachIndexed { index, value -> @@ -193,9 +196,9 @@ class JsonObjectTest { fun `int array test encode`() { val fieldName = "intField" val collection = listOf(1, 2, 2, 4) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("integer") - visitor.visitIntegerCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) Assertions.assertEquals(4, result.size()) collection.forEachIndexed { index, value -> @@ -207,9 +210,9 @@ class JsonObjectTest { fun `float array test encode`() { val fieldName = "floatField" val collection = listOf(0.1f, 0.2f, 0.2f, 0.4f) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("number","float") - visitor.visitFloatCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) Assertions.assertEquals(4, result.size()) collection.forEachIndexed { index, value -> @@ -221,9 +224,9 @@ class JsonObjectTest { fun `double array test encode`() { val fieldName = "doubleField" val collection = listOf(0.1, 0.2, 0.2, 0.4) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("number", "double") - visitor.visitDoubleCollection(fieldName, null , schema, true) + visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) Assertions.assertEquals(4, result.size()) collection.forEachIndexed { index, value -> @@ -235,9 +238,9 @@ class JsonObjectTest { fun `long array test encode`() { val fieldName = "longField" val collection = listOf(111111111L, 222222222L, 222222222L, 444444444L) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("integer", "int64") - visitor.visitLongCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) Assertions.assertEquals(4, result.size()) collection.forEachIndexed { index, value -> @@ -249,9 +252,9 @@ class JsonObjectTest { fun `big decimal array test encode`() { val fieldName = "decimalField" val collection = listOf(BigDecimal(100000000010), BigDecimal(100000002000), BigDecimal(100300000000), BigDecimal(100004000000)) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build()) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) val schema = createArrayTestSchema("number", "-") - visitor.visitBigDecimalCollection(fieldName, null, schema, true) + visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) Assertions.assertEquals(4, result.size()) collection.forEachIndexed { index, value -> @@ -294,8 +297,8 @@ class JsonObjectTest { val message = message().addField(fieldName, listValue).build() - val result = EncodeJsonObjectVisitor(message).apply { - visitObjectCollection(fieldName, null, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true, SchemaWriter(openAPI)) + val result = EncodeJsonObjectVisitor(message, openAPI).apply { + visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult().toStringUtf8() mapper.readTree(result).let { objectNode -> diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/SchemaTestUtils.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/SchemaTestUtils.kt index d34ef13..448ca3b 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/SchemaTestUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/SchemaTestUtils.kt @@ -16,6 +16,7 @@ package com.exactpro.th2.codec.openapi/* import io.swagger.v3.oas.models.media.ArraySchema import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema @@ -39,8 +40,8 @@ inline fun createTestSchema(value: T?, fillEnum: List? = null) } } Int::class -> { - return NumberSchema().apply { - type = "number" + return IntegerSchema().apply { + type = "int32" example = value fillEnum?.forEach { enum.add((it as Int).toBigDecimal()) @@ -48,8 +49,8 @@ inline fun createTestSchema(value: T?, fillEnum: List? = null) } } Long::class -> { - return NumberSchema().apply { - type = "number" + return IntegerSchema().apply { + type = "int64" example = value fillEnum?.forEach { enum.add((it as Long).toBigDecimal()) From e8a0207b0c49ff494edae4dd644e9b5dc44003cd Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 14:41:52 +0400 Subject: [PATCH 10/34] removed validate for type object/array in type field --- .../kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt | 5 ++--- .../com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt | 7 ------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index 3567d61..7514284 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -28,7 +28,6 @@ import com.exactpro.th2.codec.openapi.utils.extractType import com.exactpro.th2.codec.openapi.utils.getByMethod import com.exactpro.th2.codec.openapi.utils.getEndPoint import com.exactpro.th2.codec.openapi.utils.getMethods -import com.exactpro.th2.codec.openapi.utils.validateForType import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.VisitorFactory import com.exactpro.th2.common.grpc.MessageGroup @@ -79,7 +78,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin // Request methodValue.requestBody?.content?.forEach { (typeKey, typeValue) -> mapForName[combineName(pathKey, methodKey, typeKey)] = RequestContainer( - pattern, methodKey, dictionary.getEndPoint(typeValue.schema).validateForType(), typeKey, resultParams + pattern, methodKey, dictionary.getEndPoint(typeValue.schema), typeKey, resultParams ) if (methodValue.requestBody.required /*nullable*/ == false) { @@ -93,7 +92,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin methodValue.responses?.forEach { (responseKey, responseValue) -> responseValue.content?.forEach { (typeKey, typeValue) -> mapForName[combineName(pathKey, methodKey, responseKey, typeKey)] = ResponseContainer( - pattern, methodKey, responseKey, dictionary.getEndPoint(typeValue.schema).validateForType(), typeKey, resultParams + pattern, methodKey, responseKey, dictionary.getEndPoint(typeValue.schema), typeKey, resultParams ) } ?: run { mapForName[combineName(pathKey, methodKey, responseKey)] = ResponseContainer(pattern, methodKey, responseKey, null, null, resultParams) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt index 8d32952..862fdfa 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt @@ -76,13 +76,6 @@ fun Content.containingFormatOrNull(httpHeader: String) = when { else -> null } -fun Schema<*>.validateForType(): Schema<*> { - if (!this.isComposed()) { - checkNotNull(this.type) { "Type of schema [${this.name}] wasn't filled" } - } - return this -} - fun Schema<*>.isComposed() = this is ComposedSchema fun Schema<*>.isObject() = this is ObjectSchema fun Schema<*>.isArray() = this is ArraySchema From 96c811452001c043d9e27fb6d1317fd1167b88a3 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 14:48:44 +0400 Subject: [PATCH 11/34] required null check --- .../writer/visitors/json/DecodeJsonObjectVisitor.kt | 7 ++++++- .../writer/visitors/json/EncodeJsonObjectVisitor.kt | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index fc07a6c..bc82e34 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -101,5 +101,10 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope } } - override fun checkAgainst(fldStruct: ObjectSchema): Boolean = fromObject.fieldNames().asSequence().toList().containsAll(fldStruct.required) + override fun checkAgainst(fldStruct: ObjectSchema): Boolean { + if (fldStruct.required.isEmpty()) { + return true + } + return fromObject.fieldNames().asSequence().toList().containsAll(fldStruct.required) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index e8485fa..c16244f 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -90,7 +90,12 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open rootNode.put(fieldName, it) } - override fun checkAgainst(fldStruct: ObjectSchema): Boolean = from.fieldsMap.keys.containsAll(fldStruct.required) + override fun checkAgainst(fldStruct: ObjectSchema): Boolean { + if (fldStruct.required.isEmpty()) { + return true + } + return from.fieldsMap.keys.containsAll(fldStruct.required) + } private inline fun visitPrimitive(fieldName: String, fieldValue: Value?, fldStruct: Schema, convert: (Value) -> T, put: (T) -> Unit) { fieldValue?.run(convert)?.let { value -> From aa6f820210a8feef58a57fd645cb86fc6f1707f8 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 15:00:19 +0400 Subject: [PATCH 12/34] null or empty for required --- .../openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt | 2 +- .../openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index bc82e34..92c1989 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -102,7 +102,7 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope } override fun checkAgainst(fldStruct: ObjectSchema): Boolean { - if (fldStruct.required.isEmpty()) { + if (fldStruct.required.isNullOrEmpty()) { return true } return fromObject.fieldNames().asSequence().toList().containsAll(fldStruct.required) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index c16244f..263b869 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -91,7 +91,7 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } override fun checkAgainst(fldStruct: ObjectSchema): Boolean { - if (fldStruct.required.isEmpty()) { + if (fldStruct.required.isNullOrEmpty()) { return true } return from.fieldsMap.keys.containsAll(fldStruct.required) From c01b6198f7e8d341cb03950d9e8a422edfa34e47 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 16:45:45 +0400 Subject: [PATCH 13/34] try catch on visitor creating to detect which schema throws error --- .../com/exactpro/th2/codec/openapi/OpenApiCodec.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index 7514284..3d49122 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -50,6 +50,7 @@ import com.exactpro.th2.common.message.sessionAlias import io.swagger.v3.oas.models.PathItem import io.swagger.v3.oas.models.parameters.Parameter import mu.KotlinLogging +import java.lang.IllegalStateException class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettings) : IPipelineCodec { @@ -152,7 +153,11 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin LOGGER.debug { "Start of message encoding: ${message.messageType}" } - val visitor = VisitorFactory.createEncodeVisitor(container.bodyFormat!!, messageSchema, message, dictionary) + val visitor = try { + VisitorFactory.createEncodeVisitor(container.bodyFormat!!, messageSchema, message, dictionary) + } catch (e: Exception) { + throw IllegalStateException("Cannot create encode visitor for message: ${message.messageType} - ${container.body}", e) + } schemaWriter.traverse(visitor, messageSchema) val result = visitor.getResult() @@ -202,7 +207,12 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin val schema = dictionary.getEndPoint(checkNotNull(container.body) { "Container: $messageType did not contain schema body" }) val format = checkNotNull(container.bodyFormat) { "Container: $messageType did not contain schema bodyFormat" } - val visitor = VisitorFactory.createDecodeVisitor(format, schema, body, dictionary) + val visitor = try { + VisitorFactory.createDecodeVisitor(format, schema, body, dictionary) + } catch (e: Exception) { + throw IllegalStateException("Cannot create decode visitor for message: ${header.messageType} - ${container.body}", e) + } + schemaWriter.traverse(visitor, schema) return visitor.getResult().apply { if(rawMessage.hasParentEventId()) parentEventId = rawMessage.parentEventId From 0ce5b6641774405c51a27eda4e437b44922f7509 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 17:26:19 +0400 Subject: [PATCH 14/34] isResolveCombinators false by default, all schema<*> will be treated as objects --- README.md | 6 ++-- .../th2/codec/openapi/OpenApiCodecSettings.kt | 2 +- .../th2/codec/openapi/writer/SchemaWriter.kt | 34 +++++++++---------- .../openapi/writer/visitors/SchemaVisitor.kt | 5 +-- .../openapi/writer/visitors/VisitorFactory.kt | 7 ++-- .../visitors/json/DecodeJsonArrayVisitor.kt | 5 +-- .../visitors/json/DecodeJsonObjectVisitor.kt | 21 ++++++------ .../visitors/json/EncodeJsonArrayVisitor.kt | 5 +-- .../visitors/json/EncodeJsonObjectVisitor.kt | 13 ++++--- 9 files changed, 47 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 909afb5..ae4adde 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ Result of decode: * checkUndefinedFields - Enable or Disable warnings for all undefined fields inside object structures, true by default. **validationSettings (open api dictionary)** -* enableRecommendations - Enable or Disable recommendations, true by default. +* enableRecommendations - Enable or Disable recommendations, `true` by default. * enableApacheNginxUnderscoreRecommendation - Enable or Disable the recommendation check for Apache/Nginx potentially ignoring header with underscore by default. * enableOneOfWithPropertiesRecommendation - Enable or Disable the recommendation check for schemas containing properties and oneOf definitions. * enableUnusedSchemasRecommendation - Enable or Disable the recommendation check for unused schemas. @@ -152,8 +152,8 @@ Result of decode: * enableInvalidTypeRecommendation - Enable or Disable the recommendation check for the 'type' attribute. **dictionaryParseOption (open api dictionary)** -* resolve - true by default; -* resolveCombinators - true by default; +* resolve - `true` by default; +* resolveCombinators - will combine some schemas into one due allOf statement (`false` by default); * resolveFully; * flatten; * flattenComposedSchemas; diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt index b6bef8e..44a0a75 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt @@ -27,7 +27,7 @@ class OpenApiCodecSettings : IPipelineCodecSettings { val dictionaryParseOption: ParseOptions = ParseOptions().apply { isResolve = true isResolveFully = true - isResolveCombinators = true + isResolveCombinators = false } val checkUndefinedFields: Boolean = true } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index af1b3bd..2a90a4e 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -40,23 +40,6 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val throwUn ) { when (msgStructure) { is ArraySchema -> visitor.visit(ARRAY_TYPE, msgStructure, true) - is ObjectSchema -> { - if (throwUndefined) { - visitor.checkUndefined(msgStructure) - } - msgStructure.properties.forEach { (name, property) -> - when(property) { - is ArraySchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is StringSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is IntegerSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is NumberSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is BooleanSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is ObjectSchema -> visitor.visit(name, property, msgStructure.requiredContains(name), throwUndefined) - is ComposedSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - else -> error("Unsupported class of property: ${property::class.simpleName}") - } - } - } is ComposedSchema -> { when { !msgStructure.allOf.isNullOrEmpty() -> visitor.allOf(msgStructure.allOf.map { openApi.getEndPoint(it) } as List).forEach { @@ -71,7 +54,22 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val throwUn else -> throw IllegalStateException("Composed schema was empty at allOf, oneOf, anyOf lists") } } - else -> throw UnsupportedOperationException("Traverse does not support anything except array and object schema") + else -> { + if (throwUndefined) { + visitor.checkUndefined(msgStructure) + } + msgStructure.properties.forEach { (name, property) -> + when(property) { + is ArraySchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is StringSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is IntegerSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is NumberSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is BooleanSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is ComposedSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + else -> visitor.visit(name, property, msgStructure.requiredContains(name), throwUndefined) + } + } + } } } } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt index 86b2e92..362543b 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt @@ -9,6 +9,7 @@ import io.swagger.v3.oas.models.media.ComposedSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import java.lang.IllegalStateException @@ -16,14 +17,14 @@ sealed class SchemaVisitor { abstract val openAPI: OpenAPI abstract val from: FromType abstract fun getResult(): ToType - abstract fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean = true) + abstract fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean = true) abstract fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean = true) abstract fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) - abstract fun checkUndefined(objectSchema: ObjectSchema) + abstract fun checkUndefined(objectSchema: Schema<*>) abstract fun checkAgainst(fldStruct: ObjectSchema): Boolean fun oneOf(list: List): List = list.filter(this::checkAgainst).also { diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt index 5f36aa0..2f85bd3 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt @@ -27,7 +27,6 @@ import com.google.protobuf.ByteString import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema import io.swagger.v3.oas.models.media.ComposedSchema -import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.Schema object VisitorFactory { @@ -61,7 +60,6 @@ object VisitorFactory { private fun Schema<*>.defineType(dictionary: OpenAPI): JsonSchemaTypes { return when (this) { is ArraySchema -> JsonSchemaTypes.ARRAY - is ObjectSchema -> JsonSchemaTypes.OBJECT is ComposedSchema -> { val schemas = when { !this.anyOf.isNullOrEmpty() -> this.anyOf @@ -71,13 +69,12 @@ object VisitorFactory { } when (dictionary.getEndPoint(schemas.first())) { is ArraySchema -> JsonSchemaTypes.ARRAY - is ObjectSchema -> JsonSchemaTypes.OBJECT is ComposedSchema -> error("Unsupported level of abstraction, cannot parse composed scheme inside of composed scheme") //TODO: More than one level of abstraction isn't impossible, need to implement later, this is fast try of realization - else -> error("Unsupported type of first element from composed schema [${schemas.first()::class.simpleName}], array or object required") + else -> JsonSchemaTypes.OBJECT } } - else -> error("Unsupported type of json schema [${this::class.simpleName}], array or object required") + else -> JsonSchemaTypes.OBJECT } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt index ab72b2a..34c146a 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt @@ -21,6 +21,7 @@ import io.swagger.v3.oas.models.media.ComposedSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: OpenAPI) : SchemaVisitor.DecodeVisitor() { @@ -56,8 +57,8 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun checkUndefined(objectSchema: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun checkUndefined(objectSchema: Schema<*>) = throw UnsupportedOperationException("Array visitor supports only collections") override fun checkAgainst(fldStruct: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") override fun getResult(): Message.Builder = rootMessage diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 92c1989..7e075bf 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -36,7 +36,7 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope internal val rootMessage = message() private val fromObject = from as ObjectNode - override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) { + override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) { fromObject.getField(fieldName, required)?.let { message -> val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), openAPI) val writer = SchemaWriter(openAPI, throwUndefined) @@ -54,18 +54,17 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope is IntegerSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsLong() }) is BooleanSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBoolean() }) is StringSchema -> rootMessage.addField(fieldName, arrayNode.map { it.asText() }) - is ObjectSchema -> rootMessage.addField(fieldName, mutableListOf().apply { - arrayNode.forEach { - DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> - SchemaWriter(openAPI, throwUndefined).traverse(visitor, itemSchema) - visitor.rootMessage.build().run(this::add) - } - } - }) is ComposedSchema -> { TODO("Not yet implemented") } - else -> error("Unsupported items type: ${fldStruct.items::class.java}") + else -> rootMessage.addField(fieldName, mutableListOf().apply { + arrayNode.forEach { + DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> + SchemaWriter(openAPI, throwUndefined).traverse(visitor, itemSchema) + visitor.rootMessage.build().run(this::add) + } + } + }) } } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } } @@ -93,7 +92,7 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope } } - override fun checkUndefined(objectSchema: ObjectSchema) { + override fun checkUndefined(objectSchema: Schema<*>) { val names = objectSchema.properties.keys val undefined = fromObject.fieldNames().asSequence().toList().filter { !names.contains(it) } if (undefined.isNotEmpty()) { diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt index 37d425f..9f6669b 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt @@ -8,6 +8,7 @@ import io.swagger.v3.oas.models.media.ComposedSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema class EncodeJsonArrayVisitor(from: Message, openAPI: OpenAPI) : EncodeJsonObjectVisitor(from, openAPI) { @@ -16,8 +17,8 @@ class EncodeJsonArrayVisitor(from: Message, openAPI: OpenAPI) : EncodeJsonObject override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun checkUndefined(objectSchema: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun checkUndefined(objectSchema: Schema<*>) = throw UnsupportedOperationException("Array visitor supports only collections") override fun checkAgainst(fldStruct: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") override fun getResult(): ByteString = ByteString.copyFrom(rootNode.first().toString().toByteArray()) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 263b869..5de0cc3 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -33,7 +33,7 @@ import java.math.BigDecimal open class EncodeJsonObjectVisitor(override val from: Message, override val openAPI: OpenAPI) : SchemaVisitor.EncodeVisitor() { internal val rootNode: ObjectNode = mapper.createObjectNode() - override fun visit(fieldName: String, fldStruct: ObjectSchema, required: Boolean, throwUndefined: Boolean) { + override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) { from.getField(fieldName, required)?.getMessage()?.let { message -> val visitor = EncodeJsonObjectVisitor(message, openAPI) val writer = SchemaWriter(openAPI, throwUndefined) @@ -51,7 +51,10 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open is IntegerSchema -> rootNode.putArray(fieldName).putAll(listOfValues) is BooleanSchema -> rootNode.putArray(fieldName).putAll(listOfValues) is StringSchema -> rootNode.putArray(fieldName).putAll(listOfValues) - is ObjectSchema -> rootNode.putArray(fieldName).apply { + is ComposedSchema -> { + TODO("Not yet implemented") + } + else -> rootNode.putArray(fieldName).apply { val listOfNodes = mutableListOf() val writer = SchemaWriter(openAPI, throwUndefined) listOfValues.forEach { @@ -62,10 +65,6 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } listOfNodes.forEach { add(it) } } - is ComposedSchema -> { - TODO("Not yet implemented") - } - else -> error("Unsupported items type: ${fldStruct.items::class.java}") } } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } } @@ -112,7 +111,7 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } } - override fun checkUndefined(objectSchema: ObjectSchema) { + override fun checkUndefined(objectSchema: Schema<*>) { val names = objectSchema.properties.keys val undefined = from.fieldsMap.keys.filter { !names.contains(it) } if (undefined.isNotEmpty()) { From 50393ad5c08eb9a8263f0a9611ba10deae49e31a Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 17:49:30 +0400 Subject: [PATCH 15/34] not supported schemas exception --- .../exactpro/th2/codec/openapi/writer/SchemaWriter.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 2a90a4e..51ead4a 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -22,13 +22,22 @@ import com.exactpro.th2.codec.openapi.utils.requiredContains import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BinarySchema import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ByteArraySchema import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.DateSchema +import io.swagger.v3.oas.models.media.DateTimeSchema +import io.swagger.v3.oas.models.media.EmailSchema +import io.swagger.v3.oas.models.media.FileSchema import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.MapSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema +import io.swagger.v3.oas.models.media.UUIDSchema import java.lang.IllegalStateException @@ -66,6 +75,7 @@ class SchemaWriter constructor(private val openApi: OpenAPI, private val throwUn is NumberSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is BooleanSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is ComposedSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${property::class.simpleName} isn't supported for now") else -> visitor.visit(name, property, msgStructure.requiredContains(name), throwUndefined) } } From f81edcd99cf73fe202e36d76a4fabeda0cc8052e Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 18:30:49 +0400 Subject: [PATCH 16/34] don't throw on undefined for combined schemas, implemented composed schemas for visitors --- .../th2/codec/openapi/writer/SchemaWriter.kt | 11 ++++++----- .../visitors/json/DecodeJsonArrayVisitor.kt | 2 +- .../visitors/json/DecodeJsonObjectVisitor.kt | 13 +++++++++---- .../visitors/json/EncodeJsonObjectVisitor.kt | 15 ++++++++++----- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 51ead4a..8001e0b 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -41,24 +41,25 @@ import io.swagger.v3.oas.models.media.UUIDSchema import java.lang.IllegalStateException -class SchemaWriter constructor(private val openApi: OpenAPI, private val throwUndefined: Boolean = true) { +class SchemaWriter constructor(private val openApi: OpenAPI) { fun traverse( visitor: SchemaVisitor<*, *>, - msgStructure: Schema<*> + msgStructure: Schema<*>, + throwUndefined: Boolean = true ) { when (msgStructure) { is ArraySchema -> visitor.visit(ARRAY_TYPE, msgStructure, true) is ComposedSchema -> { when { !msgStructure.allOf.isNullOrEmpty() -> visitor.allOf(msgStructure.allOf.map { openApi.getEndPoint(it) } as List).forEach { - traverse(visitor, it) + traverse(visitor, it, false) } !msgStructure.oneOf.isNullOrEmpty() -> visitor.oneOf(msgStructure.oneOf.map { openApi.getEndPoint(it) } as List).forEach { - traverse(visitor, it) + traverse(visitor, it, false) } !msgStructure.anyOf.isNullOrEmpty() -> visitor.anyOf(msgStructure.anyOf.map { openApi.getEndPoint(it) } as List).forEach { - traverse(visitor, it) + traverse(visitor, it, false) } else -> throw IllegalStateException("Composed schema was empty at allOf, oneOf, anyOf lists") } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt index 34c146a..9495cc7 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt @@ -40,7 +40,7 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: is ObjectSchema -> rootMessage.addField(fieldName, mutableListOf().apply { fromArray.forEach { DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> - SchemaWriter(openAPI, throwUndefined).traverse(visitor, itemSchema) + SchemaWriter(openAPI).traverse(visitor, itemSchema, throwUndefined) visitor.rootMessage.build().run(this::add) } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 7e075bf..7467c77 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -39,8 +39,8 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) { fromObject.getField(fieldName, required)?.let { message -> val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), openAPI) - val writer = SchemaWriter(openAPI, throwUndefined) - writer.traverse(visitor, fldStruct) + val writer = SchemaWriter(openAPI) + writer.traverse(visitor, fldStruct, throwUndefined) rootMessage.addField(fieldName, visitor.rootMessage) } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } } @@ -60,7 +60,7 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope else -> rootMessage.addField(fieldName, mutableListOf().apply { arrayNode.forEach { DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> - SchemaWriter(openAPI, throwUndefined).traverse(visitor, itemSchema) + SchemaWriter(openAPI).traverse(visitor, itemSchema, throwUndefined) visitor.rootMessage.build().run(this::add) } } @@ -70,7 +70,12 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope } override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { - TODO("Not yet implemented") + fromObject.getField(fieldName, required)?.let { message -> + val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), openAPI) + val writer = SchemaWriter(openAPI) + writer.traverse(visitor, fldStruct, false) + rootMessage.addField(fieldName, visitor.rootMessage) + } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } } override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsBigDecimal(), fldStruct) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 5de0cc3..6636e1c 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -36,8 +36,8 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) { from.getField(fieldName, required)?.getMessage()?.let { message -> val visitor = EncodeJsonObjectVisitor(message, openAPI) - val writer = SchemaWriter(openAPI, throwUndefined) - writer.traverse(visitor, fldStruct) + val writer = SchemaWriter(openAPI) + writer.traverse(visitor, fldStruct, throwUndefined) rootNode.set(fieldName, visitor.rootNode) } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } } @@ -56,10 +56,10 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } else -> rootNode.putArray(fieldName).apply { val listOfNodes = mutableListOf() - val writer = SchemaWriter(openAPI, throwUndefined) + val writer = SchemaWriter(openAPI) listOfValues.forEach { EncodeJsonObjectVisitor(checkNotNull(it.getMessage()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> - writer.traverse(visitor, itemSchema) + writer.traverse(visitor, itemSchema, throwUndefined) visitor.rootNode.run(listOfNodes::add) } } @@ -70,7 +70,12 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { - TODO("Not yet implemented") + from.getField(fieldName, required)?.getMessage()?.let { message -> + val visitor = EncodeJsonObjectVisitor(message, openAPI) + val writer = SchemaWriter(openAPI) + writer.traverse(visitor, fldStruct, false) + rootNode.set(fieldName, visitor.rootNode) + } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } } override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getBigDecimal) { From b41a8cf44afaefc27b8648dccaac9ff886e48f80 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 18:33:32 +0400 Subject: [PATCH 17/34] don't throw on undefined for combined schemas, implemented composed schemas for visitors --- .../kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index 3d49122..91d5cba 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -52,11 +52,11 @@ import io.swagger.v3.oas.models.parameters.Parameter import mu.KotlinLogging import java.lang.IllegalStateException -class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettings) : IPipelineCodec { +class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSettings) : IPipelineCodec { private val typeToSchema: Map private val patternToPathItem: List> - private val schemaWriter = SchemaWriter(dictionary, settings.checkUndefinedFields) + private val schemaWriter = SchemaWriter(dictionary) init { val mapForName = mutableMapOf() @@ -158,7 +158,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin } catch (e: Exception) { throw IllegalStateException("Cannot create encode visitor for message: ${message.messageType} - ${container.body}", e) } - schemaWriter.traverse(visitor, messageSchema) + schemaWriter.traverse(visitor, messageSchema, settings.checkUndefinedFields) val result = visitor.getResult() LOGGER.trace { "Result of encoded message ${message.messageType}: $result" } @@ -213,7 +213,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, settings: OpenApiCodecSettin throw IllegalStateException("Cannot create decode visitor for message: ${header.messageType} - ${container.body}", e) } - schemaWriter.traverse(visitor, schema) + schemaWriter.traverse(visitor, schema, settings.checkUndefinedFields) return visitor.getResult().apply { if(rawMessage.hasParentEventId()) parentEventId = rawMessage.parentEventId sessionAlias = rawMessage.sessionAlias From f9e64ba8ccfab173682342af305ef25c7725b875 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 21:07:37 +0400 Subject: [PATCH 18/34] fixed check of type --- .../visitors/json/EncodeJsonObjectVisitor.kt | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 6636e1c..0ba2311 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -34,7 +34,12 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open internal val rootNode: ObjectNode = mapper.createObjectNode() override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) { - from.getField(fieldName, required)?.getMessage()?.let { message -> + from.getField(fieldName, required)?.let { field -> + when { + field.kindCase.number == 1 -> return + !field.hasMessageValue() -> error("$fieldName is not an message: ${field.kindCase}") + } + val message = field.getMessage()!! val visitor = EncodeJsonObjectVisitor(message, openAPI) val writer = SchemaWriter(openAPI) writer.traverse(visitor, fldStruct, throwUndefined) @@ -45,7 +50,13 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean) { val itemSchema = openAPI.getEndPoint(fldStruct.items) - from.getField(fieldName, required)?.getList()?.let { listOfValues -> + from.getField(fieldName, required)?.let { field -> + when { + field.kindCase.number == 1 -> return + !field.hasListValue() -> error("$fieldName is not an list: ${field.kindCase}") + } + val listOfValues = field.getList()!! + when (itemSchema) { is NumberSchema -> rootNode.putArray(fieldName).putAll(listOfValues) is IntegerSchema -> rootNode.putArray(fieldName).putAll(listOfValues) @@ -70,7 +81,13 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { - from.getField(fieldName, required)?.getMessage()?.let { message -> + from.getField(fieldName, required)?.let { field -> + when { + field.kindCase.number == 1 -> return + !field.hasMessageValue() -> error("$fieldName is not an message: ${field.kindCase}") + } + val message = field.getMessage()!! + val visitor = EncodeJsonObjectVisitor(message, openAPI) val writer = SchemaWriter(openAPI) writer.traverse(visitor, fldStruct, false) From bae40777e41f12b6196918e0f9ee81d1af9e9613 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 23 May 2022 21:15:40 +0400 Subject: [PATCH 19/34] check convert of simple type --- .../writer/visitors/json/EncodeJsonObjectVisitor.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 0ba2311..5cbb8c4 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -118,10 +118,11 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open return from.fieldsMap.keys.containsAll(fldStruct.required) } - private inline fun visitPrimitive(fieldName: String, fieldValue: Value?, fldStruct: Schema, convert: (Value) -> T, put: (T) -> Unit) { - fieldValue?.run(convert)?.let { value -> - fldStruct.checkEnum(value, fieldName) - put(value) + private inline fun visitPrimitive(fieldName: String, fieldValue: Value?, fldStruct: Schema, convert: (Value) -> T, put: (T) -> Unit) { + fieldValue?.let { value -> + val converted = checkNotNull(convert(value)) { "Cannot convert field $fieldName to ${T::class.simpleName}" } + fldStruct.checkEnum(converted, fieldName) + put(converted) } ?: fldStruct.default?.run(put) } From 9f3f21bc11fb46879deeddb2c1fe21f9e50b9632 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Tue, 24 May 2022 03:32:28 +0400 Subject: [PATCH 20/34] Array supports composed objects schemas in each visitor --- .../visitors/json/DecodeJsonArrayVisitor.kt | 16 +++++++--- .../visitors/json/DecodeJsonObjectVisitor.kt | 32 ++++++++++--------- .../visitors/json/EncodeJsonObjectVisitor.kt | 20 ++++++------ 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt index 9495cc7..d83306c 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt @@ -16,13 +16,22 @@ import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.JsonNodeFactory import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BinarySchema import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ByteArraySchema import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.DateSchema +import io.swagger.v3.oas.models.media.DateTimeSchema +import io.swagger.v3.oas.models.media.EmailSchema +import io.swagger.v3.oas.models.media.FileSchema import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.MapSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema +import io.swagger.v3.oas.models.media.UUIDSchema class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: OpenAPI) : SchemaVisitor.DecodeVisitor() { @@ -37,7 +46,8 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: is IntegerSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsLong() }) is BooleanSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsBoolean() }) is StringSchema -> rootMessage.addField(fieldName, fromArray.map { it.asText() }) - is ObjectSchema -> rootMessage.addField(fieldName, mutableListOf().apply { + is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") + else -> rootMessage.addField(fieldName, mutableListOf().apply { fromArray.forEach { DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> SchemaWriter(openAPI).traverse(visitor, itemSchema, throwUndefined) @@ -45,10 +55,6 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: } } }) - is ComposedSchema -> { - TODO("Not yet implemented") - } - else -> error("Unsupported items type: ${fldStruct.items::class.java}") } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 7467c77..679801e 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -20,13 +20,22 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.fasterxml.jackson.databind.node.ObjectNode import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BinarySchema import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ByteArraySchema import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.DateSchema +import io.swagger.v3.oas.models.media.DateTimeSchema +import io.swagger.v3.oas.models.media.EmailSchema +import io.swagger.v3.oas.models.media.FileSchema import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.MapSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema +import io.swagger.v3.oas.models.media.UUIDSchema import java.lang.IllegalStateException open class DecodeJsonObjectVisitor(override val from: JsonNode, override val openAPI: OpenAPI) : SchemaVisitor.DecodeVisitor() { @@ -54,17 +63,15 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope is IntegerSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsLong() }) is BooleanSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBoolean() }) is StringSchema -> rootMessage.addField(fieldName, arrayNode.map { it.asText() }) - is ComposedSchema -> { - TODO("Not yet implemented") - } + is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") else -> rootMessage.addField(fieldName, mutableListOf().apply { - arrayNode.forEach { - DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> - SchemaWriter(openAPI).traverse(visitor, itemSchema, throwUndefined) - visitor.rootMessage.build().run(this::add) + arrayNode.forEach { + DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> + SchemaWriter(openAPI).traverse(visitor, itemSchema, throwUndefined) + visitor.rootMessage.build().run(this::add) + } } - } - }) + }) } } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } } @@ -105,10 +112,5 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope } } - override fun checkAgainst(fldStruct: ObjectSchema): Boolean { - if (fldStruct.required.isNullOrEmpty()) { - return true - } - return fromObject.fieldNames().asSequence().toList().containsAll(fldStruct.required) - } + override fun checkAgainst(fldStruct: ObjectSchema): Boolean = fldStruct.required.isNullOrEmpty() || fromObject.fieldNames().asSequence().toList().containsAll(fldStruct.required) } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 5cbb8c4..324eb68 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -20,13 +20,22 @@ import com.fasterxml.jackson.databind.node.ObjectNode import com.google.protobuf.ByteString import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema +import io.swagger.v3.oas.models.media.BinarySchema import io.swagger.v3.oas.models.media.BooleanSchema +import io.swagger.v3.oas.models.media.ByteArraySchema import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.DateSchema +import io.swagger.v3.oas.models.media.DateTimeSchema +import io.swagger.v3.oas.models.media.EmailSchema +import io.swagger.v3.oas.models.media.FileSchema import io.swagger.v3.oas.models.media.IntegerSchema +import io.swagger.v3.oas.models.media.MapSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.ObjectSchema +import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema +import io.swagger.v3.oas.models.media.UUIDSchema import java.lang.IllegalStateException import java.math.BigDecimal @@ -62,9 +71,7 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open is IntegerSchema -> rootNode.putArray(fieldName).putAll(listOfValues) is BooleanSchema -> rootNode.putArray(fieldName).putAll(listOfValues) is StringSchema -> rootNode.putArray(fieldName).putAll(listOfValues) - is ComposedSchema -> { - TODO("Not yet implemented") - } + is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") else -> rootNode.putArray(fieldName).apply { val listOfNodes = mutableListOf() val writer = SchemaWriter(openAPI) @@ -111,12 +118,7 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open rootNode.put(fieldName, it) } - override fun checkAgainst(fldStruct: ObjectSchema): Boolean { - if (fldStruct.required.isNullOrEmpty()) { - return true - } - return from.fieldsMap.keys.containsAll(fldStruct.required) - } + override fun checkAgainst(fldStruct: ObjectSchema): Boolean = fldStruct.required.isNullOrEmpty() || from.fieldsMap.keys.containsAll(fldStruct.required) private inline fun visitPrimitive(fieldName: String, fieldValue: Value?, fldStruct: Schema, convert: (Value) -> T, put: (T) -> Unit) { fieldValue?.let { value -> From 310adfd03e4bed80435e776c4bacc55bf0f8206b Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Tue, 24 May 2022 03:36:42 +0400 Subject: [PATCH 21/34] version up and readme updated --- README.md | 7 ++++++- gradle.properties | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae4adde..fbcb1c7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # OpenApi Codec -![version](https://img.shields.io/badge/version-0.2.0-blue.svg) +![version](https://img.shields.io/badge/version-0.3.0-blue.svg) This microservice can validate open api dictionary, encode th2 messages or decode http body. @@ -175,6 +175,11 @@ May be empty due to missing required fields ## Release notes +### 0.3.0 + ++ Feature: anyOf, allOf, oneOf support for objects and arrays ++ Fix: Reworked visitor interface + ### 0.2.0 + Feature: check for undefined fields and throw errors if they are found diff --git a/gradle.properties b/gradle.properties index 21726cd..22b590a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ kotlin.code.style=official kotlin_version=1.5.31 -release_version=0.2.0 +release_version=0.3.0 vcs_url=https://github.com/th2-net/th2-codec-open-api \ No newline at end of file From 9ac4d7e912517a452ae2d7ed7bd8a6f2f35ecbfb Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Tue, 24 May 2022 03:43:03 +0400 Subject: [PATCH 22/34] copyright updated --- .../openapi/writer/visitors/SchemaVisitor.kt | 16 ++++++++++++++++ .../visitors/json/DecodeJsonArrayVisitor.kt | 16 ++++++++++++++++ .../visitors/json/DecodeJsonObjectVisitor.kt | 16 ++++++++++++++++ .../visitors/json/EncodeJsonArrayVisitor.kt | 16 ++++++++++++++++ .../visitors/json/EncodeJsonObjectVisitor.kt | 16 ++++++++++++++++ .../exactpro/th2/codec/openapi/EmptyBodyTests.kt | 16 ++++++++++++++++ .../th2/codec/openapi/ParametersTests.kt | 16 ++++++++++++++++ .../codec/openapi/ValidationDictionaryTests.kt | 4 +++- .../openapi/json/visitor/decode/JsonArrayTest.kt | 2 +- .../json/visitor/decode/JsonObjectTest.kt | 4 ++-- .../openapi/json/visitor/encode/JsonArrayTest.kt | 10 +++++----- .../json/visitor/encode/JsonObjectTest.kt | 12 ++++++------ .../th2/codec/openapi/utils/JsonTestUtils.kt | 4 +++- .../th2/codec/openapi/utils/SchemaTestUtils.kt | 4 +++- .../th2/codec/openapi/utils/TestUtils.kt | 4 +++- 15 files changed, 138 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt index 362543b..54aa232 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.exactpro.th2.codec.openapi.writer.visitors import com.exactpro.th2.common.grpc.Message diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt index d83306c..7410502 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.exactpro.th2.codec.openapi.writer.visitors.json import com.exactpro.th2.codec.openapi.utils.getEndPoint diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 679801e..69ac1da 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.exactpro.th2.codec.openapi.writer.visitors.json import com.exactpro.th2.codec.openapi.utils.checkEnum diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt index 9f6669b..7f6b541 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.exactpro.th2.codec.openapi.writer.visitors.json import com.exactpro.th2.common.grpc.Message diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 324eb68..abb0829 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.exactpro.th2.codec.openapi.writer.visitors.json import com.exactpro.th2.codec.openapi.utils.checkEnum diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/EmptyBodyTests.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/EmptyBodyTests.kt index 5f700e8..12ab650 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/EmptyBodyTests.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/EmptyBodyTests.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.exactpro.th2.codec.openapi import com.exactpro.th2.codec.openapi.utils.getResourceAsText diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/ParametersTests.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/ParametersTests.kt index 2ecfb3d..379c697 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/ParametersTests.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/ParametersTests.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.exactpro.th2.codec.openapi import com.exactpro.th2.codec.openapi.OpenApiCodec.Companion.HEADERS_FIELD diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/ValidationDictionaryTests.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/ValidationDictionaryTests.kt index edd1265..3b1dea0 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/ValidationDictionaryTests.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/ValidationDictionaryTests.kt @@ -1,4 +1,4 @@ -package com.exactpro.th2.codec.openapi/* +/* * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,8 @@ package com.exactpro.th2.codec.openapi/* * limitations under the License. */ +package com.exactpro.th2.codec.openapi + import com.exactpro.th2.codec.api.DictionaryAlias import com.exactpro.th2.codec.api.IPipelineCodecContext import com.exactpro.th2.codec.openapi.throwable.DictionaryValidationException diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt index 5126ca2..851081c 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt @@ -27,7 +27,7 @@ import com.exactpro.th2.common.value.getList import com.exactpro.th2.common.value.toValue import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ObjectNode -import com.exactpro.th2.codec.openapi.createArrayTestSchema +import com.exactpro.th2.codec.openapi.utils.createArrayTestSchema import com.exactpro.th2.codec.openapi.utils.getResourceAsText import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt index 23d5acc..d7997cf 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt @@ -28,8 +28,8 @@ import com.exactpro.th2.common.value.getList import com.exactpro.th2.common.value.toValue import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ObjectNode -import com.exactpro.th2.codec.openapi.createArrayTestSchema -import com.exactpro.th2.codec.openapi.createTestSchema +import com.exactpro.th2.codec.openapi.utils.createArrayTestSchema +import com.exactpro.th2.codec.openapi.utils.createTestSchema import com.exactpro.th2.codec.openapi.utils.getResourceAsText import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt index 4d9b09d..90d46b8 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt @@ -16,10 +16,10 @@ package com.exactpro.th2.codec.openapi.json.visitor.encode -import com.exactpro.th2.codec.openapi.assertBoolean -import com.exactpro.th2.codec.openapi.assertFloat -import com.exactpro.th2.codec.openapi.assertInteger -import com.exactpro.th2.codec.openapi.assertString +import com.exactpro.th2.codec.openapi.utils.assertBoolean +import com.exactpro.th2.codec.openapi.utils.assertFloat +import com.exactpro.th2.codec.openapi.utils.assertInteger +import com.exactpro.th2.codec.openapi.utils.assertString import com.exactpro.th2.codec.openapi.OpenApiCodecSettings import com.exactpro.th2.codec.openapi.writer.visitors.json.EncodeJsonArrayVisitor import com.exactpro.th2.common.grpc.ListValue @@ -28,7 +28,7 @@ import com.exactpro.th2.common.message.message import com.exactpro.th2.common.value.toValue import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ArrayNode -import com.exactpro.th2.codec.openapi.createArrayTestSchema +import com.exactpro.th2.codec.openapi.utils.createArrayTestSchema import com.exactpro.th2.codec.openapi.utils.getResourceAsText import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt index f8097f3..85be792 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt @@ -16,10 +16,10 @@ package com.exactpro.th2.codec.openapi.json.visitor.encode -import com.exactpro.th2.codec.openapi.assertBoolean -import com.exactpro.th2.codec.openapi.assertFloat -import com.exactpro.th2.codec.openapi.assertInteger -import com.exactpro.th2.codec.openapi.assertString +import com.exactpro.th2.codec.openapi.utils.assertBoolean +import com.exactpro.th2.codec.openapi.utils.assertFloat +import com.exactpro.th2.codec.openapi.utils.assertInteger +import com.exactpro.th2.codec.openapi.utils.assertString import com.exactpro.th2.codec.openapi.OpenApiCodecSettings import com.exactpro.th2.codec.openapi.writer.visitors.json.EncodeJsonObjectVisitor import com.exactpro.th2.common.grpc.ListValue @@ -28,8 +28,8 @@ import com.exactpro.th2.common.message.message import com.exactpro.th2.common.value.toValue import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ArrayNode -import com.exactpro.th2.codec.openapi.createArrayTestSchema -import com.exactpro.th2.codec.openapi.createTestSchema +import com.exactpro.th2.codec.openapi.utils.createArrayTestSchema +import com.exactpro.th2.codec.openapi.utils.createTestSchema import com.exactpro.th2.codec.openapi.utils.getResourceAsText import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/JsonTestUtils.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/JsonTestUtils.kt index 946fce8..6543215 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/JsonTestUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/JsonTestUtils.kt @@ -1,4 +1,4 @@ -package com.exactpro.th2.codec.openapi/* +/* * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,8 @@ package com.exactpro.th2.codec.openapi/* * limitations under the License. */ +package com.exactpro.th2.codec.openapi.utils + import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.JsonNodeType diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/SchemaTestUtils.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/SchemaTestUtils.kt index 448ca3b..5ed98ce 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/SchemaTestUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/SchemaTestUtils.kt @@ -1,4 +1,4 @@ -package com.exactpro.th2.codec.openapi/* +/* * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,8 @@ package com.exactpro.th2.codec.openapi/* * limitations under the License. */ +package com.exactpro.th2.codec.openapi.utils + import io.swagger.v3.oas.models.media.ArraySchema import io.swagger.v3.oas.models.media.BooleanSchema import io.swagger.v3.oas.models.media.IntegerSchema diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/TestUtils.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/TestUtils.kt index 0d5eef4..117ff26 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/TestUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/TestUtils.kt @@ -1,4 +1,4 @@ -package com.exactpro.th2.codec.openapi.utils/* +/* * Copyright 2021-2022 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,8 @@ package com.exactpro.th2.codec.openapi.utils/* * limitations under the License. */ +package com.exactpro.th2.codec.openapi.utils + import com.exactpro.th2.codec.openapi.OpenApiCodec import com.exactpro.th2.common.assertEqualMessages import com.exactpro.th2.common.assertString From ce08f6b0183babfb4fef3af16e9640c26f76aab7 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Tue, 24 May 2022 13:28:20 +0400 Subject: [PATCH 23/34] reworked oneOf - first valid will be chosen, if there will be more than one valid - will be chosen one with unique fields contained refactored some method of visitors, moved to abstract realization removed objectSchema from composed choice, objects can be as Schema<*> as valid one --- .../th2/codec/openapi/utils/OpenApiUtils.kt | 5 +++ .../th2/codec/openapi/writer/SchemaWriter.kt | 7 ++-- .../openapi/writer/visitors/SchemaVisitor.kt | 39 +++++++++++++------ .../visitors/json/DecodeJsonArrayVisitor.kt | 5 +-- .../visitors/json/DecodeJsonObjectVisitor.kt | 12 +----- .../visitors/json/EncodeJsonArrayVisitor.kt | 4 +- .../visitors/json/EncodeJsonObjectVisitor.kt | 15 ++----- .../json/decode/JsonObjectDecodeTests.kt | 8 +--- .../json/encode/JsonObjectEncodeTests.kt | 5 +-- 9 files changed, 46 insertions(+), 54 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt index 862fdfa..7abefb2 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt @@ -76,6 +76,11 @@ fun Content.containingFormatOrNull(httpHeader: String) = when { else -> null } +fun Schema<*>.getExclusiveProperties(against: List>): List = mutableListOf().apply { + addAll(properties.keys) + removeAll { name -> against.find { it.properties.keys.contains(name) } != null } +} + fun Schema<*>.isComposed() = this is ComposedSchema fun Schema<*>.isObject() = this is ObjectSchema fun Schema<*>.isArray() = this is ArraySchema diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 8001e0b..5dd1c6d 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -33,7 +33,6 @@ import io.swagger.v3.oas.models.media.FileSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.MapSchema import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema @@ -52,13 +51,13 @@ class SchemaWriter constructor(private val openApi: OpenAPI) { is ArraySchema -> visitor.visit(ARRAY_TYPE, msgStructure, true) is ComposedSchema -> { when { - !msgStructure.allOf.isNullOrEmpty() -> visitor.allOf(msgStructure.allOf.map { openApi.getEndPoint(it) } as List).forEach { + !msgStructure.allOf.isNullOrEmpty() -> visitor.allOf(msgStructure.allOf.map { openApi.getEndPoint(it) }).forEach { traverse(visitor, it, false) } - !msgStructure.oneOf.isNullOrEmpty() -> visitor.oneOf(msgStructure.oneOf.map { openApi.getEndPoint(it) } as List).forEach { + !msgStructure.oneOf.isNullOrEmpty() -> visitor.oneOf(msgStructure.oneOf.map { openApi.getEndPoint(it) }).also { traverse(visitor, it, false) } - !msgStructure.anyOf.isNullOrEmpty() -> visitor.anyOf(msgStructure.anyOf.map { openApi.getEndPoint(it) } as List).forEach { + !msgStructure.anyOf.isNullOrEmpty() -> visitor.anyOf(msgStructure.anyOf.map { openApi.getEndPoint(it) }).forEach { traverse(visitor, it, false) } else -> throw IllegalStateException("Composed schema was empty at allOf, oneOf, anyOf lists") diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt index 54aa232..5a698b8 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt @@ -16,6 +16,7 @@ package com.exactpro.th2.codec.openapi.writer.visitors +import com.exactpro.th2.codec.openapi.utils.getExclusiveProperties import com.exactpro.th2.common.grpc.Message import com.google.protobuf.ByteString import io.swagger.v3.oas.models.OpenAPI @@ -24,7 +25,6 @@ import io.swagger.v3.oas.models.media.BooleanSchema import io.swagger.v3.oas.models.media.ComposedSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import java.lang.IllegalStateException @@ -33,6 +33,7 @@ sealed class SchemaVisitor { abstract val openAPI: OpenAPI abstract val from: FromType abstract fun getResult(): ToType + abstract fun getFieldNames(): Collection abstract fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean = true) abstract fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean = true) abstract fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) @@ -40,28 +41,44 @@ sealed class SchemaVisitor { abstract fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) - abstract fun checkUndefined(objectSchema: Schema<*>) - abstract fun checkAgainst(fldStruct: ObjectSchema): Boolean - fun oneOf(list: List): List = list.filter(this::checkAgainst).also { - if (it.size != 1) { - throw IllegalStateException("OneOf statement have ${it.size} valid schemas") - } - } + fun oneOf(list: List>): Schema<*> = chooseOneOf(list.filter(this::checkAgainst)) - fun anyOf(list: List): List = list.filter(this::checkAgainst).also{ + fun anyOf(list: List>): List> = list.filter(this::checkAgainst).also { if (it.isEmpty()) { throw IllegalStateException("AnyOf statement had zero valid schemas") } } - fun allOf(list: List): List = list.filter(this::checkAgainst).also{ + fun allOf(list: List>): List> = list.filter(this::checkAgainst).also { if (list.size != it.size) { throw IllegalStateException("AllOf statement have only ${it.size} valid schemas of ${list.size} available") } } + fun checkUndefined(objectSchema: Schema<*>) { + val names = objectSchema.properties.keys + val undefined = getFieldNames().filter { !names.contains(it) } + if (undefined.isNotEmpty()) { + throw IllegalStateException("Message have undefined fields: ${undefined.joinToString(", ")}") + } + } + + private fun checkAgainst(fldStruct: Schema<*>): Boolean = fldStruct.required.isNullOrEmpty() || getFieldNames().containsAll(fldStruct.required) + + private fun chooseOneOf(list: List>): Schema<*> = when(list.size) { + 0 -> throw IllegalStateException("OneOf statement have 0 valid schemas") + 1 -> list[0] + else -> { + val objectFieldNames = getFieldNames() + list.find { schema -> + val exclusiveNames = schema.getExclusiveProperties(list.toMutableList().apply { remove(schema) }) + objectFieldNames.find { exclusiveNames.contains(it) } != null + } ?: list[0] + } + } + abstract class EncodeVisitor : SchemaVisitor() abstract class DecodeVisitor : SchemaVisitor() -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt index 7410502..d8f5249 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt @@ -43,7 +43,6 @@ import io.swagger.v3.oas.models.media.FileSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.MapSchema import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema @@ -80,9 +79,8 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun checkUndefined(objectSchema: Schema<*>) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun checkAgainst(fldStruct: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun getFieldNames() = throw UnsupportedOperationException("Array visitor supports only collections") override fun getResult(): Message.Builder = rootMessage @@ -91,4 +89,5 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: nodeFactory = JsonNodeFactory.withExactBigDecimals(true) } } + } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 69ac1da..83b5c3f 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -47,12 +47,10 @@ import io.swagger.v3.oas.models.media.FileSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.MapSchema import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import io.swagger.v3.oas.models.media.UUIDSchema -import java.lang.IllegalStateException open class DecodeJsonObjectVisitor(override val from: JsonNode, override val openAPI: OpenAPI) : SchemaVisitor.DecodeVisitor() { @@ -112,6 +110,7 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope } } + override fun getFieldNames(): Collection = fromObject.fieldNames().asSequence().toList() override fun getResult(): Message.Builder = rootMessage private companion object { @@ -120,13 +119,4 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope } } - override fun checkUndefined(objectSchema: Schema<*>) { - val names = objectSchema.properties.keys - val undefined = fromObject.fieldNames().asSequence().toList().filter { !names.contains(it) } - if (undefined.isNotEmpty()) { - throw IllegalStateException("Message have undefined fields: ${undefined.joinToString(", ")}") - } - } - - override fun checkAgainst(fldStruct: ObjectSchema): Boolean = fldStruct.required.isNullOrEmpty() || fromObject.fieldNames().asSequence().toList().containsAll(fldStruct.required) } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt index 7f6b541..1f57e9c 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt @@ -23,7 +23,6 @@ import io.swagger.v3.oas.models.media.BooleanSchema import io.swagger.v3.oas.models.media.ComposedSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema @@ -34,8 +33,7 @@ class EncodeJsonArrayVisitor(from: Message, openAPI: OpenAPI) : EncodeJsonObject override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun checkUndefined(objectSchema: Schema<*>) = throw UnsupportedOperationException("Array visitor supports only collections") - override fun checkAgainst(fldStruct: ObjectSchema) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun getFieldNames() = throw UnsupportedOperationException("Array visitor supports only collections") override fun getResult(): ByteString = ByteString.copyFrom(rootNode.first().toString().toByteArray()) } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index abb0829..41538f5 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -47,12 +47,10 @@ import io.swagger.v3.oas.models.media.FileSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.MapSchema import io.swagger.v3.oas.models.media.NumberSchema -import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import io.swagger.v3.oas.models.media.UUIDSchema -import java.lang.IllegalStateException import java.math.BigDecimal open class EncodeJsonObjectVisitor(override val from: Message, override val openAPI: OpenAPI) : SchemaVisitor.EncodeVisitor() { @@ -134,7 +132,9 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open rootNode.put(fieldName, it) } - override fun checkAgainst(fldStruct: ObjectSchema): Boolean = fldStruct.required.isNullOrEmpty() || from.fieldsMap.keys.containsAll(fldStruct.required) + override fun getFieldNames(): Collection = from.fieldsMap.keys + override fun getResult(): ByteString = ByteString.copyFrom(rootNode.toString().toByteArray()) + private inline fun visitPrimitive(fieldName: String, fieldValue: Value?, fldStruct: Schema, convert: (Value) -> T, put: (T) -> Unit) { fieldValue?.let { value -> @@ -144,7 +144,6 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } ?: fldStruct.default?.run(put) } - override fun getResult(): ByteString = ByteString.copyFrom(rootNode.toString().toByteArray()) private companion object { val mapper = ObjectMapper().apply { @@ -152,12 +151,4 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } } - override fun checkUndefined(objectSchema: Schema<*>) { - val names = objectSchema.properties.keys - val undefined = from.fieldsMap.keys.filter { !names.contains(it) } - if (undefined.isNotEmpty()) { - throw IllegalStateException("Message have undefined fields: ${undefined.joinToString(", ")}") - } - } - } \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonObjectDecodeTests.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonObjectDecodeTests.kt index 1c8eb26..5e3a599 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonObjectDecodeTests.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/decode/JsonObjectDecodeTests.kt @@ -117,12 +117,8 @@ class JsonObjectDecodeTests { "300", "application/json", """{ - "oneOfInteger" : 1234567, - "oneOfEnabled" : true, - "publicKey" : "1234567", - "testEnabled" : true, - "testStatus" : "FAILED" - }""".trimIndent())!! + "onlyWrongOne" : 1234567, + }""".trimIndent())!! } } diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/encode/JsonObjectEncodeTests.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/encode/JsonObjectEncodeTests.kt index 7ace09c..f29e279 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/encode/JsonObjectEncodeTests.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/encode/JsonObjectEncodeTests.kt @@ -93,10 +93,7 @@ class JsonObjectEncodeTests { Assertions.assertThrows(EncodeException::class.java) { codec.testEncode("/test", "get", "300", "application/json", "json") { - addField("oneOfInteger", "1234567") - addField("oneOfEnabled", false) - addField("publicKey", "1234567") - addField("testEnabled", true) + addField("onlyWrongOne", "1234567") } } } From 8a3d826626466516f6add18207e97de09dfcadcd Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Tue, 24 May 2022 14:05:06 +0400 Subject: [PATCH 24/34] check null value for primitive on encode --- .../openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 41538f5..683972f 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -138,6 +138,9 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open private inline fun visitPrimitive(fieldName: String, fieldValue: Value?, fldStruct: Schema, convert: (Value) -> T, put: (T) -> Unit) { fieldValue?.let { value -> + if (value.kindCase.number == 1) { + return + } val converted = checkNotNull(convert(value)) { "Cannot convert field $fieldName to ${T::class.simpleName}" } fldStruct.checkEnum(converted, fieldName) put(converted) From fc38368201cab6c5d551dc94ef93340b987b6ec5 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Tue, 24 May 2022 14:30:55 +0400 Subject: [PATCH 25/34] nullValue into empty scheme --- .../exactpro/th2/codec/openapi/utils/CommonUtils.kt | 4 +++- .../writer/visitors/json/EncodeJsonObjectVisitor.kt | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/CommonUtils.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/CommonUtils.kt index 48c7894..0dc68fe 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/CommonUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/CommonUtils.kt @@ -27,7 +27,9 @@ import com.exactpro.th2.common.value.getString * @param required check if value is required, used only if extracted value was null */ fun Message.getField(fieldName: String, required: Boolean): Value? = this[fieldName].apply { - if (required) checkNotNull(this) { "Field [$fieldName] is required for message [$messageType]" } + if (required) { + check(this != null && this.kindCase.number != 1) { "Field [$fieldName] is required for message [$messageType]" } + } } fun Value.getBoolean(): Boolean? = this.getString()?.toBooleanStrictOrNull() \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 683972f..9da6e1f 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -59,7 +59,10 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) { from.getField(fieldName, required)?.let { field -> when { - field.kindCase.number == 1 -> return + field.kindCase.number == 1 -> { + rootNode.set(fieldName, ObjectNode(mapper.nodeFactory)) + return + } !field.hasMessageValue() -> error("$fieldName is not an message: ${field.kindCase}") } val message = field.getMessage()!! @@ -75,7 +78,10 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open from.getField(fieldName, required)?.let { field -> when { - field.kindCase.number == 1 -> return + field.kindCase.number == 1 -> { + rootNode.putArray(fieldName) + return + } !field.hasListValue() -> error("$fieldName is not an list: ${field.kindCase}") } val listOfValues = field.getList()!! From fe342278fa54f4a6126d8e96eb63dcd08625b1e5 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Wed, 25 May 2022 00:12:46 +0400 Subject: [PATCH 26/34] Date format support, VisitorSettings created --- README.md | 4 ++- .../th2/codec/openapi/OpenApiCodec.kt | 7 ++-- .../th2/codec/openapi/OpenApiCodecSettings.kt | 1 + .../th2/codec/openapi/writer/SchemaWriter.kt | 5 +-- .../openapi/writer/visitors/SchemaVisitor.kt | 11 ++++-- .../openapi/writer/visitors/VisitorFactory.kt | 17 +++++----- .../visitors/json/DecodeJsonArrayVisitor.kt | 15 ++++---- .../visitors/json/DecodeJsonObjectVisitor.kt | 21 ++++++------ .../visitors/json/EncodeJsonArrayVisitor.kt | 6 ++-- .../visitors/json/EncodeJsonObjectVisitor.kt | 23 ++++++++----- .../json/visitor/decode/JsonArrayTest.kt | 20 ++++++----- .../json/visitor/decode/JsonObjectTest.kt | 34 ++++++++++--------- .../json/visitor/encode/JsonArrayTest.kt | 20 ++++++----- .../json/visitor/encode/JsonObjectTest.kt | 34 ++++++++++--------- 14 files changed, 124 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index fbcb1c7..f1ec8ab 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,8 @@ Result of decode: ### Codec configs: -* checkUndefinedFields - Enable or Disable warnings for all undefined fields inside object structures, true by default. +* checkUndefinedFields - Enable or Disable warnings for all undefined fields inside object structures, (`true` by default). +* dateFormat - setup format of processed date (`yyyy-MM-dd Z` by default) **validationSettings (open api dictionary)** * enableRecommendations - Enable or Disable recommendations, `true` by default. @@ -178,6 +179,7 @@ May be empty due to missing required fields ### 0.3.0 + Feature: anyOf, allOf, oneOf support for objects and arrays ++ Feature: Date format support + Fix: Reworked visitor interface ### 0.2.0 diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index 91d5cba..40dfe9a 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -30,6 +30,7 @@ import com.exactpro.th2.codec.openapi.utils.getEndPoint import com.exactpro.th2.codec.openapi.utils.getMethods import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.VisitorFactory +import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import com.exactpro.th2.common.grpc.MessageGroup import com.exactpro.th2.common.grpc.RawMessage import com.exactpro.th2.common.message.plusAssign @@ -51,12 +52,14 @@ import io.swagger.v3.oas.models.PathItem import io.swagger.v3.oas.models.parameters.Parameter import mu.KotlinLogging import java.lang.IllegalStateException +import java.text.SimpleDateFormat class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSettings) : IPipelineCodec { private val typeToSchema: Map private val patternToPathItem: List> private val schemaWriter = SchemaWriter(dictionary) + private val visitorSettings = VisitorSettings(openAPI = dictionary, SimpleDateFormat(settings.dateFormat)) init { val mapForName = mutableMapOf() @@ -154,7 +157,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSe LOGGER.debug { "Start of message encoding: ${message.messageType}" } val visitor = try { - VisitorFactory.createEncodeVisitor(container.bodyFormat!!, messageSchema, message, dictionary) + VisitorFactory.createEncodeVisitor(container.bodyFormat!!, messageSchema, message, visitorSettings) } catch (e: Exception) { throw IllegalStateException("Cannot create encode visitor for message: ${message.messageType} - ${container.body}", e) } @@ -208,7 +211,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSe val format = checkNotNull(container.bodyFormat) { "Container: $messageType did not contain schema bodyFormat" } val visitor = try { - VisitorFactory.createDecodeVisitor(format, schema, body, dictionary) + VisitorFactory.createDecodeVisitor(format, schema, body, visitorSettings) } catch (e: Exception) { throw IllegalStateException("Cannot create decode visitor for message: ${header.messageType} - ${container.body}", e) } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt index 44a0a75..e3ecec7 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt @@ -30,4 +30,5 @@ class OpenApiCodecSettings : IPipelineCodecSettings { isResolveCombinators = false } val checkUndefinedFields: Boolean = true + val dateFormat: String = "yyyy-MM-dd Z" } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 5dd1c6d..057fe78 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -70,12 +70,13 @@ class SchemaWriter constructor(private val openApi: OpenAPI) { msgStructure.properties.forEach { (name, property) -> when(property) { is ArraySchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is StringSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is StringSchema -> visitor.visit(name, property , msgStructure.requiredContains(name)) is IntegerSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is NumberSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is BooleanSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is DateSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is ComposedSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${property::class.simpleName} isn't supported for now") + is PasswordSchema, is EmailSchema, is BinarySchema, is ByteArraySchema, is DateTimeSchema, is FileSchema, is MapSchema, is UUIDSchema -> throw UnsupportedOperationException("${property::class.simpleName} isn't supported for now") else -> visitor.visit(name, property, msgStructure.requiredContains(name), throwUndefined) } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt index 5a698b8..700b192 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt @@ -23,17 +23,18 @@ import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema import io.swagger.v3.oas.models.media.BooleanSchema import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.DateSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema -import java.lang.IllegalStateException +import java.text.SimpleDateFormat sealed class SchemaVisitor { - abstract val openAPI: OpenAPI + protected abstract val settings: VisitorSettings abstract val from: FromType abstract fun getResult(): ToType - abstract fun getFieldNames(): Collection + protected abstract fun getFieldNames(): Collection abstract fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean = true) abstract fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean = true) abstract fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) @@ -41,6 +42,7 @@ sealed class SchemaVisitor { abstract fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) fun oneOf(list: List>): Schema<*> = chooseOneOf(list.filter(this::checkAgainst)) @@ -82,3 +84,6 @@ sealed class SchemaVisitor { abstract class DecodeVisitor : SchemaVisitor() } + +data class VisitorSettings(val openAPI: OpenAPI, val dateFormat: SimpleDateFormat) + diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt index 2f85bd3..34daaed 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/VisitorFactory.kt @@ -32,25 +32,24 @@ import io.swagger.v3.oas.models.media.Schema object VisitorFactory { private const val JSON_FORMAT = "application/json" - - fun createEncodeVisitor(format: String, schema: Schema<*>, message: Message, dictionary: OpenAPI): SchemaVisitor.EncodeVisitor<*> { + fun createEncodeVisitor(format: String, schema: Schema<*>, message: Message, visitorSettings: VisitorSettings): SchemaVisitor.EncodeVisitor<*> { when (format) { JSON_FORMAT -> { - return when (schema.defineType(dictionary)) { - JsonSchemaTypes.ARRAY -> EncodeJsonArrayVisitor(message, dictionary) - JsonSchemaTypes.OBJECT -> EncodeJsonObjectVisitor(message, dictionary) + return when (schema.defineType(visitorSettings.openAPI)) { + JsonSchemaTypes.ARRAY -> EncodeJsonArrayVisitor(message, visitorSettings) + JsonSchemaTypes.OBJECT -> EncodeJsonObjectVisitor(message, visitorSettings) } } else -> error("Unsupported format of message $format for encode") } } - fun createDecodeVisitor(format: String, schema: Schema<*>, data: ByteString, dictionary: OpenAPI): SchemaVisitor.DecodeVisitor<*> { + fun createDecodeVisitor(format: String, schema: Schema<*>, data: ByteString, visitorSettings: VisitorSettings): SchemaVisitor.DecodeVisitor<*> { when (format) { JSON_FORMAT -> { - return when (schema.defineType(dictionary)) { - JsonSchemaTypes.ARRAY -> DecodeJsonArrayVisitor(data.toStringUtf8(), dictionary) - JsonSchemaTypes.OBJECT -> DecodeJsonObjectVisitor(data.toStringUtf8(), dictionary) + return when (schema.defineType(visitorSettings.openAPI)) { + JsonSchemaTypes.ARRAY -> DecodeJsonArrayVisitor(data.toStringUtf8(), visitorSettings) + JsonSchemaTypes.OBJECT -> DecodeJsonObjectVisitor(data.toStringUtf8(), visitorSettings) } } else -> error("Unsupported format of message $format for decode") diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt index d8f5249..c06a708 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt @@ -23,6 +23,7 @@ import com.exactpro.th2.codec.openapi.utils.validateAsLong import com.exactpro.th2.codec.openapi.utils.validateAsObject import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor +import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import com.exactpro.th2.common.grpc.Message import com.exactpro.th2.common.message.addField import com.exactpro.th2.common.message.message @@ -30,7 +31,6 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.JsonNodeFactory -import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema import io.swagger.v3.oas.models.media.BinarySchema import io.swagger.v3.oas.models.media.BooleanSchema @@ -48,15 +48,15 @@ import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import io.swagger.v3.oas.models.media.UUIDSchema -class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: OpenAPI) : SchemaVisitor.DecodeVisitor() { +class DecodeJsonArrayVisitor(override val from: JsonNode, override val settings: VisitorSettings) : SchemaVisitor.DecodeVisitor() { - constructor(jsonString: String, openAPI: OpenAPI) : this(mapper.readTree(jsonString), openAPI) + constructor(jsonString: String, settings: VisitorSettings) : this(mapper.readTree(jsonString), settings) internal val rootMessage = message() private val fromArray = from as ArrayNode override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean) { - when (val itemSchema = openAPI.getEndPoint(fldStruct.items)) { + when (val itemSchema = settings.openAPI.getEndPoint(fldStruct.items)) { is NumberSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsBigDecimal() }) is IntegerSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsLong() }) is BooleanSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsBoolean() }) @@ -64,8 +64,8 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") else -> rootMessage.addField(fieldName, mutableListOf().apply { fromArray.forEach { - DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> - SchemaWriter(openAPI).traverse(visitor, itemSchema, throwUndefined) + DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, settings).let { visitor -> + SchemaWriter(settings.openAPI).traverse(visitor, itemSchema, throwUndefined) visitor.rootMessage.build().run(this::add) } } @@ -79,6 +79,7 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun getFieldNames() = throw UnsupportedOperationException("Array visitor supports only collections") override fun getResult(): Message.Builder = rootMessage @@ -90,4 +91,6 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val openAPI: } } + + } \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 83b5c3f..f7d53b9 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -26,6 +26,7 @@ import com.exactpro.th2.codec.openapi.utils.validateAsLong import com.exactpro.th2.codec.openapi.utils.validateAsObject import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor +import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import com.exactpro.th2.common.grpc.Message import com.exactpro.th2.common.message.addField import com.exactpro.th2.common.message.addFields @@ -34,7 +35,6 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.fasterxml.jackson.databind.node.ObjectNode -import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema import io.swagger.v3.oas.models.media.BinarySchema import io.swagger.v3.oas.models.media.BooleanSchema @@ -52,24 +52,24 @@ import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import io.swagger.v3.oas.models.media.UUIDSchema -open class DecodeJsonObjectVisitor(override val from: JsonNode, override val openAPI: OpenAPI) : SchemaVisitor.DecodeVisitor() { +open class DecodeJsonObjectVisitor(override val from: JsonNode, override val settings: VisitorSettings) : SchemaVisitor.DecodeVisitor() { - constructor(jsonString: String, openAPI: OpenAPI) : this(mapper.readTree(jsonString), openAPI) + constructor(jsonString: String, settings: VisitorSettings) : this(mapper.readTree(jsonString), settings) internal val rootMessage = message() private val fromObject = from as ObjectNode override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) { fromObject.getField(fieldName, required)?.let { message -> - val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), openAPI) - val writer = SchemaWriter(openAPI) + val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), settings) + val writer = SchemaWriter(settings.openAPI) writer.traverse(visitor, fldStruct, throwUndefined) rootMessage.addField(fieldName, visitor.rootMessage) } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } } override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean) { - val itemSchema = openAPI.getEndPoint(fldStruct.items) + val itemSchema = settings.openAPI.getEndPoint(fldStruct.items) fromObject.getRequiredArray(fieldName, required)?.let { arrayNode -> when (itemSchema) { @@ -80,8 +80,8 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") else -> rootMessage.addField(fieldName, mutableListOf().apply { arrayNode.forEach { - DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> - SchemaWriter(openAPI).traverse(visitor, itemSchema, throwUndefined) + DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, settings).let { visitor -> + SchemaWriter(settings.openAPI).traverse(visitor, itemSchema, throwUndefined) visitor.rootMessage.build().run(this::add) } } @@ -92,8 +92,8 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) { fromObject.getField(fieldName, required)?.let { message -> - val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), openAPI) - val writer = SchemaWriter(openAPI) + val visitor = DecodeJsonObjectVisitor(message.validateAsObject(), settings) + val writer = SchemaWriter(settings.openAPI) writer.traverse(visitor, fldStruct, false) rootMessage.addField(fieldName, visitor.rootMessage) } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } @@ -103,6 +103,7 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val ope override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsLong(), fldStruct) override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.asText(), fldStruct) override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsBoolean(), fldStruct) + override fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) = visitPrimitive(fieldName, settings.dateFormat.parse(fromObject.getField(fieldName, required)?.asText()), fldStruct) private fun visitPrimitive(fieldName: String, value: T?, fldStruct: Schema) { (value?.also { fldStruct.checkEnum(it, fieldName) } ?: fldStruct.default)?.let { diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt index 1f57e9c..6454e9c 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt @@ -16,23 +16,25 @@ package com.exactpro.th2.codec.openapi.writer.visitors.json +import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import com.exactpro.th2.common.grpc.Message import com.google.protobuf.ByteString -import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.BooleanSchema import io.swagger.v3.oas.models.media.ComposedSchema +import io.swagger.v3.oas.models.media.DateSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema -class EncodeJsonArrayVisitor(from: Message, openAPI: OpenAPI) : EncodeJsonObjectVisitor(from, openAPI) { +class EncodeJsonArrayVisitor(from: Message, visitorSettings: VisitorSettings) : EncodeJsonObjectVisitor(from, visitorSettings) { override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun getFieldNames() = throw UnsupportedOperationException("Array visitor supports only collections") override fun getResult(): ByteString = ByteString.copyFrom(rootNode.first().toString().toByteArray()) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 9da6e1f..aeaa505 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -23,6 +23,7 @@ import com.exactpro.th2.codec.openapi.utils.getField import com.exactpro.th2.codec.openapi.utils.putAll import com.exactpro.th2.codec.openapi.writer.SchemaWriter import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor +import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import com.exactpro.th2.common.grpc.Message import com.exactpro.th2.common.grpc.Value import com.exactpro.th2.common.value.getBigDecimal @@ -34,7 +35,6 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.fasterxml.jackson.databind.node.ObjectNode import com.google.protobuf.ByteString -import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema import io.swagger.v3.oas.models.media.BinarySchema import io.swagger.v3.oas.models.media.BooleanSchema @@ -53,7 +53,7 @@ import io.swagger.v3.oas.models.media.StringSchema import io.swagger.v3.oas.models.media.UUIDSchema import java.math.BigDecimal -open class EncodeJsonObjectVisitor(override val from: Message, override val openAPI: OpenAPI) : SchemaVisitor.EncodeVisitor() { +open class EncodeJsonObjectVisitor(override val from: Message, override val settings: VisitorSettings) : SchemaVisitor.EncodeVisitor() { internal val rootNode: ObjectNode = mapper.createObjectNode() override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) { @@ -66,15 +66,15 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open !field.hasMessageValue() -> error("$fieldName is not an message: ${field.kindCase}") } val message = field.getMessage()!! - val visitor = EncodeJsonObjectVisitor(message, openAPI) - val writer = SchemaWriter(openAPI) + val visitor = EncodeJsonObjectVisitor(message, settings) + val writer = SchemaWriter(settings.openAPI) writer.traverse(visitor, fldStruct, throwUndefined) rootNode.set(fieldName, visitor.rootNode) } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } } override fun visit(fieldName: String, fldStruct: ArraySchema, required: Boolean, throwUndefined: Boolean) { - val itemSchema = openAPI.getEndPoint(fldStruct.items) + val itemSchema = settings.openAPI.getEndPoint(fldStruct.items) from.getField(fieldName, required)?.let { field -> when { @@ -94,9 +94,9 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") else -> rootNode.putArray(fieldName).apply { val listOfNodes = mutableListOf() - val writer = SchemaWriter(openAPI) + val writer = SchemaWriter(settings.openAPI) listOfValues.forEach { - EncodeJsonObjectVisitor(checkNotNull(it.getMessage()) { " Value from list [$fieldName] must be message" }, openAPI).let { visitor -> + EncodeJsonObjectVisitor(checkNotNull(it.getMessage()) { " Value from list [$fieldName] must be message" }, settings).let { visitor -> writer.traverse(visitor, itemSchema, throwUndefined) visitor.rootNode.run(listOfNodes::add) } @@ -115,8 +115,8 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open } val message = field.getMessage()!! - val visitor = EncodeJsonObjectVisitor(message, openAPI) - val writer = SchemaWriter(openAPI) + val visitor = EncodeJsonObjectVisitor(message, settings) + val writer = SchemaWriter(settings.openAPI) writer.traverse(visitor, fldStruct, false) rootNode.set(fieldName, visitor.rootNode) } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } @@ -138,6 +138,11 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val open rootNode.put(fieldName, it) } + override fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, { value -> settings.dateFormat.parse(value.simpleValue) }) { + rootNode.put(fieldName, it.toString()) + } + + override fun getFieldNames(): Collection = from.fieldsMap.keys override fun getResult(): ByteString = ByteString.copyFrom(rootNode.toString().toByteArray()) diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt index 851081c..da1ecc6 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt @@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ObjectNode import com.exactpro.th2.codec.openapi.utils.createArrayTestSchema import com.exactpro.th2.codec.openapi.utils.getResourceAsText +import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema @@ -39,6 +40,7 @@ import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test +import java.text.SimpleDateFormat class JsonArrayTest { @@ -46,7 +48,7 @@ class JsonArrayTest { @Test fun `not supported decode`() { val node = mapper.createArrayNode() - val visitor = DecodeJsonArrayVisitor(node, openAPI) + val visitor = DecodeJsonArrayVisitor(node, VisitorSettings(openAPI, SimpleDateFormat())) Assertions.assertThrows(UnsupportedOperationException::class.java) { visitor.visit("", StringSchema(), true) } @@ -112,7 +114,7 @@ class JsonArrayTest { }) } - val result = DecodeJsonArrayVisitor(jsonArrayNode, openAPI).apply { + val result = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult() @@ -139,7 +141,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) visitor.visit(fieldName, createArrayTestSchema("string"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -151,7 +153,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) visitor.visit(fieldName, createArrayTestSchema("boolean"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -163,7 +165,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) visitor.visit(fieldName, createArrayTestSchema("integer"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -175,7 +177,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) visitor.visit(fieldName, createArrayTestSchema("number", "double"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -187,7 +189,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) visitor.visit(fieldName, createArrayTestSchema("number", "float"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -199,7 +201,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) visitor.visit(fieldName, createArrayTestSchema("integer", "int64"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -211,7 +213,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, openAPI) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) visitor.visit(fieldName, createArrayTestSchema("integer", "int64"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt index d7997cf..d9fc47a 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt @@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode import com.exactpro.th2.codec.openapi.utils.createArrayTestSchema import com.exactpro.th2.codec.openapi.utils.createTestSchema import com.exactpro.th2.codec.openapi.utils.getResourceAsText +import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema @@ -40,6 +41,7 @@ import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Test +import java.text.SimpleDateFormat @Suppress("CAST_NEVER_SUCCEEDS") class JsonObjectTest { @@ -75,7 +77,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { this.set(fieldName, objectValue) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, openAPI.components.schemas["ObjectTest"]!! as ObjectSchema, true) }.getResult() result[fieldName]!!.messageValue.let { bigMessage -> @@ -95,7 +97,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createTestSchema(simpleValue) as StringSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue) @@ -108,7 +110,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createTestSchema(simpleValue) as BooleanSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) @@ -121,7 +123,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createTestSchema(simpleValue) as IntegerSchema, true) }.getResult() result.build().assertInt(fieldName, simpleValue) @@ -134,7 +136,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createTestSchema(simpleValue) as NumberSchema, true) }.getResult() result.build().assertDouble(fieldName, simpleValue) @@ -147,7 +149,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createTestSchema(simpleValue) as NumberSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) @@ -160,7 +162,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createTestSchema(simpleValue) as IntegerSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) @@ -173,7 +175,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createTestSchema(simpleValue) as IntegerSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) @@ -187,7 +189,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -201,7 +203,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -215,7 +217,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createArrayTestSchema("integer"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -229,7 +231,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -243,7 +245,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -257,7 +259,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -271,7 +273,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -325,7 +327,7 @@ class JsonObjectTest { this.set(fieldName, jsonArrayNode) } - val result = DecodeJsonObjectVisitor(json, openAPI).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult() diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt index 90d46b8..58a6dde 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt @@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ArrayNode import com.exactpro.th2.codec.openapi.utils.createArrayTestSchema import com.exactpro.th2.codec.openapi.utils.getResourceAsText +import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema @@ -37,6 +38,7 @@ import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.math.BigDecimal +import java.text.SimpleDateFormat class JsonArrayTest { @@ -44,7 +46,7 @@ class JsonArrayTest { @Test fun `not supported encode`() { val message = message().build() - val visitor = EncodeJsonArrayVisitor(message, openAPI) + val visitor = EncodeJsonArrayVisitor(message, VisitorSettings(openAPI, SimpleDateFormat())) Assertions.assertThrows(UnsupportedOperationException::class.java) { visitor.visit("", StringSchema(), true) } @@ -109,7 +111,7 @@ class JsonArrayTest { val message = message().addField(fieldName, listValue).build() - val result = EncodeJsonArrayVisitor(message, openAPI).apply { + val result = EncodeJsonArrayVisitor(message, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult() @@ -132,7 +134,7 @@ class JsonArrayTest { fun `string array test encode`() { val fieldName = "stringField" val collection = listOf("stringValue1", "stringValue2", "stringValue3", "stringValue4") - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("string") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -145,7 +147,7 @@ class JsonArrayTest { fun `boolean array test encode`() { val fieldName = "booleanField" val collection = listOf(true, false, false, true) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("boolean") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -158,7 +160,7 @@ class JsonArrayTest { fun `int array test encode`() { val fieldName = "intField" val collection = listOf(1, 2, 2, 4) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("integer") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -171,7 +173,7 @@ class JsonArrayTest { fun `float array test encode`() { val fieldName = "floatField" val collection = listOf(0.1f, 0.2f, 0.2f, 0.4f) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("number","float") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -184,7 +186,7 @@ class JsonArrayTest { fun `double array test encode`() { val fieldName = "doubleField" val collection = listOf(0.1, 0.2, 0.2, 0.4) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("number", "double") visitor.visit(fieldName , schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -197,7 +199,7 @@ class JsonArrayTest { fun `long array test encode`() { val fieldName = "longField" val collection = listOf(111111111L, 222222222L, 222222222L, 444444444L) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("integer", "int64") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -210,7 +212,7 @@ class JsonArrayTest { fun `big decimal array test encode`() { val fieldName = "decimalField" val collection = listOf(BigDecimal(100044000000), BigDecimal(100000030000), BigDecimal(100000001000), BigDecimal(100000022000)) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("number", "-") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt index 85be792..4b421f5 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt @@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode import com.exactpro.th2.codec.openapi.utils.createArrayTestSchema import com.exactpro.th2.codec.openapi.utils.createTestSchema import com.exactpro.th2.codec.openapi.utils.getResourceAsText +import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import io.swagger.parser.OpenAPIParser import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.media.ArraySchema @@ -42,6 +43,7 @@ import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.math.BigDecimal +import java.text.SimpleDateFormat @Suppress("CAST_NEVER_SUCCEEDS") class JsonObjectTest { @@ -73,7 +75,7 @@ class JsonObjectTest { addField(includedObject, includedObjectValue) }).build() - val result = EncodeJsonObjectVisitor(message, openAPI).apply { + val result = EncodeJsonObjectVisitor(message, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, openAPI.components.schemas["ObjectTest"]!! as ObjectSchema, true) }.getResult().toStringUtf8() @@ -91,7 +93,7 @@ class JsonObjectTest { fun `string test encode`() { val fieldName = "stringField" val simpleValue = "stringValue" - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as StringSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asText() @@ -102,7 +104,7 @@ class JsonObjectTest { fun `boolean test encode`() { val fieldName = "booleanField" val simpleValue = true - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as BooleanSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asBoolean() @@ -113,7 +115,7 @@ class JsonObjectTest { fun `int test encode`() { val fieldName = "intField" val simpleValue = 123 - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as IntegerSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asInt() @@ -124,7 +126,7 @@ class JsonObjectTest { fun `float test encode`() { val fieldName = "floatField" val simpleValue = 123.1f - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as NumberSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asText()?.toFloat() @@ -135,7 +137,7 @@ class JsonObjectTest { fun `double test encode`() { val fieldName = "doubleField" val simpleValue = 123.1 - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as NumberSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asDouble() @@ -146,7 +148,7 @@ class JsonObjectTest { fun `long test encode`() { val fieldName = "longField" val simpleValue = 123123L - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as IntegerSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asLong() @@ -157,7 +159,7 @@ class JsonObjectTest { fun `big decimal test encode`() { val fieldName = "decimalField" val simpleValue = BigDecimal(100000000000) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as NumberSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asText()?.toBigDecimal() @@ -168,7 +170,7 @@ class JsonObjectTest { fun `string array test encode`() { val fieldName = "stringField" val collection = listOf("stringValue1", "stringValue2", "stringValue3", "stringValue4") - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("string") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -182,7 +184,7 @@ class JsonObjectTest { fun `boolean array test encode`() { val fieldName = "booleanField" val collection = listOf(true, false, false, true) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("boolean") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -196,7 +198,7 @@ class JsonObjectTest { fun `int array test encode`() { val fieldName = "intField" val collection = listOf(1, 2, 2, 4) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("integer") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -210,7 +212,7 @@ class JsonObjectTest { fun `float array test encode`() { val fieldName = "floatField" val collection = listOf(0.1f, 0.2f, 0.2f, 0.4f) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("number","float") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -224,7 +226,7 @@ class JsonObjectTest { fun `double array test encode`() { val fieldName = "doubleField" val collection = listOf(0.1, 0.2, 0.2, 0.4) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("number", "double") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -238,7 +240,7 @@ class JsonObjectTest { fun `long array test encode`() { val fieldName = "longField" val collection = listOf(111111111L, 222222222L, 222222222L, 444444444L) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("integer", "int64") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -252,7 +254,7 @@ class JsonObjectTest { fun `big decimal array test encode`() { val fieldName = "decimalField" val collection = listOf(BigDecimal(100000000010), BigDecimal(100000002000), BigDecimal(100300000000), BigDecimal(100004000000)) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), openAPI) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) val schema = createArrayTestSchema("number", "-") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -297,7 +299,7 @@ class JsonObjectTest { val message = message().addField(fieldName, listValue).build() - val result = EncodeJsonObjectVisitor(message, openAPI).apply { + val result = EncodeJsonObjectVisitor(message, VisitorSettings(openAPI, SimpleDateFormat())).apply { visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult().toStringUtf8() From 1c711da09e98cd1908e7ed4dca31f2b4a8d96501 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Wed, 25 May 2022 00:19:52 +0400 Subject: [PATCH 27/34] null check for json date field --- .../exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt | 1 - .../openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt index 700b192..b211e21 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt @@ -81,7 +81,6 @@ sealed class SchemaVisitor { } abstract class EncodeVisitor : SchemaVisitor() - abstract class DecodeVisitor : SchemaVisitor() } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index f7d53b9..0f17e5d 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -103,7 +103,7 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val set override fun visit(fieldName: String, fldStruct: IntegerSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsLong(), fldStruct) override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.asText(), fldStruct) override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsBoolean(), fldStruct) - override fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) = visitPrimitive(fieldName, settings.dateFormat.parse(fromObject.getField(fieldName, required)?.asText()), fldStruct) + override fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.asText()?.let(settings.dateFormat::parse), fldStruct) private fun visitPrimitive(fieldName: String, value: T?, fldStruct: Schema) { (value?.also { fldStruct.checkEnum(it, fieldName) } ?: fldStruct.default)?.let { From d5d94d1472996baecba32d355027d9c8d9ef8ec0 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Wed, 25 May 2022 12:35:11 +0400 Subject: [PATCH 28/34] date time format support --- README.md | 3 ++ .../th2/codec/openapi/OpenApiCodec.kt | 3 +- .../th2/codec/openapi/OpenApiCodecSettings.kt | 1 + .../th2/codec/openapi/writer/SchemaWriter.kt | 3 +- .../openapi/writer/visitors/SchemaVisitor.kt | 5 ++- .../visitors/json/DecodeJsonArrayVisitor.kt | 5 ++- .../visitors/json/DecodeJsonObjectVisitor.kt | 6 +++- .../visitors/json/EncodeJsonArrayVisitor.kt | 2 ++ .../visitors/json/EncodeJsonObjectVisitor.kt | 14 +++++++- .../json/visitor/decode/JsonArrayTest.kt | 19 ++++++----- .../json/visitor/decode/JsonObjectTest.kt | 33 ++++++++++--------- .../json/visitor/encode/JsonArrayTest.kt | 19 ++++++----- .../json/visitor/encode/JsonObjectTest.kt | 33 ++++++++++--------- 13 files changed, 90 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index f1ec8ab..26a40df 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,8 @@ Result of decode: * checkUndefinedFields - Enable or Disable warnings for all undefined fields inside object structures, (`true` by default). * dateFormat - setup format of processed date (`yyyy-MM-dd Z` by default) +* dateTimeFormat - setup format of processed date-time (`yyyy/MM/dd HH:mm:ss` by default) + **validationSettings (open api dictionary)** * enableRecommendations - Enable or Disable recommendations, `true` by default. @@ -180,6 +182,7 @@ May be empty due to missing required fields + Feature: anyOf, allOf, oneOf support for objects and arrays + Feature: Date format support ++ Feature: DateTime format support + Fix: Reworked visitor interface ### 0.2.0 diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index 40dfe9a..099fc58 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -53,13 +53,14 @@ import io.swagger.v3.oas.models.parameters.Parameter import mu.KotlinLogging import java.lang.IllegalStateException import java.text.SimpleDateFormat +import java.time.format.DateTimeFormatter class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSettings) : IPipelineCodec { private val typeToSchema: Map private val patternToPathItem: List> private val schemaWriter = SchemaWriter(dictionary) - private val visitorSettings = VisitorSettings(openAPI = dictionary, SimpleDateFormat(settings.dateFormat)) + private val visitorSettings = VisitorSettings(dictionary, SimpleDateFormat(settings.dateFormat), DateTimeFormatter.ofPattern(settings.dateTimeFormat)) init { val mapForName = mutableMapOf() diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt index e3ecec7..4799966 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodecSettings.kt @@ -31,4 +31,5 @@ class OpenApiCodecSettings : IPipelineCodecSettings { } val checkUndefinedFields: Boolean = true val dateFormat: String = "yyyy-MM-dd Z" + val dateTimeFormat: String = "yyyy/MM/dd HH:mm:ss" } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 057fe78..8357b5c 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -75,8 +75,9 @@ class SchemaWriter constructor(private val openApi: OpenAPI) { is NumberSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is BooleanSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is DateSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is DateTimeSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is ComposedSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is PasswordSchema, is EmailSchema, is BinarySchema, is ByteArraySchema, is DateTimeSchema, is FileSchema, is MapSchema, is UUIDSchema -> throw UnsupportedOperationException("${property::class.simpleName} isn't supported for now") + is PasswordSchema, is EmailSchema, is BinarySchema, is ByteArraySchema, is FileSchema, is MapSchema, is UUIDSchema -> throw UnsupportedOperationException("${property::class.simpleName} isn't supported for now") else -> visitor.visit(name, property, msgStructure.requiredContains(name), throwUndefined) } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt index b211e21..7e6ba50 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt @@ -24,11 +24,13 @@ import io.swagger.v3.oas.models.media.ArraySchema import io.swagger.v3.oas.models.media.BooleanSchema import io.swagger.v3.oas.models.media.ComposedSchema import io.swagger.v3.oas.models.media.DateSchema +import io.swagger.v3.oas.models.media.DateTimeSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import java.text.SimpleDateFormat +import java.time.format.DateTimeFormatter sealed class SchemaVisitor { protected abstract val settings: VisitorSettings @@ -43,6 +45,7 @@ sealed class SchemaVisitor { abstract fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) abstract fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) + abstract fun visit(fieldName: String, fldStruct: DateTimeSchema, required: Boolean) fun oneOf(list: List>): Schema<*> = chooseOneOf(list.filter(this::checkAgainst)) @@ -84,5 +87,5 @@ sealed class SchemaVisitor { abstract class DecodeVisitor : SchemaVisitor() } -data class VisitorSettings(val openAPI: OpenAPI, val dateFormat: SimpleDateFormat) +data class VisitorSettings(val openAPI: OpenAPI, val dateFormat: SimpleDateFormat, val dateTimeFormat: DateTimeFormatter) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt index c06a708..a4815d0 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonArrayVisitor.kt @@ -61,7 +61,9 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val settings: is IntegerSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsLong() }) is BooleanSchema -> rootMessage.addField(fieldName, fromArray.map { it.validateAsBoolean() }) is StringSchema -> rootMessage.addField(fieldName, fromArray.map { it.asText() }) - is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") + is DateSchema -> rootMessage.addField(fieldName, fromArray.map { settings.dateFormat.parse(it.asText()) }) + is DateTimeSchema -> rootMessage.addField(fieldName, fromArray.map { settings.dateTimeFormat.parse(it.asText()) }) + is BinarySchema, is ByteArraySchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") else -> rootMessage.addField(fieldName, mutableListOf().apply { fromArray.forEach { DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, settings).let { visitor -> @@ -80,6 +82,7 @@ class DecodeJsonArrayVisitor(override val from: JsonNode, override val settings: override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: DateTimeSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun getFieldNames() = throw UnsupportedOperationException("Array visitor supports only collections") override fun getResult(): Message.Builder = rootMessage diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 0f17e5d..87eec62 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -51,6 +51,7 @@ import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import io.swagger.v3.oas.models.media.UUIDSchema +import java.time.OffsetDateTime open class DecodeJsonObjectVisitor(override val from: JsonNode, override val settings: VisitorSettings) : SchemaVisitor.DecodeVisitor() { @@ -77,7 +78,9 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val set is IntegerSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsLong() }) is BooleanSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBoolean() }) is StringSchema -> rootMessage.addField(fieldName, arrayNode.map { it.asText() }) - is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") + is DateSchema -> rootMessage.addField(fieldName, arrayNode.map { settings.dateFormat.parse(it.asText()).toString() }) + is DateTimeSchema -> rootMessage.addField(fieldName, arrayNode.map { settings.dateTimeFormat.parse(it.asText()).toString() }) + is BinarySchema, is ByteArraySchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") else -> rootMessage.addField(fieldName, mutableListOf().apply { arrayNode.forEach { DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, settings).let { visitor -> @@ -104,6 +107,7 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val set override fun visit(fieldName: String, fldStruct: StringSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.asText(), fldStruct) override fun visit(fieldName: String, fldStruct: BooleanSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsBoolean(), fldStruct) override fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.asText()?.let(settings.dateFormat::parse), fldStruct) + override fun visit(fieldName: String, fldStruct: DateTimeSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.asText()?.let { settings.dateTimeFormat.parse(it) as OffsetDateTime }, fldStruct) private fun visitPrimitive(fieldName: String, value: T?, fldStruct: Schema) { (value?.also { fldStruct.checkEnum(it, fieldName) } ?: fldStruct.default)?.let { diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt index 6454e9c..41ae667 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonArrayVisitor.kt @@ -22,6 +22,7 @@ import com.google.protobuf.ByteString import io.swagger.v3.oas.models.media.BooleanSchema import io.swagger.v3.oas.models.media.ComposedSchema import io.swagger.v3.oas.models.media.DateSchema +import io.swagger.v3.oas.models.media.DateTimeSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.NumberSchema import io.swagger.v3.oas.models.media.Schema @@ -35,6 +36,7 @@ class EncodeJsonArrayVisitor(from: Message, visitorSettings: VisitorSettings) : override fun visit(fieldName: String, fldStruct: ComposedSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun visit(fieldName: String, fldStruct: DateSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") + override fun visit(fieldName: String, fldStruct: DateTimeSchema, required: Boolean) = throw UnsupportedOperationException("Array visitor supports only collections") override fun getFieldNames() = throw UnsupportedOperationException("Array visitor supports only collections") override fun getResult(): ByteString = ByteString.copyFrom(rootNode.first().toString().toByteArray()) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index aeaa505..0164398 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -52,6 +52,7 @@ import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import io.swagger.v3.oas.models.media.UUIDSchema import java.math.BigDecimal +import java.time.OffsetDateTime open class EncodeJsonObjectVisitor(override val from: Message, override val settings: VisitorSettings) : SchemaVisitor.EncodeVisitor() { internal val rootNode: ObjectNode = mapper.createObjectNode() @@ -91,7 +92,13 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val sett is IntegerSchema -> rootNode.putArray(fieldName).putAll(listOfValues) is BooleanSchema -> rootNode.putArray(fieldName).putAll(listOfValues) is StringSchema -> rootNode.putArray(fieldName).putAll(listOfValues) - is BinarySchema, is ByteArraySchema, is DateSchema, is DateTimeSchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") + is DateSchema -> rootNode.putArray(fieldName).run { + listOfValues.forEach { add(settings.dateFormat.parse(it.simpleValue).toString()) } + } + is DateTimeSchema -> rootNode.putArray(fieldName).run { + listOfValues.forEach { add(settings.dateTimeFormat.parse(it.simpleValue).toString()) } + } + is BinarySchema, is ByteArraySchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") else -> rootNode.putArray(fieldName).apply { val listOfNodes = mutableListOf() val writer = SchemaWriter(settings.openAPI) @@ -142,6 +149,11 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val sett rootNode.put(fieldName, it.toString()) } + override fun visit(fieldName: String, fldStruct: DateTimeSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, { value -> settings.dateTimeFormat.parse(value.simpleValue) as OffsetDateTime }) { + rootNode.put(fieldName, it.toString()) + } + + override fun getFieldNames(): Collection = from.fieldsMap.keys override fun getResult(): ByteString = ByteString.copyFrom(rootNode.toString().toByteArray()) diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt index da1ecc6..b6f2410 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonArrayTest.kt @@ -41,6 +41,7 @@ import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.text.SimpleDateFormat +import java.time.format.DateTimeFormatter class JsonArrayTest { @@ -48,7 +49,7 @@ class JsonArrayTest { @Test fun `not supported decode`() { val node = mapper.createArrayNode() - val visitor = DecodeJsonArrayVisitor(node, VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = DecodeJsonArrayVisitor(node, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) Assertions.assertThrows(UnsupportedOperationException::class.java) { visitor.visit("", StringSchema(), true) } @@ -114,7 +115,7 @@ class JsonArrayTest { }) } - val result = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult() @@ -141,7 +142,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) visitor.visit(fieldName, createArrayTestSchema("string"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -153,7 +154,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) visitor.visit(fieldName, createArrayTestSchema("boolean"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -165,7 +166,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) visitor.visit(fieldName, createArrayTestSchema("integer"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -177,7 +178,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) visitor.visit(fieldName, createArrayTestSchema("number", "double"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -189,7 +190,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) visitor.visit(fieldName, createArrayTestSchema("number", "float"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -201,7 +202,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) visitor.visit(fieldName, createArrayTestSchema("integer", "int64"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } @@ -213,7 +214,7 @@ class JsonArrayTest { val jsonArrayNode = mapper.createArrayNode().apply { collection.forEach(this::add) } - val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = DecodeJsonArrayVisitor(jsonArrayNode, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) visitor.visit(fieldName, createArrayTestSchema("integer", "int64"), true) visitor.getResult().build().assertList(fieldName, collection.map {it.toValue()}) } diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt index d9fc47a..d6923e8 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/decode/JsonObjectTest.kt @@ -42,6 +42,7 @@ import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.StringSchema import org.junit.jupiter.api.Test import java.text.SimpleDateFormat +import java.time.format.DateTimeFormatter @Suppress("CAST_NEVER_SUCCEEDS") class JsonObjectTest { @@ -77,7 +78,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { this.set(fieldName, objectValue) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, openAPI.components.schemas["ObjectTest"]!! as ObjectSchema, true) }.getResult() result[fieldName]!!.messageValue.let { bigMessage -> @@ -97,7 +98,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createTestSchema(simpleValue) as StringSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue) @@ -110,7 +111,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createTestSchema(simpleValue) as BooleanSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) @@ -123,7 +124,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createTestSchema(simpleValue) as IntegerSchema, true) }.getResult() result.build().assertInt(fieldName, simpleValue) @@ -136,7 +137,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createTestSchema(simpleValue) as NumberSchema, true) }.getResult() result.build().assertDouble(fieldName, simpleValue) @@ -149,7 +150,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createTestSchema(simpleValue) as NumberSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) @@ -162,7 +163,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createTestSchema(simpleValue) as IntegerSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) @@ -175,7 +176,7 @@ class JsonObjectTest { val json = mapper.createObjectNode().apply { put(fieldName, simpleValue) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createTestSchema(simpleValue) as IntegerSchema, true) }.getResult() result.build().assertString(fieldName, simpleValue.toString()) @@ -189,7 +190,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -203,7 +204,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -217,7 +218,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createArrayTestSchema("integer"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -231,7 +232,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -245,7 +246,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -259,7 +260,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -273,7 +274,7 @@ class JsonObjectTest { val arrayNode = putArray(fieldName) collection.forEach(arrayNode::add) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, createArrayTestSchema("string"), true) }.getResult() result.build().assertList(fieldName, collection.map { it.toValue() }) @@ -327,7 +328,7 @@ class JsonObjectTest { this.set(fieldName, jsonArrayNode) } - val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = DecodeJsonObjectVisitor(json, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult() diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt index 58a6dde..a19bf3b 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonArrayTest.kt @@ -39,6 +39,7 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.math.BigDecimal import java.text.SimpleDateFormat +import java.time.format.DateTimeFormatter class JsonArrayTest { @@ -46,7 +47,7 @@ class JsonArrayTest { @Test fun `not supported encode`() { val message = message().build() - val visitor = EncodeJsonArrayVisitor(message, VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonArrayVisitor(message, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) Assertions.assertThrows(UnsupportedOperationException::class.java) { visitor.visit("", StringSchema(), true) } @@ -111,7 +112,7 @@ class JsonArrayTest { val message = message().addField(fieldName, listValue).build() - val result = EncodeJsonArrayVisitor(message, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = EncodeJsonArrayVisitor(message, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult() @@ -134,7 +135,7 @@ class JsonArrayTest { fun `string array test encode`() { val fieldName = "stringField" val collection = listOf("stringValue1", "stringValue2", "stringValue3", "stringValue4") - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("string") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -147,7 +148,7 @@ class JsonArrayTest { fun `boolean array test encode`() { val fieldName = "booleanField" val collection = listOf(true, false, false, true) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("boolean") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -160,7 +161,7 @@ class JsonArrayTest { fun `int array test encode`() { val fieldName = "intField" val collection = listOf(1, 2, 2, 4) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("integer") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -173,7 +174,7 @@ class JsonArrayTest { fun `float array test encode`() { val fieldName = "floatField" val collection = listOf(0.1f, 0.2f, 0.2f, 0.4f) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("number","float") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -186,7 +187,7 @@ class JsonArrayTest { fun `double array test encode`() { val fieldName = "doubleField" val collection = listOf(0.1, 0.2, 0.2, 0.4) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("number", "double") visitor.visit(fieldName , schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -199,7 +200,7 @@ class JsonArrayTest { fun `long array test encode`() { val fieldName = "longField" val collection = listOf(111111111L, 222222222L, 222222222L, 444444444L) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("integer", "int64") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) @@ -212,7 +213,7 @@ class JsonArrayTest { fun `big decimal array test encode`() { val fieldName = "decimalField" val collection = listOf(BigDecimal(100044000000), BigDecimal(100000030000), BigDecimal(100000001000), BigDecimal(100000022000)) - val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonArrayVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("number", "-") visitor.visit(fieldName, schema, true) val result = (mapper.readTree(visitor.getResult().toStringUtf8()) as ArrayNode) diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt index 4b421f5..c7a5637 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/json/visitor/encode/JsonObjectTest.kt @@ -44,6 +44,7 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.math.BigDecimal import java.text.SimpleDateFormat +import java.time.format.DateTimeFormatter @Suppress("CAST_NEVER_SUCCEEDS") class JsonObjectTest { @@ -75,7 +76,7 @@ class JsonObjectTest { addField(includedObject, includedObjectValue) }).build() - val result = EncodeJsonObjectVisitor(message, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = EncodeJsonObjectVisitor(message, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, openAPI.components.schemas["ObjectTest"]!! as ObjectSchema, true) }.getResult().toStringUtf8() @@ -93,7 +94,7 @@ class JsonObjectTest { fun `string test encode`() { val fieldName = "stringField" val simpleValue = "stringValue" - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as StringSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asText() @@ -104,7 +105,7 @@ class JsonObjectTest { fun `boolean test encode`() { val fieldName = "booleanField" val simpleValue = true - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as BooleanSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asBoolean() @@ -115,7 +116,7 @@ class JsonObjectTest { fun `int test encode`() { val fieldName = "intField" val simpleValue = 123 - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as IntegerSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asInt() @@ -126,7 +127,7 @@ class JsonObjectTest { fun `float test encode`() { val fieldName = "floatField" val simpleValue = 123.1f - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as NumberSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asText()?.toFloat() @@ -137,7 +138,7 @@ class JsonObjectTest { fun `double test encode`() { val fieldName = "doubleField" val simpleValue = 123.1 - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as NumberSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asDouble() @@ -148,7 +149,7 @@ class JsonObjectTest { fun `long test encode`() { val fieldName = "longField" val simpleValue = 123123L - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as IntegerSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asLong() @@ -159,7 +160,7 @@ class JsonObjectTest { fun `big decimal test encode`() { val fieldName = "decimalField" val simpleValue = BigDecimal(100000000000) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, simpleValue).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createTestSchema(simpleValue) visitor.visit(fieldName, schema as NumberSchema, true) val result = mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName)?.asText()?.toBigDecimal() @@ -170,7 +171,7 @@ class JsonObjectTest { fun `string array test encode`() { val fieldName = "stringField" val collection = listOf("stringValue1", "stringValue2", "stringValue3", "stringValue4") - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("string") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -184,7 +185,7 @@ class JsonObjectTest { fun `boolean array test encode`() { val fieldName = "booleanField" val collection = listOf(true, false, false, true) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("boolean") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -198,7 +199,7 @@ class JsonObjectTest { fun `int array test encode`() { val fieldName = "intField" val collection = listOf(1, 2, 2, 4) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("integer") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -212,7 +213,7 @@ class JsonObjectTest { fun `float array test encode`() { val fieldName = "floatField" val collection = listOf(0.1f, 0.2f, 0.2f, 0.4f) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("number","float") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -226,7 +227,7 @@ class JsonObjectTest { fun `double array test encode`() { val fieldName = "doubleField" val collection = listOf(0.1, 0.2, 0.2, 0.4) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("number", "double") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -240,7 +241,7 @@ class JsonObjectTest { fun `long array test encode`() { val fieldName = "longField" val collection = listOf(111111111L, 222222222L, 222222222L, 444444444L) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("integer", "int64") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -254,7 +255,7 @@ class JsonObjectTest { fun `big decimal array test encode`() { val fieldName = "decimalField" val collection = listOf(BigDecimal(100000000010), BigDecimal(100000002000), BigDecimal(100300000000), BigDecimal(100004000000)) - val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat())) + val visitor = EncodeJsonObjectVisitor(message().addField(fieldName, collection).build(), VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)) val schema = createArrayTestSchema("number", "-") visitor.visit(fieldName, schema, true) val result = requireNotNull(mapper.readTree(visitor.getResult().toStringUtf8()).get(fieldName) as? ArrayNode) @@ -299,7 +300,7 @@ class JsonObjectTest { val message = message().addField(fieldName, listValue).build() - val result = EncodeJsonObjectVisitor(message, VisitorSettings(openAPI, SimpleDateFormat())).apply { + val result = EncodeJsonObjectVisitor(message, VisitorSettings(openAPI, SimpleDateFormat(), DateTimeFormatter.ISO_DATE_TIME)).apply { visit(fieldName, openAPI.components.schemas["ArrayObjectTest"]!! as ArraySchema, true) }.getResult().toStringUtf8() From 1075f9ccee20eadf392d747f73b3cd13fb47cf1d Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Fri, 27 May 2022 11:05:16 +0400 Subject: [PATCH 29/34] removed header message if encoding message don`t contain headers map --- .../th2/codec/openapi/OpenApiCodec.kt | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index 099fc58..6271c21 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -131,11 +131,13 @@ class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSe val container = checkNotNull(typeToSchema[messageType]) { "There no message $messageType in dictionary" } - builder += createHeaderMessage(container, parsedMessage).apply { - if (parsedMessage.hasParentEventId()) parentEventId = parsedMessage.parentEventId - sessionAlias = parsedMessage.sessionAlias - metadataBuilder.putAllProperties(parsedMessage.metadata.propertiesMap) - LOGGER.trace { "Created header message for ${parsedMessage.messageType}: ${this.messageType}" } + if (container.headers.isNotEmpty()) { + builder += createHeaderMessage(container, parsedMessage).apply { + if (parsedMessage.hasParentEventId()) parentEventId = parsedMessage.parentEventId + sessionAlias = parsedMessage.sessionAlias + metadataBuilder.putAllProperties(parsedMessage.metadata.propertiesMap) + LOGGER.trace { "Created header message for ${parsedMessage.messageType}: ${this.messageType}" } + } } try { @@ -177,6 +179,19 @@ class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSe this.id = metadata.id this.timestamp = metadata.timestamp protocol = message.metadata.protocol + + when (container) { + is ResponseContainer -> this.propertiesMap[CODE_FIELD] = container.code + is RequestContainer -> { + this.propertiesMap[URI_FIELD] = if (container.params.isNotEmpty()) { + container.uriPattern.resolve(container.params, message.getMessage(URI_PARAMS_FIELD).orEmpty().fieldsMap.mapValues { it.value.simpleValue }) + } else { + container.uriPattern.pattern + } + this.propertiesMap[METHOD_FIELD] = container.method + } + else -> error("Wrong type of Http Route Container") + } } body = result }.build() @@ -320,16 +335,14 @@ class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSe }) } - if (container.headers.isNotEmpty()) { - val headerMessage = message.getMessage(HEADER_PARAMS_FIELD).orEmpty() - container.headers.forEach { (name, value) -> - headerMessage[name]?.let { header -> - headers.add(message().apply { - addField("name", name) - addField("value", header.simpleValue) - }) - } ?: run { if (value.required) error("Header param [$name] is required for ${message.messageType} message") } - } + val headerMessage = message.getMessage(HEADER_PARAMS_FIELD).orEmpty() + container.headers.forEach { (name, value) -> + headerMessage[name]?.let { header -> + headers.add(message().apply { + addField("name", name) + addField("value", header.simpleValue) + }) + } ?: run { if (value.required) error("Header param [$name] is required for ${message.messageType} message") } } addField(HEADERS_FIELD, headers) From f3afd2642b6d68a152211c87e230be58b173ef39 Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Fri, 27 May 2022 11:19:39 +0400 Subject: [PATCH 30/34] fixed tests for non header cases --- .../th2/codec/openapi/OpenApiCodec.kt | 10 ++-- .../th2/codec/openapi/utils/TestUtils.kt | 60 +++++++++---------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index 6271c21..d66a176 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -131,7 +131,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSe val container = checkNotNull(typeToSchema[messageType]) { "There no message $messageType in dictionary" } - if (container.headers.isNotEmpty()) { + if (container.headers.isNotEmpty() || container.body == null) { builder += createHeaderMessage(container, parsedMessage).apply { if (parsedMessage.hasParentEventId()) parentEventId = parsedMessage.parentEventId sessionAlias = parsedMessage.sessionAlias @@ -181,14 +181,14 @@ class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSe protocol = message.metadata.protocol when (container) { - is ResponseContainer -> this.propertiesMap[CODE_FIELD] = container.code + is ResponseContainer -> this.putProperties(CODE_FIELD, container.code) is RequestContainer -> { - this.propertiesMap[URI_FIELD] = if (container.params.isNotEmpty()) { + this.putProperties(URI_FIELD, if (container.params.isNotEmpty()) { container.uriPattern.resolve(container.params, message.getMessage(URI_PARAMS_FIELD).orEmpty().fieldsMap.mapValues { it.value.simpleValue }) } else { container.uriPattern.pattern - } - this.propertiesMap[METHOD_FIELD] = container.method + }) + this.putProperties(METHOD_FIELD, container.method) } else -> error("Wrong type of Http Route Container") } diff --git a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/TestUtils.kt b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/TestUtils.kt index 117ff26..53e32b2 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/TestUtils.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/openapi/utils/TestUtils.kt @@ -159,42 +159,41 @@ fun OpenApiCodec.testEncode(path: String, method: String, code: String?, type: S val resultGroup = encode(messageGroup.build()) - Assertions.assertTrue(resultGroup.messagesList[0].hasMessage()) + Assertions.assertEquals(1, resultGroup.messagesList.size) - val header = resultGroup.messagesList[0].message + if (fillMessage == null) { + Assertions.assertTrue(resultGroup.messagesList[0].hasMessage()) + val header = resultGroup.messagesList[0].message - val headerType = if (code == null) REQUEST_MESSAGE_TYPE else RESPONSE_MESSAGE_TYPE + val headerType = if (code == null) REQUEST_MESSAGE_TYPE else RESPONSE_MESSAGE_TYPE - header.run { - Assertions.assertEquals(rawParentID, parentEventId) {"Decode process shouldn't lose parent event id inside of header"} - Assertions.assertEquals(headerType, this.messageType) - messageToEncode.metadata.propertiesMap.forEach { - Assertions.assertEquals(it.value, this.metadata.propertiesMap[it.key]) {"Property ${it.key} must be the same as in input message"} - } - when (headerType) { - REQUEST_MESSAGE_TYPE -> { - assertString(OpenApiCodec.URI_FIELD, path) - assertString(OpenApiCodec.METHOD_FIELD, method) - type?.let { - this.getList(OpenApiCodec.HEADERS_FIELD)!![0].messageValue.assertString("value", type) - } ?: Assertions.assertNull(this.getList(OpenApiCodec.HEADERS_FIELD)?.find { it.messageValue.getString("name") == FORMAT_HEADER_NAME }) + header.run { + Assertions.assertEquals(rawParentID, parentEventId) {"Decode process shouldn't lose parent event id inside of header"} + Assertions.assertEquals(headerType, this.messageType) + messageToEncode.metadata.propertiesMap.forEach { + Assertions.assertEquals(it.value, this.metadata.propertiesMap[it.key]) {"Property ${it.key} must be the same as in input message"} } - RESPONSE_MESSAGE_TYPE -> { - assertString(OpenApiCodec.CODE_FIELD, code) - type?.let { - this.getList(OpenApiCodec.HEADERS_FIELD)!![0].messageValue.assertString("value", type) - } ?: Assertions.assertNull(this.getList(OpenApiCodec.HEADERS_FIELD)?.find { it.messageValue.getString("name") == FORMAT_HEADER_NAME }) + when (headerType) { + REQUEST_MESSAGE_TYPE -> { + assertString(OpenApiCodec.URI_FIELD, path) + assertString(OpenApiCodec.METHOD_FIELD, method) + type?.let { + this.getList(OpenApiCodec.HEADERS_FIELD)!![0].messageValue.assertString("value", type) + } ?: Assertions.assertNull(this.getList(OpenApiCodec.HEADERS_FIELD)?.find { it.messageValue.getString("name") == FORMAT_HEADER_NAME }) + } + RESPONSE_MESSAGE_TYPE -> { + assertString(OpenApiCodec.CODE_FIELD, code) + type?.let { + this.getList(OpenApiCodec.HEADERS_FIELD)!![0].messageValue.assertString("value", type) + } ?: Assertions.assertNull(this.getList(OpenApiCodec.HEADERS_FIELD)?.find { it.messageValue.getString("name") == FORMAT_HEADER_NAME }) + } + else -> error("Wrong type of header message: $headerType") } - else -> error("Wrong type of header message: $headerType") } - } - - val body = resultGroup.messagesList.getOrNull(1)?.rawMessage + } else { + val body = resultGroup.messagesList.getOrNull(0)?.rawMessage - if (fillMessage != null) { Assertions.assertNotNull(body) {"Encode must have body in result of filling incoming message"} - Assertions.assertEquals(2, resultGroup.messagesList.size) - Assertions.assertTrue(resultGroup.messagesList[1].hasRawMessage()) body!!.let { rawMessage -> messageToEncode.metadata.propertiesMap.forEach { @@ -207,11 +206,10 @@ fun OpenApiCodec.testEncode(path: String, method: String, code: String?, type: S Assertions.assertEquals(method.lowercase(), rawMessage.metadata.propertiesMap[OpenApiCodec.METHOD_PROPERTY]?.lowercase()) Assertions.assertEquals(path, rawMessage.metadata.propertiesMap[OpenApiCodec.URI_PROPERTY]) } - } else { - Assertions.assertEquals(1, resultGroup.messagesList.size) + return body } - return body + return null } fun OpenApiCodec.encode(message: Message) = encode(MessageGroup.newBuilder().addMessages(AnyMessage.newBuilder().setMessage(message).build()).build()) From f7c8e890f07c80ea8ba0299e15b9906ae394e4db Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Fri, 27 May 2022 13:45:55 +0400 Subject: [PATCH 31/34] review update --- README.md | 2 +- .../th2/codec/openapi/utils/CommonUtils.kt | 3 +- .../th2/codec/openapi/utils/OpenApiUtils.kt | 16 +++------ .../th2/codec/openapi/writer/SchemaWriter.kt | 15 ++++---- .../openapi/writer/visitors/SchemaVisitor.kt | 34 ++++++++---------- .../visitors/json/DecodeJsonObjectVisitor.kt | 28 +++++++-------- .../visitors/json/EncodeJsonObjectVisitor.kt | 35 ++++++++----------- 7 files changed, 57 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 26a40df..d55d22b 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ Result of decode: ### Codec configs: -* checkUndefinedFields - Enable or Disable warnings for all undefined fields inside object structures, (`true` by default). +* failOnUndefinedFields - fail on undefined fields inside object structures, (`true` by default). * dateFormat - setup format of processed date (`yyyy-MM-dd Z` by default) * dateTimeFormat - setup format of processed date-time (`yyyy/MM/dd HH:mm:ss` by default) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/CommonUtils.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/CommonUtils.kt index 0dc68fe..d677027 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/CommonUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/CommonUtils.kt @@ -18,6 +18,7 @@ package com.exactpro.th2.codec.openapi.utils import com.exactpro.th2.common.grpc.Message import com.exactpro.th2.common.grpc.Value +import com.exactpro.th2.common.grpc.Value.KindCase.NULL_VALUE import com.exactpro.th2.common.message.get import com.exactpro.th2.common.message.messageType import com.exactpro.th2.common.value.getString @@ -28,7 +29,7 @@ import com.exactpro.th2.common.value.getString */ fun Message.getField(fieldName: String, required: Boolean): Value? = this[fieldName].apply { if (required) { - check(this != null && this.kindCase.number != 1) { "Field [$fieldName] is required for message [$messageType]" } + check(this != null && this.kindCase != NULL_VALUE) { "Field [$fieldName] is required for message [$messageType]" } } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt index 7abefb2..c89c9ff 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/OpenApiUtils.kt @@ -19,10 +19,7 @@ package com.exactpro.th2.codec.openapi.utils import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.Operation import io.swagger.v3.oas.models.PathItem -import io.swagger.v3.oas.models.media.ArraySchema -import io.swagger.v3.oas.models.media.ComposedSchema import io.swagger.v3.oas.models.media.Content -import io.swagger.v3.oas.models.media.ObjectSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.parser.models.RefType @@ -71,18 +68,13 @@ fun Schema<*>.checkEnum(value: T?, name: String) { fun String.extractType() = substringBefore(';').trim() fun Content.containingFormatOrNull(httpHeader: String) = when { - httpHeader == JSON_FORMAT && this.contains(ANY_FORMAT) -> ANY_FORMAT - this.contains(httpHeader) -> httpHeader + httpHeader == JSON_FORMAT && ANY_FORMAT in this -> ANY_FORMAT + httpHeader in this -> httpHeader else -> null } -fun Schema<*>.getExclusiveProperties(against: List>): List = mutableListOf().apply { - addAll(properties.keys) - removeAll { name -> against.find { it.properties.keys.contains(name) } != null } +fun Schema<*>.getExclusiveProperties(against: List>): List = properties.keys.filter { field -> + against.none { field in it.properties } } -fun Schema<*>.isComposed() = this is ComposedSchema -fun Schema<*>.isObject() = this is ObjectSchema -fun Schema<*>.isArray() = this is ArraySchema - fun Schema<*>.requiredContains(name: String) = this.required?.contains(name) ?: false \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 8357b5c..5545a88 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -37,7 +37,6 @@ import io.swagger.v3.oas.models.media.PasswordSchema import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.media.StringSchema import io.swagger.v3.oas.models.media.UUIDSchema -import java.lang.IllegalStateException class SchemaWriter constructor(private val openApi: OpenAPI) { @@ -45,26 +44,26 @@ class SchemaWriter constructor(private val openApi: OpenAPI) { fun traverse( visitor: SchemaVisitor<*, *>, msgStructure: Schema<*>, - throwUndefined: Boolean = true + checkForUndefinedFields: Boolean = true ) { when (msgStructure) { is ArraySchema -> visitor.visit(ARRAY_TYPE, msgStructure, true) is ComposedSchema -> { when { - !msgStructure.allOf.isNullOrEmpty() -> visitor.allOf(msgStructure.allOf.map { openApi.getEndPoint(it) }).forEach { + !msgStructure.allOf.isNullOrEmpty() -> visitor.allOf(msgStructure.allOf.map(openApi::getEndPoint)).forEach { traverse(visitor, it, false) } - !msgStructure.oneOf.isNullOrEmpty() -> visitor.oneOf(msgStructure.oneOf.map { openApi.getEndPoint(it) }).also { + !msgStructure.oneOf.isNullOrEmpty() -> visitor.oneOf(msgStructure.oneOf.map(openApi::getEndPoint)).also { traverse(visitor, it, false) } - !msgStructure.anyOf.isNullOrEmpty() -> visitor.anyOf(msgStructure.anyOf.map { openApi.getEndPoint(it) }).forEach { + !msgStructure.anyOf.isNullOrEmpty() -> visitor.anyOf(msgStructure.anyOf.map(openApi::getEndPoint)).forEach { traverse(visitor, it, false) } - else -> throw IllegalStateException("Composed schema was empty at allOf, oneOf, anyOf lists") + else -> error("Composed schema has no allOf, oneOf, anyOf definitions") } } else -> { - if (throwUndefined) { + if (checkForUndefinedFields) { visitor.checkUndefined(msgStructure) } msgStructure.properties.forEach { (name, property) -> @@ -78,7 +77,7 @@ class SchemaWriter constructor(private val openApi: OpenAPI) { is DateTimeSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is ComposedSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) is PasswordSchema, is EmailSchema, is BinarySchema, is ByteArraySchema, is FileSchema, is MapSchema, is UUIDSchema -> throw UnsupportedOperationException("${property::class.simpleName} isn't supported for now") - else -> visitor.visit(name, property, msgStructure.requiredContains(name), throwUndefined) + else -> visitor.visit(name, property, msgStructure.requiredContains(name), checkForUndefinedFields) } } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt index 7e6ba50..c65a621 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/SchemaVisitor.kt @@ -50,36 +50,30 @@ sealed class SchemaVisitor { fun oneOf(list: List>): Schema<*> = chooseOneOf(list.filter(this::checkAgainst)) fun anyOf(list: List>): List> = list.filter(this::checkAgainst).also { - if (it.isEmpty()) { - throw IllegalStateException("AnyOf statement had zero valid schemas") - } + check(it.isNotEmpty()) { "AnyOf statement had zero valid schemas" } } fun allOf(list: List>): List> = list.filter(this::checkAgainst).also { - if (list.size != it.size) { - throw IllegalStateException("AllOf statement have only ${it.size} valid schemas of ${list.size} available") - } + check(list.size != it.size) { "AllOf statement have only ${it.size} valid schemas of ${list.size} available" } } fun checkUndefined(objectSchema: Schema<*>) { - val names = objectSchema.properties.keys - val undefined = getFieldNames().filter { !names.contains(it) } - if (undefined.isNotEmpty()) { - throw IllegalStateException("Message have undefined fields: ${undefined.joinToString(", ")}") - } + val undefined = getFieldNames() - objectSchema.properties.keys + check(undefined.isEmpty()) { "Message have undefined fields: ${undefined.joinToString(", ")}" } } private fun checkAgainst(fldStruct: Schema<*>): Boolean = fldStruct.required.isNullOrEmpty() || getFieldNames().containsAll(fldStruct.required) - private fun chooseOneOf(list: List>): Schema<*> = when(list.size) { - 0 -> throw IllegalStateException("OneOf statement have 0 valid schemas") - 1 -> list[0] - else -> { - val objectFieldNames = getFieldNames() - list.find { schema -> - val exclusiveNames = schema.getExclusiveProperties(list.toMutableList().apply { remove(schema) }) - objectFieldNames.find { exclusiveNames.contains(it) } != null - } ?: list[0] + private fun chooseOneOf(schemas: List>): Schema<*> = when(schemas.size) { + 0 -> error("OneOf statement have 0 valid schemas") + 1 -> schemas[0] + else -> getFieldNames().let { names -> + getFieldNames() + schemas.find { schema -> + schema.getExclusiveProperties(schemas - schema).run { + names.any(this::contains) + } + } ?: schemas[0] } } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt index 87eec62..2224546 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/DecodeJsonObjectVisitor.kt @@ -31,6 +31,7 @@ import com.exactpro.th2.common.grpc.Message import com.exactpro.th2.common.message.addField import com.exactpro.th2.common.message.addFields import com.exactpro.th2.common.message.message +import com.exactpro.th2.common.message.set import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.JsonNodeFactory @@ -74,21 +75,20 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val set fromObject.getRequiredArray(fieldName, required)?.let { arrayNode -> when (itemSchema) { - is NumberSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBigDecimal() }) - is IntegerSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsLong() }) - is BooleanSchema -> rootMessage.addField(fieldName, arrayNode.map { it.validateAsBoolean() }) - is StringSchema -> rootMessage.addField(fieldName, arrayNode.map { it.asText() }) - is DateSchema -> rootMessage.addField(fieldName, arrayNode.map { settings.dateFormat.parse(it.asText()).toString() }) - is DateTimeSchema -> rootMessage.addField(fieldName, arrayNode.map { settings.dateTimeFormat.parse(it.asText()).toString() }) + is NumberSchema -> rootMessage[fieldName] = arrayNode.asSequence().map(JsonNode::validateAsBigDecimal).iterator() + is IntegerSchema -> rootMessage[fieldName] = arrayNode.asSequence().map(JsonNode::validateAsLong).iterator() + is BooleanSchema -> rootMessage[fieldName] = arrayNode.asSequence().map(JsonNode::validateAsBoolean).iterator() + is StringSchema -> rootMessage[fieldName] = arrayNode.asSequence().map(JsonNode::asText).iterator() + is DateSchema -> rootMessage[fieldName] = arrayNode.asSequence().map { settings.dateFormat.parse(it.asText()).toString() }.iterator() + is DateTimeSchema -> rootMessage[fieldName] = arrayNode.map { settings.dateTimeFormat.parse(it.asText()).toString() }.iterator() is BinarySchema, is ByteArraySchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") - else -> rootMessage.addField(fieldName, mutableListOf().apply { - arrayNode.forEach { - DecodeJsonObjectVisitor(checkNotNull(it.validateAsObject()) { " Value from list [$fieldName] must be message" }, settings).let { visitor -> - SchemaWriter(settings.openAPI).traverse(visitor, itemSchema, throwUndefined) - visitor.rootMessage.build().run(this::add) - } + else -> rootMessage[fieldName] = arrayNode.map { + val message = checkNotNull(it.validateAsObject()) { "'$fieldName' field element is not a message: $it" } + DecodeJsonObjectVisitor(message, settings).let { visitor -> + SchemaWriter(settings.openAPI).traverse(visitor, itemSchema, throwUndefined) + visitor.rootMessage } - }) + } } } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } } @@ -99,7 +99,7 @@ open class DecodeJsonObjectVisitor(override val from: JsonNode, override val set val writer = SchemaWriter(settings.openAPI) writer.traverse(visitor, fldStruct, false) rootMessage.addField(fieldName, visitor.rootMessage) - } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } + } ?: fldStruct.default?.let { error("Default values isn't supported for composed objects") } } override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, fromObject.getField(fieldName, required)?.validateAsBigDecimal(), fldStruct) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt index 0164398..87c7021 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/visitors/json/EncodeJsonObjectVisitor.kt @@ -26,8 +26,8 @@ import com.exactpro.th2.codec.openapi.writer.visitors.SchemaVisitor import com.exactpro.th2.codec.openapi.writer.visitors.VisitorSettings import com.exactpro.th2.common.grpc.Message import com.exactpro.th2.common.grpc.Value +import com.exactpro.th2.common.grpc.Value.KindCase.NULL_VALUE import com.exactpro.th2.common.value.getBigDecimal -import com.exactpro.th2.common.value.getList import com.exactpro.th2.common.value.getLong import com.exactpro.th2.common.value.getMessage import com.exactpro.th2.common.value.getString @@ -59,15 +59,13 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val sett override fun visit(fieldName: String, fldStruct: Schema<*>, required: Boolean, throwUndefined: Boolean) { from.getField(fieldName, required)?.let { field -> - when { - field.kindCase.number == 1 -> { - rootNode.set(fieldName, ObjectNode(mapper.nodeFactory)) - return - } - !field.hasMessageValue() -> error("$fieldName is not an message: ${field.kindCase}") + if (field.kindCase == NULL_VALUE) { + rootNode.putObject(fieldName) + return } - val message = field.getMessage()!! - val visitor = EncodeJsonObjectVisitor(message, settings) + check(field.hasMessageValue()) { "$fieldName is not an message: ${field.kindCase}" } + + val visitor = EncodeJsonObjectVisitor(field.messageValue, settings) val writer = SchemaWriter(settings.openAPI) writer.traverse(visitor, fldStruct, throwUndefined) rootNode.set(fieldName, visitor.rootNode) @@ -78,14 +76,13 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val sett val itemSchema = settings.openAPI.getEndPoint(fldStruct.items) from.getField(fieldName, required)?.let { field -> - when { - field.kindCase.number == 1 -> { - rootNode.putArray(fieldName) - return - } - !field.hasListValue() -> error("$fieldName is not an list: ${field.kindCase}") + if (field.kindCase == NULL_VALUE) { + rootNode.putArray(fieldName) + return } - val listOfValues = field.getList()!! + check(field.hasListValue()) { "$fieldName is not an list: ${field.kindCase}" } + + val listOfValues = field.listValue.valuesList when (itemSchema) { is NumberSchema -> rootNode.putArray(fieldName).putAll(listOfValues) @@ -100,15 +97,13 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val sett } is BinarySchema, is ByteArraySchema, is EmailSchema, is FileSchema, is MapSchema, is PasswordSchema, is UUIDSchema -> throw UnsupportedOperationException("${itemSchema::class.simpleName} for json array isn't supported for now") else -> rootNode.putArray(fieldName).apply { - val listOfNodes = mutableListOf() val writer = SchemaWriter(settings.openAPI) listOfValues.forEach { EncodeJsonObjectVisitor(checkNotNull(it.getMessage()) { " Value from list [$fieldName] must be message" }, settings).let { visitor -> writer.traverse(visitor, itemSchema, throwUndefined) - visitor.rootNode.run(listOfNodes::add) + add(visitor.rootNode) } } - listOfNodes.forEach { add(it) } } } } ?: fldStruct.default?.let { error("Default values isn't supported for arrays") } @@ -126,7 +121,7 @@ open class EncodeJsonObjectVisitor(override val from: Message, override val sett val writer = SchemaWriter(settings.openAPI) writer.traverse(visitor, fldStruct, false) rootNode.set(fieldName, visitor.rootNode) - } ?: fldStruct.default?.let { error("Default values isn't supported for objects") } + } ?: fldStruct.default?.let { error("Default values isn't supported for composed objects") } } override fun visit(fieldName: String, fldStruct: NumberSchema, required: Boolean) = visitPrimitive(fieldName, from.getField(fieldName, required), fldStruct, Value::getBigDecimal) { From 81924e715b00cb0d2d89be5664758a139aba7b5b Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Fri, 27 May 2022 15:23:20 +0400 Subject: [PATCH 32/34] fixed timestamp and id --- .../exactpro/th2/codec/openapi/OpenApiCodec.kt | 6 +++--- .../exactpro/th2/codec/openapi/utils/JsonUtils.kt | 15 --------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt index d66a176..1540067 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/OpenApiCodec.kt @@ -176,8 +176,8 @@ class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSe container.fillHttpMetadata(metadataBuilder) metadataBuilder.apply { putAllProperties(message.metadata.propertiesMap) - this.id = metadata.id - this.timestamp = metadata.timestamp + this.id = message.metadata.id + this.timestamp = message.metadata.timestamp protocol = message.metadata.protocol when (container) { @@ -239,7 +239,7 @@ class OpenApiCodec(private val dictionary: OpenAPI, val settings: OpenApiCodecSe this.messageType = messageType metadataBuilder.apply { id = rawMessage.metadata.id - timestamp = metadata.timestamp + timestamp = rawMessage.metadata.timestamp protocol = rawMessage.metadata.protocol putAllProperties(rawMessage.metadata.propertiesMap) } diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/JsonUtils.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/JsonUtils.kt index 3682869..f3dcde5 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/JsonUtils.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/utils/JsonUtils.kt @@ -78,21 +78,6 @@ fun JsonNode.validateAsBigDecimal(): BigDecimal = when { else -> error("Cannot convert $this to BigDecimal") } -fun JsonNode.validateAsInteger(): Int = when { - isNumber -> asInt() - else -> error("Cannot convert $this to Int") -} - -fun JsonNode.validateAsDouble(): Double = when { - isNumber -> asDouble() - else -> error("Cannot convert $this to Double") -} - -fun JsonNode.validateAsFloat(): Float = when { - isNumber -> asText().toFloat() - else -> error("Cannot convert $this to Float") -} - fun JsonNode.validateAsObject(): ObjectNode = when { isObject -> this as ObjectNode else -> error("Cannot convert $this to Object") From 3c7e43cf3f1bc32642dec51f16a728be0ac3256c Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 30 May 2022 16:29:12 +0400 Subject: [PATCH 33/34] fixing null properties --- .../th2/codec/openapi/writer/SchemaWriter.kt | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 5545a88..6c7717c 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -66,19 +66,21 @@ class SchemaWriter constructor(private val openApi: OpenAPI) { if (checkForUndefinedFields) { visitor.checkUndefined(msgStructure) } - msgStructure.properties.forEach { (name, property) -> - when(property) { - is ArraySchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is StringSchema -> visitor.visit(name, property , msgStructure.requiredContains(name)) - is IntegerSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is NumberSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is BooleanSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is DateSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is DateTimeSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is ComposedSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) - is PasswordSchema, is EmailSchema, is BinarySchema, is ByteArraySchema, is FileSchema, is MapSchema, is UUIDSchema -> throw UnsupportedOperationException("${property::class.simpleName} isn't supported for now") - else -> visitor.visit(name, property, msgStructure.requiredContains(name), checkForUndefinedFields) - } + openApi.getEndPoint(msgStructure).let { + it.properties?.forEach { (name, property) -> + when(property) { + is ArraySchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is StringSchema -> visitor.visit(name, property , msgStructure.requiredContains(name)) + is IntegerSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is NumberSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is BooleanSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is DateSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is DateTimeSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is ComposedSchema -> visitor.visit(name, property, msgStructure.requiredContains(name)) + is PasswordSchema, is EmailSchema, is BinarySchema, is ByteArraySchema, is FileSchema, is MapSchema, is UUIDSchema -> throw UnsupportedOperationException("${property::class.simpleName} isn't supported for now") + else -> visitor.visit(name, property, msgStructure.requiredContains(name), checkForUndefinedFields) + } + } ?: error("Schema ${it.name}:${it.type} have no properties to process") } } } From bfa1efae7357b142d656067849ce08bdad61053c Mon Sep 17 00:00:00 2001 From: Nikita Shaposhnikov Date: Mon, 30 May 2022 16:56:33 +0400 Subject: [PATCH 34/34] check for undefined oneOf --- .../com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt index 6c7717c..c850f6a 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/openapi/writer/SchemaWriter.kt @@ -54,7 +54,7 @@ class SchemaWriter constructor(private val openApi: OpenAPI) { traverse(visitor, it, false) } !msgStructure.oneOf.isNullOrEmpty() -> visitor.oneOf(msgStructure.oneOf.map(openApi::getEndPoint)).also { - traverse(visitor, it, false) + traverse(visitor, it, true) } !msgStructure.anyOf.isNullOrEmpty() -> visitor.anyOf(msgStructure.anyOf.map(openApi::getEndPoint)).forEach { traverse(visitor, it, false)