diff --git a/README.md b/README.md index 49edd7d1..cc6d8b93 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ In case you don't have much time for this - at least spend 5 seconds to give us ## Acknowledgement Special thanks to those awesome developers who give us great suggestions, help us to maintain and improve this project: -@NightEule5, @bishiboosh, @Peanuuutz, @petertrr, @nulls, @Olivki, @edrd-f and @BOOMeranGG. +@NightEule5, @bishiboosh, @Peanuuutz, @petertrr, @nulls, @Olivki, @edrd-f, @BOOMeranGG, @aSemy ## Supported platforms All the code is written in Kotlin **common** module. This means that it can be built for each and every Kotlin native platform. @@ -68,7 +68,8 @@ We are still developing and testing this library, so it has several limitations: :white_check_mark: Offset Date-Time (to `Instant` of [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)) \ :white_check_mark: Local Date-Time (to `LocalDateTime` of [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)) \ :white_check_mark: Local Date (to `LocalDate` of [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)) \ -:x: Arrays: nested; multiline; of Different Types \ +:white_check_mark: Arrays (including multiline arrays) \ +:x: Arrays: nested; of Different Types \ :x: Multiline Strings \ :x: Nested Inline Tables \ :x: Array of Tables \ diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt index e4802b86..03fa4133 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt @@ -26,8 +26,8 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { override fun decodeByte(): Byte = decodePrimitiveType() override fun decodeShort(): Short = decodePrimitiveType() override fun decodeInt(): Int = decodePrimitiveType() - override fun decodeFloat(): Float = decodePrimitiveType() - override fun decodeChar(): Char = decodePrimitiveType() + override fun decodeFloat(): Float = invalidType("Float", "Double") + override fun decodeChar(): Char = invalidType("Char", "String") // Valid Toml types that should be properly decoded override fun decodeBoolean(): Boolean = decodePrimitiveType() @@ -86,6 +86,11 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { Short::class -> validateAndConvertInt(content, lineNo, SHORT) { num: Long -> num.toShort() as T } Int::class -> validateAndConvertInt(content, lineNo, INT) { num: Long -> num.toInt() as T } Long::class -> validateAndConvertInt(content, lineNo, LONG) { num: Long -> num as T } + Double::class, Float::class -> throw IllegalTypeException( + "Expected floating-point number, but received integer literal: <$content>. " + + "Deserialized floating-point number should have a dot: <$content.0>", + lineNo + ) else -> invalidType(T::class.toString(), "Signed Type") } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt index 3449eaab..83b030dc 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/encoders/TomlAbstractEncoder.kt @@ -2,7 +2,6 @@ package com.akuleshov7.ktoml.encoders import com.akuleshov7.ktoml.TomlInputConfig import com.akuleshov7.ktoml.TomlOutputConfig -import com.akuleshov7.ktoml.exceptions.IllegalEncodingTypeException import com.akuleshov7.ktoml.exceptions.InternalEncodingException import com.akuleshov7.ktoml.exceptions.UnsupportedEncodingFeatureException import com.akuleshov7.ktoml.tree.nodes.TomlNode @@ -154,24 +153,11 @@ public abstract class TomlAbstractEncoder protected constructor( } } - override fun encodeByte(value: Byte): Nothing = invalidType("Byte", "Long", value) - override fun encodeShort(value: Short): Nothing = invalidType("Short", "Long", value) - override fun encodeInt(value: Int): Nothing = invalidType("Int", "Long", value) - override fun encodeFloat(value: Float): Nothing = invalidType("Float", "Double", value) - override fun encodeChar(value: Char): Nothing = invalidType("Char", "String", value) - - // Todo: Do we really want to make these invalid? - private fun invalidType( - typeName: String, - requiredType: String, - value: Any - ): Nothing { - throw IllegalEncodingTypeException( - "<$typeName> is not allowed by the TOML specification, use <$requiredType>" + - " instead (key = ${attributes.getFullKey()}; value = $value)", - elementIndex - ) - } + override fun encodeByte(value: Byte): Unit = encodeLong(value.toLong()) + override fun encodeShort(value: Short): Unit = encodeLong(value.toLong()) + override fun encodeInt(value: Int): Unit = encodeLong(value.toLong()) + override fun encodeFloat(value: Float): Unit = encodeDouble(value.toDouble()) + override fun encodeChar(value: Char): Unit = encodeString(value.toString()) // Structure diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt index 92b0c992..9aae9c25 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/GeneralDecoderTest.kt @@ -125,10 +125,12 @@ class GeneralDecoderTest { @Test fun testForSimpleNestedTable() { - val test = ("c = 5 \n" + - "[table1] \n" + - " b = 6 \n" + - " a = 5 \n ") + val test = """ + c = 5 + [table1] + b = 6 + a = 5 + """.trimIndent() assertEquals(NestedSimpleTable(5, Table1(5, 6)), Toml.decodeFromString(test)) } @@ -374,19 +376,20 @@ class GeneralDecoderTest { @Test fun severalTablesOnTheSameLevel() { - val test = """|[table] - |[table.in1] - | a = 1 - | [table.in1.in1] - | a = 1 - | [table.in1.in2] - | a = 1 - |[table.in2] - | a = 1 - | [table.in2.in1] - | a = 1 - | [table.in2.in2] - | a = 1 + val test = """ + |[table] + |[table.in1] + | a = 1 + | [table.in1.in1] + | a = 1 + | [table.in1.in2] + | a = 1 + |[table.in2] + | a = 1 + | [table.in2.in1] + | a = 1 + | [table.in2.in2] + | a = 1 """.trimMargin() assertEquals( diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt new file mode 100644 index 00000000..1e3ff3ca --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt @@ -0,0 +1,262 @@ +package com.akuleshov7.ktoml.decoders + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.exceptions.IllegalTypeException +import com.akuleshov7.ktoml.exceptions.ParseException +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + + +class PrimitivesDecoderTest { + @Test + fun decodeByte() { + fun test(expected: Byte, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Byte) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + + test(0, "0") + test(1, "1") + test(-1, "-1") + test(-128, "-128") + test(127, "127") + } + + @Test + fun decodeByteFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Byte) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("-129") + testFails("128") + } + + @Test + fun decodeChar() { + fun test(expected: Char, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Char) + + assertFailsWith { + val data = Toml.decodeFromString(toml) + assertEquals(expected, data.value) + } + } + + test((0).toChar(), "0") + test((-1).toChar(), "-1") + test((1).toChar(), "1") + test(Char.MAX_VALUE, "0") + test(Char.MIN_VALUE, "1") + } + + @Test + fun decodeShort() { + fun test(expected: Short, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Short) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + + test(0, "0") + test(1, "1") + test(-1, "-1") + test(-128, "-128") + test(127, "127") + } + + @Test + fun decodeShortFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Short) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("${Short.MAX_VALUE.toInt() + 1}") + testFails("${Short.MIN_VALUE.toInt() - 1}") + } + + @Test + fun decodeInt() { + fun test(expected: Int, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Int) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + + test(0, "0") + test(1, "1") + test(-1, "-1") + test(-128, "-128") + test(127, "127") + test(Int.MAX_VALUE, "${Int.MAX_VALUE}") + test(Int.MIN_VALUE, "${Int.MIN_VALUE}") + } + + @Test + fun decodeIntFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Int) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("${Int.MIN_VALUE.toLong() - 1}") + testFails("${Int.MAX_VALUE.toLong() + 1}") + } + + @Test + fun decodeLong() { + fun test(expected: Long, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Long) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + + test(0, "0") + test(1, "1") + test(-1, "-1") + test(-128, "-128") + test(127, "127") + test(Long.MIN_VALUE, "${Long.MIN_VALUE}") + test(Long.MAX_VALUE, "${Long.MAX_VALUE}") + } + + @Test + fun decodeLongFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Long) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("${Long.MIN_VALUE}0") + testFails("${Long.MAX_VALUE}0") + } + + @Test + fun decodeFloat() { + fun test(expected: Float, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Float) + + assertFailsWith { + val data = Toml.decodeFromString(toml) + assertEquals(expected, data.value) + } + } + + test(0f, "0") + test(1f, "1") + test(-1f, "-1") + test(-128f, "-128") + test(127f, "127") + } + + @Test + fun decodeFloatFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Float) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("-129") + testFails("128") + } + + @Test + fun decodeDouble() { + fun test(expected: Double, input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Double) + + val data = Toml.decodeFromString(toml) + + assertEquals(expected, data.value) + } + + test(0.0, "0.0") + test(1.0, "1.0") + test(-1.0, "-1.0") + test(-128.0, "-128.0") + test(127.0, "127.0") + } + + @Test + fun decodeDoubleFailure() { + fun testFails(input: String) { + val toml = /*language=TOML*/ """value = $input""" + + @Serializable + data class Data(val value: Double) + + assertFailsWith(input) { + Toml.decodeFromString(toml) + } + } + + testFails("-129") + testFails("128") + testFails("0") + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt index 7882e487..a2e1d3a9 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/PrimitiveEncoderTest.kt @@ -18,6 +18,7 @@ class PrimitiveEncoderTest { val enabled: Boolean = true, val pi: Double = 3.14, val count: Long = 3, + val port: Int = 8080, val greeting: String = "hello", val enumGreeting: Greeting = Greeting.Hello, @TomlLiteral @@ -30,6 +31,7 @@ class PrimitiveEncoderTest { enabled = true pi = 3.14 count = 3 + port = 8080 greeting = "hello" enumGreeting = "hello" path = 'C:\some\path\'