Skip to content

Commit

Permalink
chore(model): Make also readValueOrNull() throw on multiple documents
Browse files Browse the repository at this point in the history
Align with `readValue()` to throw in case of multiple documents per
file. `null` is only return for empty files.

Signed-off-by: Sebastian Schuberth <sebastian@doubleopen.org>
  • Loading branch information
sschuberth committed Jun 11, 2024
1 parent 64fccd8 commit dd81d17
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 12 deletions.
22 changes: 10 additions & 12 deletions model/src/main/kotlin/FileFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ package org.ossreviewtoolkit.model

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.convertValue
import com.fasterxml.jackson.module.kotlin.readValues

import java.io.File
Expand Down Expand Up @@ -98,27 +97,26 @@ fun File.readTree(): JsonNode = mapper().readTree(this)
* Use the Jackson mapper returned from [File.mapper] to read a single object of type [T] from this file. Throw an
* [IOException] if not exactly one value is contained in the file, e.g. in case of multiple YAML documents per file.
*/
inline fun <reified T : Any> File.readValue(): T {
inline fun <reified T : Any> File.readValue(): T =
readValueOrNull() ?: throw IOException("No object found in file '$this'.")

/**
* Use the Jackson mapper returned from [File.mapper] to read an object of type [T] from this file, or return null if
* the file has no content. Throw an [IOException] if not exactly one value is contained in the file, e.g. in case of
* multiple YAML documents per file.
*/
inline fun <reified T : Any> File.readValueOrNull(): T? {
val mapper = mapper()
val parser = mapper.factory.createParser(this)

val values = mapper.readValues<T>(parser).readAll().also {
if (it.isEmpty()) throw IOException("No object found in file '$this'.")
if (it.isEmpty()) return null
if (it.size > 1) throw IOException("Multiple top-level objects found in file '$this'.")
}

return values.first()
}

/**
* Use the Jackson mapper returned from [File.mapper] to read an object of type [T] from this file, or return null if
* the file has no content.
*/
inline fun <reified T : Any> File.readValueOrNull(): T? =
// Parse the file in a two-step process to avoid readValue() throwing an exception on empty files. Also see
// https://github.com/FasterXML/jackson-databind/issues/1406#issuecomment-252676674.
mapper().let { it.convertValue(it.readTree(this)) }

/**
* Use the Jackson mapper returned from [File.mapper] to read an object of type [T] from this file, or return the
* [default] value if the file has no content.
Expand Down
22 changes: 22 additions & 0 deletions model/src/test/kotlin/FileFormatTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,27 @@ class FileFormatTest : WordSpec({
file.readValueOrNull()
}
}

"refuse to read multiple documents per file" {
val file = tempfile(null, ".yml").apply {
@Suppress("MaxLineLength")
writeText(
"""
---
id: "Maven:dom4j:dom4j:1.6.1"
source_artifact_url: "https://repo.maven.apache.org/maven2/dom4j/dom4j/1.6.1/dom4j-1.6.1-sources.jar"
---
id: "Maven:dom4j:dom4j:1.6.1"
source_artifact_url: "<INTERNAL_ARTIFACTORY>/dom4j-1.6.1-sources.jar"
""".trimIndent()
)
}

shouldThrowWithMessage<IOException>(
"Multiple top-level objects found in file '$file'."
) {
file.readValueOrNull()
}
}
}
})

0 comments on commit dd81d17

Please sign in to comment.