diff --git a/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt index db57e31..9929dc5 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt @@ -46,6 +46,7 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings) private val beginString = settings.beginString private val charset = settings.charset private val isDirtyMode = settings.dirtyMode + private val isDecodeToStrings = settings.decodeValuesToStrings private val fieldsEncode = convertToFieldsByName(dictionary.fields, true) private val fieldsDecode = convertToFieldsByTag(dictionary.fields) @@ -139,7 +140,7 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings) } header["BeginString"] = beginString - header["BodyLength"] = bodyLength + header["BodyLength"] = if (isDecodeToStrings) bodyLengthString else bodyLength header["MsgType"] = msgType body[HEADER] = header @@ -243,7 +244,7 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings) handleError(isDirty, context, "Invalid value in enum field $name. Actual: $value. Valid values $values.") } - return try { + val decodedValue = try { when (primitiveType) { java.lang.String::class.java -> value java.lang.Character::class.java -> { @@ -279,6 +280,8 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings) } catch (e: DateTimeParseException) { handleError(isDirty, context, "Wrong date/time value in ${primitiveType.name} field '$name'. Value: $value.", value) } + + return if (isDecodeToStrings) value else decodedValue } private fun Group.decode(source: ByteBuf, count: Int, isDirty: Boolean, context: IReportingContext): List> = ArrayList>().also { list -> diff --git a/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecSettings.kt b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecSettings.kt index 40f3138..52f1825 100644 --- a/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecSettings.kt +++ b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecSettings.kt @@ -23,5 +23,6 @@ data class FixNgCodecSettings( val beginString: String = "FIXT.1.1", val dictionary: String, val charset: Charset = Charsets.US_ASCII, - val dirtyMode: Boolean = false -) : IPipelineCodecSettings + val dirtyMode: Boolean = false, + val decodeValuesToStrings: Boolean = true, +) : IPipelineCodecSettings \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecTest.kt b/src/test/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecTest.kt index 19e2f41..d69346c 100644 --- a/src/test/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecTest.kt +++ b/src/test/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecTest.kt @@ -34,14 +34,19 @@ import org.junit.jupiter.api.Test import java.math.BigDecimal import java.nio.charset.StandardCharsets import java.time.Instant +import java.time.LocalDate import java.time.LocalDateTime +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeFormatterBuilder +import java.time.temporal.ChronoField class FixNgCodecTest { private val dictionary: IDictionaryStructure = FixNgCodecTest::class.java.classLoader .getResourceAsStream("dictionary.xml") .use(XmlDictionaryStructureLoader()::load) - private val codec = FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "")) + private val codec = FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "", decodeValuesToStrings = false)) private val reportingContext = object : IReportingContext { private val _warnings: MutableList = ArrayList() @@ -64,6 +69,9 @@ class FixNgCodecTest { @Test fun `simple decode`() = decodeTest(MSG_CORRECT) + @Test + fun `simple decode to string values`() = decodeTest(MSG_CORRECT, stringValues = true) + @Test fun `simple decode with no body`() = decodeTest(MSG_CORRECT_WITHOUT_BODY, expectedMessage = expectedMessageWithoutBody) @@ -246,7 +254,8 @@ class FixNgCodecTest { rawMessageString: String, expectedErrorText: String? = null, expectedMessage: ParsedMessage = expectedParsedMessage, - dirtyMode: Boolean = true + dirtyMode: Boolean = true, + stringValues: Boolean = false ) { val expectedBody = expectedMessage.body val rawMessage = RawMessage( @@ -257,6 +266,7 @@ class FixNgCodecTest { ) val decodedGroup = try { + val codec = if (stringValues) FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "")) else this.codec codec.decode(MessageGroup(listOf(rawMessage)), reportingContext) } catch (e: IllegalStateException) { if (dirtyMode) { @@ -269,13 +279,14 @@ class FixNgCodecTest { val parsedMessage = decodedGroup.messages.single() as ParsedMessage - // we don't validate CheckSum and BodyLength for incorrect messages + // we don't validate `CheckSum` and `BodyLength` in incorrect messages val fieldsToIgnore = if (expectedErrorText == null) emptyArray() else arrayOf("trailer.CheckSum", "header.BodyLength") + val expected = if (stringValues) convertValuesToString(expectedBody) else expectedBody assertThat(parsedMessage.body) .usingRecursiveComparison() .ignoringFields(*fieldsToIgnore) - .isEqualTo(expectedBody) + .isEqualTo(expected) if (expectedErrorText == null) { assertThat(reportingContext.warnings).isEmpty() @@ -284,6 +295,16 @@ class FixNgCodecTest { } } + private fun convertValuesToString(value: Any?): Any = when (value) { + is Map<*, *> -> value.mapValues { convertValuesToString(it.value) } + is List<*> -> value.map(::convertValuesToString) + is java.lang.Boolean -> if (value.booleanValue()) "Y" else "N" + is LocalDateTime -> value.format(dateTimeFormatter) + is LocalDate -> value.format(dateFormatter) + is LocalTime -> value.format(timeFormatter) + else -> value.toString() + } + private val parsedMessage = ParsedMessage( MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), EventId("test_id", "test_book", "test_scope", Instant.now()), @@ -435,5 +456,17 @@ class FixNgCodecTest { private const val MSG_NON_PRINTABLE = "8=FIXT.1.1\u00019=303\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\taccount\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=171\u0001" private const val MSG_REQUIRED_HEADER_REMOVED = "8=FIXT.1.1\u00019=236\u000135=8\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=050\u0001" private const val MSG_TAG_OUT_OF_ORDER = "8=FIXT.1.1\u00019=295\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=000\u0001999=500\u0001" + + private val dateTimeFormatter = DateTimeFormatterBuilder() + .appendPattern("yyyyMMdd-HH:mm:ss") + .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) + .toFormatter() + + private val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd") + + private val timeFormatter = DateTimeFormatterBuilder() + .appendPattern("HH:mm:ss") + .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 9, true) + .toFormatter() } } \ No newline at end of file