Skip to content

Commit

Permalink
Initial test support for Map decoding
Browse files Browse the repository at this point in the history
### What's done:
- Tests
- MapDecoder
  • Loading branch information
orchestr7 committed Dec 3, 2023
1 parent 42a8daa commit ecffbd9
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package com.akuleshov7.ktoml.decoders

import TomlMapDecoder

Check failure

Code scanning / ktlint

[FILE_UNORDERED_IMPORTS] imports should be ordered alphabetically and shouldn't be separated by newlines: import com.akuleshov7.ktoml.TomlConfig... Error

[FILE_UNORDERED_IMPORTS] imports should be ordered alphabetically and shouldn't be separated by newlines: import com.akuleshov7.ktoml.TomlConfig...

Check failure

Code scanning / ktlint

[FILE_UNORDERED_IMPORTS] imports should be ordered alphabetically and shouldn't be separated by newlines: import com.akuleshov7.ktoml.TomlConfig... Error

[FILE_UNORDERED_IMPORTS] imports should be ordered alphabetically and shouldn't be separated by newlines: import com.akuleshov7.ktoml.TomlConfig...
import com.akuleshov7.ktoml.TomlConfig
import com.akuleshov7.ktoml.TomlInputConfig
import com.akuleshov7.ktoml.exceptions.*
Expand All @@ -10,6 +11,7 @@ import com.akuleshov7.ktoml.tree.nodes.pairs.values.TomlNull
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.StructureKind
import kotlinx.serialization.descriptors.elementNames
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
Expand Down Expand Up @@ -162,8 +164,8 @@ public class TomlMainDecoder(
descriptor: SerialDescriptor
) {
if (currentNode is TomlKeyValue &&
currentNode.value is TomlNull &&
!descriptor.getElementDescriptor(currentProperty).isNullable
currentNode.value is TomlNull &&

Check failure

Code scanning / ktlint

[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 16 but was 12 Error

[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 16 but was 12

Check failure

Code scanning / ktlint

[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 16 but was 12 Error

[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 16 but was 12
!descriptor.getElementDescriptor(currentProperty).isNullable

Check failure

Code scanning / ktlint

[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 16 but was 12 Error

[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 16 but was 12

Check failure

Code scanning / ktlint

[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 16 but was 12 Error

[WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 16 but was 12
) {
throw NullValueException(
descriptor.getElementName(currentProperty),
Expand All @@ -174,9 +176,14 @@ public class TomlMainDecoder(

/**
* Actually this method is not needed as serialization lib should do everything for us, but let's
* fail-fast in the very beginning if the structure is inconsistent and required properties are missing
* fail-fast in the very beginning if the structure is inconsistent and required properties are missing.
* Also we will throw much more clear ktoml-like exception MissingRequiredPropertyException
*/
private fun checkMissingRequiredProperties(children: MutableList<TomlNode>?, descriptor: SerialDescriptor) {
// the only case when we are not able to check required properties is when our descriptor type is a Map with unnamed properties:
// in this case we will just ignore this check and will put all values that we have in the table to the map
if (descriptor.kind == StructureKind.MAP) return

Check failure

Code scanning / ktlint

[NO_BRACES_IN_CONDITIONALS_AND_LOOPS] in if, else, when, for, do, and while statements braces should be used. Exception: single line ternary operator statement: IF Error

[NO_BRACES_IN_CONDITIONALS_AND_LOOPS] in if, else, when, for, do, and while statements braces should be used. Exception: single line ternary operator statement: IF

Check failure

Code scanning / ktlint

[NO_BRACES_IN_CONDITIONALS_AND_LOOPS] in if, else, when, for, do, and while statements braces should be used. Exception: single line ternary operator statement: IF Error

[NO_BRACES_IN_CONDITIONALS_AND_LOOPS] in if, else, when, for, do, and while statements braces should be used. Exception: single line ternary operator statement: IF

val propertyNames = children?.map {
it.name
} ?: emptyList()
Expand All @@ -200,20 +207,20 @@ public class TomlMainDecoder(
* A hack that comes from a compiler plugin to process Inline (value) classes
*/
override fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder =
iterateOverStructure(inlineDescriptor, true)
iterateOverTomlStructure(inlineDescriptor, true)

/**
* this method does all the iteration logic for processing code structures and collections
* treat it as an !entry point! and the orchestrator of the decoding
*/
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
iterateOverStructure(descriptor, false)
iterateOverTomlStructure(descriptor, false)

/**
* Entry Point into the logic, core logic of the structure traversal and linking the data from TOML AST
* to the descriptor and vica-versa. Basically this logic is used to iterate through data structures and do processing.
*/
private fun iterateOverStructure(descriptor: SerialDescriptor, inlineFunc: Boolean): TomlAbstractDecoder =
private fun iterateOverTomlStructure(descriptor: SerialDescriptor, inlineFunc: Boolean): TomlAbstractDecoder =
if (rootNode is TomlFile) {
checkMissingRequiredProperties(rootNode.children, descriptor)
val firstFileChild = getFirstChild(rootNode)
Expand All @@ -234,13 +241,24 @@ public class TomlMainDecoder(
is TomlKeyValueArray -> TomlArrayDecoder(nextProcessingNode, config)
is TomlKeyValuePrimitive, is TomlStubEmptyNode -> TomlMainDecoder(nextProcessingNode, config)
is TomlTable -> {
val firstTableChild = nextProcessingNode.getFirstChild() ?: throw InternalDecodingException(
"Decoding process has failed due to invalid structure of parsed AST tree: missing children" +
" in a table <${nextProcessingNode.fullTableKey}>"
)
checkMissingRequiredProperties(firstTableChild.getNeighbourNodes(), descriptor)
TomlMainDecoder(firstTableChild, config)
when (descriptor.kind) {
// This logic is a special case when user would like to parse key-values from a table to a map.
// It can be useful, when the user does not know key names of TOML key-value pairs, for example:
// if parsing
StructureKind.MAP -> TomlMapDecoder(nextProcessingNode)

else -> {
val firstTableChild = nextProcessingNode.getFirstChild() ?: throw InternalDecodingException(
"Decoding process has failed due to invalid structure of parsed AST tree: missing children" +
" in a table <${nextProcessingNode.fullTableKey}>"
)

checkMissingRequiredProperties(firstTableChild.getNeighbourNodes(), descriptor)
TomlMainDecoder(firstTableChild, config)
}
}
}

else -> throw InternalDecodingException(
"Incorrect decoding state in the beginStructure()" +
" with $nextProcessingNode ($nextProcessingNode)[${nextProcessingNode.name}]"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import com.akuleshov7.ktoml.decoders.TomlAbstractDecoder

Check failure

Code scanning / ktlint

[UNUSED_IMPORT] unused imports should be removed: com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive - unused import Error

[UNUSED_IMPORT] unused imports should be removed: com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive - unused import

Check failure

Code scanning / ktlint

[PACKAGE_NAME_MISSING] no package name declared in a file: TomlMapDecoder.kt Error

[PACKAGE_NAME_MISSING] no package name declared in a file: TomlMapDecoder.kt

Check failure

Code scanning / ktlint

[UNUSED_IMPORT] unused imports should be removed: com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive - unused import Error

[UNUSED_IMPORT] unused imports should be removed: com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive - unused import

Check failure

Code scanning / ktlint

[PACKAGE_NAME_MISSING] no package name declared in a file: TomlMapDecoder.kt Error

[PACKAGE_NAME_MISSING] no package name declared in a file: TomlMapDecoder.kt
import com.akuleshov7.ktoml.tree.nodes.TomlKeyValue
import com.akuleshov7.ktoml.tree.nodes.TomlKeyValuePrimitive
import com.akuleshov7.ktoml.tree.nodes.TomlTable
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule

/**
* @property rootNode
*/
@ExperimentalSerializationApi
public class TomlMapDecoder(
private val rootNode: TomlTable,
) : TomlAbstractDecoder() {
override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeValue(): Any = rootNode.children.map {
when(it) {

Check failure

Code scanning / ktlint

[WRONG_WHITESPACE] incorrect usage of whitespaces for code separation: keyword 'when' should be separated from '(' with a whitespace Error

[WRONG_WHITESPACE] incorrect usage of whitespaces for code separation: keyword 'when' should be separated from '(' with a whitespace

Check failure

Code scanning / ktlint

[WRONG_WHITESPACE] incorrect usage of whitespaces for code separation: keyword 'when' should be separated from '(' with a whitespace Error

[WRONG_WHITESPACE] incorrect usage of whitespaces for code separation: keyword 'when' should be separated from '(' with a whitespace
is TomlKeyValue -> it.key to it.value
else -> throw Exception()
}
}
override fun decodeElementIndex(descriptor: SerialDescriptor): Int = 0

override fun decodeKeyValue(): TomlKeyValue = throw NotImplementedError("")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.akuleshov7.ktoml.decoders

import com.akuleshov7.ktoml.Toml

import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.Serializable
import kotlin.test.Test

class PlainMapDecoderTest {
@Serializable
private data class TestDataMap(
val text: String,
val map: Map<String, String>,
val number: Int,
)

@Test
fun testMapDecoderPositiveCase() {
var data = """
text = "Test"
number = 15
[map]
a = 1
b = 1
c = 1
number = 31
""".trimIndent()

Toml.decodeFromString<TestDataMap>(data)

data = """
map = { a = 1, b = 2, c = 3 }
text = "Test"
number = 15
""".trimIndent()

Toml.decodeFromString<TestDataMap>(data)
}

@Test
fun testMapDecoderNegativeCases() {
var data = """
a = 1
b = 1
c = 1
text = "Test"
number = 15
""".trimIndent()

Toml.decodeFromString<TestDataMap>(data)

data = """
[map]
[map.a]
b = 1
[map.b]
c = 1
text = "Test"
number = 15
""".trimIndent()

Toml.decodeFromString<TestDataMap>(data)

data = """
text = "Test"
number = 15
""".trimIndent()

Toml.decodeFromString<TestDataMap>(data)
}

@Test
fun testSimpleMapDecoder() {
val data = TestDataMap(text = "text value", number = 7321, map = mapOf("a" to "b", "c" to "d"))
val encoded = Toml.encodeToString(data)
val decoded: TestDataMap = Toml.decodeFromString(encoded) // throws MissingRequiredPropertyException
}
}

0 comments on commit ecffbd9

Please sign in to comment.