Skip to content

Commit fb29785

Browse files
[GH-68] Fix: can't encoded typed value for STRING filed (#69)
1 parent 4a31b8a commit fb29785

File tree

5 files changed

+82
-14
lines changed

5 files changed

+82
-14
lines changed

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# th2-codec-fix-ng 0.1.5
1+
# th2-codec-fix-ng 0.1.6
22

33
This codec can be used in dirty mode for decoding and encoding messages via the FIX protocol.
44

@@ -130,6 +130,17 @@ Component benchmark results available [here](docs/benchmarks/jmh-benchmark.md).
130130

131131
## Release notes
132132

133+
### 0.1.6
134+
135+
+ Fixes:
136+
+ encoded typed value without error for STRING filed
137+
+ Updated:
138+
+ th2 gradle plugin: `0.3.8` (bom: `4.14.1`)
139+
+ kotlin: `2.2.10`
140+
+ kotlin-logging: `7.0.13`
141+
+ common: `5.16.1-dev`
142+
+ codec: `5.6.0-dev`
143+
133144
### 0.1.5
134145

135146
+ Fixes:

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
kotlin.code.style=official
2-
release_version=0.1.5
2+
release_version=0.1.6
33
vcs_url=https://github.com/th2-net/th2-codec-fix-ng

src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -506,23 +506,25 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
506506
}
507507

508508
private fun encodeField(field: Field, value: Any, target: ByteBuf, isDirty: Boolean, dictionaryFields: Map<String, Field>, context: IReportingContext) {
509-
when {
510-
field is Primitive -> {
509+
when (field) {
510+
is Primitive -> {
511511
val valueToEncode = when {
512512
isCompatibleType(value.javaClass, field.primitiveType) -> value
513+
field.primitiveType == String::class.java -> value
513514
value is String -> {
514515
try {
515516
when (field.primitiveType) {
516-
LocalDateTime::class.java -> MultiConverter.convert<LocalDateTime>(value, LocalDateTime::class.java)
517-
LocalDate::class.java -> MultiConverter.convert<LocalDate>(value, LocalDate::class.java)
518-
LocalTime::class.java -> MultiConverter.convert<LocalTime>(value, LocalTime::class.java)
517+
LocalDateTime::class.java -> MultiConverter.convert(value, LocalDateTime::class.java)
518+
LocalDate::class.java -> MultiConverter.convert(value, LocalDate::class.java)
519+
LocalTime::class.java -> MultiConverter.convert(value, LocalTime::class.java)
519520
java.lang.Boolean::class.java -> when {
520521
value.equals("true", true) -> true
521522
value.equals("Y", true) -> true // https://github.com/th2-net/th2-codec-fix-ng/issues/43
522523
value.equals("false", true) -> false
523524
value.equals("N", true) -> false // https://github.com/th2-net/th2-codec-fix-ng/issues/43
524525
else -> handleError(isDirty, context, "Wrong boolean value in ${field.primitiveType.name} field '$field.name'. Value: $value.", value)
525526
}
527+
526528
else -> {
527529
// we reuse decode() method for the types that have the same string representation
528530
// of values in FIX protocol and in TH2 transport protocol
@@ -535,6 +537,7 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
535537
handleError(isDirty, context, "Wrong date/time value in ${field.primitiveType.name} field '$field.name'. Value: $value.", value)
536538
}
537539
}
540+
538541
else -> handleError(isDirty, context, "Wrong type value in field ${field.name}. Actual: ${value.javaClass} (value: $value). Expected ${field.primitiveType}", value)
539542
}
540543

@@ -555,8 +558,8 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
555558
target.writeField(field.tag, stringValue, SOH_BYTE, charset)
556559
}
557560

558-
field is Group && value is List<*> -> field.encode(value, target, isDirty, dictionaryFields, context)
559-
field is Message && value is Map<*,*> -> {
561+
is Group if value is List<*> -> field.encode(value, target, isDirty, dictionaryFields, context)
562+
is Message if value is Map<*,*> -> {
560563
@Suppress("UNCHECKED_CAST")
561564
val messageValue = value as Map<String, *>
562565
field.encode(messageValue, target, isDirty, dictionaryFields, context)
@@ -711,7 +714,7 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
711714
java.lang.Short::class.java to 2,
712715
java.lang.Integer::class.java to 3,
713716
java.lang.Long::class.java to 4,
714-
BigDecimal:: class.java to 5
717+
BigDecimal::class.java to 5
715718
)
716719

717720
private fun isCompatibleType(from: Class<*>, to: Class<*>): Boolean {

src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecSettings.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ data class FixNgCodecSettings(
3131
val decodeDelimiter: Char = SOH_CHAR,
3232
val dirtyMode: Boolean = false,
3333
val decodeValuesToStrings: Boolean = true,
34-
val decodeComponentsToNestedMaps: Boolean = true
34+
val decodeComponentsToNestedMaps: Boolean = true,
3535
) : IPipelineCodecSettings
3636

3737
object CharsetDeserializer : JsonDeserializer<Charset>() {

src/test/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecTest.kt

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.exactpro.th2.codec.fixng
1818

1919
import com.exactpro.sf.common.messages.structures.IDictionaryStructure
2020
import com.exactpro.sf.common.messages.structures.loaders.XmlDictionaryStructureLoader
21+
import com.exactpro.sf.comparison.conversion.MultiConverter
2122
import com.exactpro.th2.codec.api.IReportingContext
2223
import com.exactpro.th2.codec.fixng.FixNgCodecFactory.Companion.PROTOCOL
2324
import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.Direction
@@ -69,6 +70,53 @@ class FixNgCodecTest {
6970
@MethodSource("configs")
7071
fun `simple encode`(isDirty: Boolean, delimiter: Char) = encodeTest(MSG_CORRECT, isDirty, delimiter)
7172

73+
@ParameterizedTest
74+
@CsvSource(
75+
"true,java.lang.Character,a,,287,072",
76+
"true,java.lang.Byte,127,,289,131",
77+
"true,java.lang.Short,32767,,291,235",
78+
"true,java.lang.Integer,2147483647,,296,245",
79+
"true,java.lang.Long,9223372036854775807,,305,198",
80+
"true,java.lang.Float,3.4028234,,295,174",
81+
"true,java.lang.Double,1.7976931348623157,,304,141",
82+
"true,java.math.BigDecimal,1.7976931348623157,,304,141",
83+
"true,java.time.LocalDateTime,2025-09-29T14:07:53.168966352,20250929-14:07:53.168966352,313,091",
84+
"true,java.time.LocalDate,2025-09-29,20250929,294,130",
85+
"true,java.time.LocalTime,14:07:53.168,,298,094", // FIXME: Seconds unit should depend of settings
86+
"true,java.lang.Boolean,true,Y,287,064",
87+
88+
"false,java.lang.Character,a,,287,072",
89+
"false,java.lang.Byte,127,,289,131",
90+
"false,java.lang.Short,32767,,291,235",
91+
"false,java.lang.Integer,2147483647,,296,245",
92+
"false,java.lang.Long,9223372036854775807,,305,198",
93+
"false,java.lang.Float,3.4028234,,295,174",
94+
"false,java.lang.Double,1.7976931348623157,,304,141",
95+
"false,java.math.BigDecimal,1.7976931348623157,,304,141",
96+
"false,java.time.LocalDateTime,2025-09-29T14:07:53.168966352,20250929-14:07:53.168966352,313,091",
97+
"false,java.time.LocalDate,2025-09-29,20250929,294,130",
98+
"false,java.time.LocalTime,14:07:53.168,,298,094", // FIXME: Seconds unit should depend of settings
99+
"false,java.lang.Boolean,true,Y,287,064",
100+
)
101+
fun `encode with different value types for string field`(
102+
isDirty: Boolean,
103+
clazz: Class<*>,
104+
value: String,
105+
encodedValue: String?,
106+
length: Int,
107+
checkSum: String,
108+
) {
109+
parsedBody["ExecID"] = MultiConverter.convert(value, clazz)
110+
encodeTest(
111+
MSG_CORRECT
112+
.replace("17=495504662", "17=${encodedValue ?: value}")
113+
.replace("9=295", "9=${length}")
114+
.replace("10=191", "10=${checkSum}"),
115+
dirtyMode = isDirty,
116+
'',
117+
)
118+
}
119+
72120
@ParameterizedTest
73121
@MethodSource("configs")
74122
fun `simple encode from string values`(isDirty: Boolean, delimiter: Char) =
@@ -297,6 +345,7 @@ class FixNgCodecTest {
297345

298346
@ParameterizedTest
299347
@ValueSource(chars = ['', '|'])
348+
@Suppress("SpellCheckingInspection")
300349
fun `tags with 0 prefix decode (dirty)`(delimiter: Char) {
301350
with(parsedMessage) {
302351
(body map "header").set("BodyLength", 302)
@@ -1007,7 +1056,7 @@ class FixNgCodecTest {
10071056
return FixNgCodec(dictionary, FixNgCodecSettings(
10081057
dictionary = "",
10091058
decodeValuesToStrings = decodeValuesToStrings,
1010-
decodeDelimiter = delimiter
1059+
decodeDelimiter = delimiter,
10111060
))
10121061
}
10131062

@@ -1031,7 +1080,7 @@ class FixNgCodecTest {
10311080
parsedMessage: ParsedMessage,
10321081
delimiter: Char,
10331082
expectedWarning: String? = null,
1034-
encodeFromStringValues: Boolean = false
1083+
encodeFromStringValues: Boolean = false,
10351084
) {
10361085
val parsedBody = parsedMessage.body as MutableMap<String, Any?>
10371086

@@ -1057,7 +1106,7 @@ class FixNgCodecTest {
10571106
parsedMessage: ParsedMessage,
10581107
delimiter: Char,
10591108
expectedError: String? = null,
1060-
encodeFromStringValues: Boolean = false
1109+
encodeFromStringValues: Boolean = false,
10611110
) {
10621111
val parsedBody = parsedMessage.body as MutableMap<String, Any?>
10631112

@@ -1192,6 +1241,7 @@ class FixNgCodecTest {
11921241
else -> value.toString()
11931242
}
11941243

1244+
@Suppress("SpellCheckingInspection")
11951245
private val parsedMessage = ParsedMessage(
11961246
MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()),
11971247
EventId("test_id", "test_book", "test_scope", Instant.now()),
@@ -1249,6 +1299,7 @@ class FixNgCodecTest {
12491299

12501300
private val parsedBody: MutableMap<String, Any?> = parsedMessage.body as MutableMap
12511301

1302+
@Suppress("SpellCheckingInspection")
12521303
private val expectedMessageWithoutBody = ParsedMessage(
12531304
MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()),
12541305
EventId("test_id", "test_book", "test_scope", Instant.now()),
@@ -1271,6 +1322,7 @@ class FixNgCodecTest {
12711322
)
12721323
)
12731324

1325+
@Suppress("SpellCheckingInspection")
12741326
private val parsedMessageWithNestedComponents = ParsedMessage(
12751327
MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()),
12761328
EventId("test_id", "test_book", "test_scope", Instant.now()),
@@ -1300,6 +1352,7 @@ class FixNgCodecTest {
13001352
)
13011353
private val parsedBodyWithNestedComponents: MutableMap<String, Any?> = parsedMessageWithNestedComponents.body as MutableMap
13021354

1355+
@Suppress("SpellCheckingInspection")
13031356
private val parsedMessageWithNestedGroups = ParsedMessage(
13041357
MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()),
13051358
EventId("test_id", "test_book", "test_scope", Instant.now()),
@@ -1368,6 +1421,7 @@ class FixNgCodecTest {
13681421
)
13691422
)
13701423

1424+
@Suppress("SpellCheckingInspection")
13711425
private val parsedLogon = ParsedMessage(
13721426
MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()),
13731427
EventId("test_id", "test_book", "test_scope", Instant.now()),

0 commit comments

Comments
 (0)