Skip to content

Commit

Permalink
feat(#9): implement file reading as a given type
Browse files Browse the repository at this point in the history
  • Loading branch information
LVMVRQUXL committed Dec 27, 2021
1 parent 0d21bf6 commit 5cb00f6
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 7 deletions.
10 changes: 9 additions & 1 deletion src/main/kotlin/io/github/kotools/csv/Exception.kt
@@ -1,15 +1,20 @@
package io.github.kotools.csv

import kotlin.reflect.KClass
import kotlin.reflect.KProperty

@Throws(FileNotFoundException::class)
internal fun fileNotFoundException(path: String): Nothing =
throw FileNotFoundException(path)

@Throws(InvalidPropertyException::class)
internal fun <T> invalidPropertyException(property: KProperty<T>): Nothing =
internal fun <T : Any> invalidPropertyException(property: KProperty<T>): Nothing =
throw InvalidPropertyException(property.name)

@Throws(InvalidTypeException::class)
internal fun <T : Any> invalidTypeException(type: KClass<T>): Nothing =
throw InvalidTypeException(type)

public sealed class CsvConfigurationException(message: String) :
IllegalArgumentException("$message!")

Expand All @@ -18,3 +23,6 @@ private class FileNotFoundException(path: String) :

private class InvalidPropertyException(property: String) :
CsvConfigurationException("The property `$property` is invalid")

private class InvalidTypeException(type: KClass<*>) :
CsvConfigurationException("The type `$type` is invalid")
68 changes: 62 additions & 6 deletions src/main/kotlin/io/github/kotools/csv/Reader.kt
Expand Up @@ -22,14 +22,27 @@ public suspend fun csvReader(configuration: Reader.() -> Unit):
withContext(IO) { delegateCsvReader(configuration) }

/**
* Returns the file's records **asynchronously** according to the given
* [configuration], or throws a [CsvConfigurationException] when the
* [configuration] is invalid or when the targeted file doesn't exist.
* Returns the file's records as a given list of type [T] according to the given
* [configuration].
* This method throws a [CsvConfigurationException] when the type [T] is not a
* public or internal data class, or when the [configuration] is invalid, or
* when the targeted file doesn't exist.
*/
public infix fun CoroutineScope.csvReaderAsync(
@Suppress("DEPRECATION")
@Throws(CsvConfigurationException::class)
public suspend inline fun <reified T : Any> csvReaderAs(
noinline configuration: Reader.() -> Unit
): List<T> = csvReaderAs(T::class, configuration)

@Deprecated(
message = "Use the `csvReaderAs<T> {}` method instead.",
ReplaceWith("csvReaderAs<T> {}")
)
@Throws(CsvConfigurationException::class)
public suspend fun <T : Any> csvReaderAs(
type: KClass<T>,
configuration: Reader.() -> Unit
): Deferred<List<Map<String, String>>> =
async(IO) { delegateCsvReader(configuration) }
): List<T> = withContext(IO) { delegateCsvReaderAs(type, configuration) }

/**
* Returns the file's records according to the given [configuration], or returns
Expand Down Expand Up @@ -61,6 +74,40 @@ public suspend fun <T : Any> csvReaderOrNullAs(
configuration: Reader.() -> Unit
): List<T>? = withContext(IO) { delegateCsvReaderOrNullAs(type, configuration) }

/**
* Returns the file's records **asynchronously** according to the given
* [configuration], or throws a [CsvConfigurationException] when the
* [configuration] is invalid or when the targeted file doesn't exist.
*/
@Throws(CsvConfigurationException::class)
public infix fun CoroutineScope.csvReaderAsync(
configuration: Reader.() -> Unit
): Deferred<List<Map<String, String>>> =
async(IO) { delegateCsvReader(configuration) }

/**
* Returns the file's records as a given list of type [T] **asynchronously**
* according to the given [configuration].
* This method throws a [CsvConfigurationException] when the type [T] is not a
* public or internal data class, or when the [configuration] is invalid, or
* when the targeted file doesn't exist.
*/
@Suppress("DEPRECATION")
@Throws(CsvConfigurationException::class)
public inline infix fun <reified T : Any> CoroutineScope.csvReaderAsAsync(
noinline configuration: Reader.() -> Unit
): Deferred<List<T>> = csvReaderAsAsync(T::class, configuration)

@Deprecated(
message = "Use the `csvReaderAsAsync<T> {}` method instead.",
ReplaceWith("csvReaderAsAsync<T> {}")
)
@Throws(CsvConfigurationException::class)
public fun <T : Any> CoroutineScope.csvReaderAsAsync(
type: KClass<T>,
configuration: Reader.() -> Unit
): Deferred<List<T>> = async(IO) { delegateCsvReaderAs(type, configuration) }

/**
* Returns the file's records as a given list of type [T] **asynchronously**
* according to the given [configuration].
Expand Down Expand Up @@ -101,6 +148,15 @@ private inline fun delegateCsvReader(configuration: Reader.() -> Unit):
return reader() ?: fileNotFoundException("${reader.folder}${reader.file}")
}

@Throws(CsvConfigurationException::class)
private inline fun <T : Any> delegateCsvReaderAs(
type: KClass<T>,
configuration: Reader.() -> Unit
): List<T> {
val t: DataType<T> = type.toDataTypeOrNull() ?: invalidTypeException(type)
return delegateCsvReader(configuration).mapNotNull(t::createTypeOrNullFrom)
}

private inline fun delegateCsvReaderOrNull(configuration: Reader.() -> Unit):
List<Map<String, String>>? {
val reader: Reader = Reader create configuration
Expand Down
39 changes: 39 additions & 0 deletions src/test/kotlin/io/github/kotools/csv/ReaderTest.kt
Expand Up @@ -59,6 +59,45 @@ class ReaderTest {
}
}

@Nested
inner class CsvReaderAs {
@Test
fun `should pass`(): Unit =
assertIsValid { csvReaderAs<Example>(validConfiguration) }

@Test
fun `should fail with blank file name`(): Unit = runBlocking {
assertFailsWith<CsvConfigurationException> {
csvReaderAs<Example> {}
}
}

@Test
fun `should fail with invalid type`(): Unit = runBlocking {
assertFailsWith<CsvConfigurationException> {
csvReaderAs<InvalidExample>(validConfiguration)
}
assertFailsWith<CsvConfigurationException> {
csvReaderAs<PrivateExample>(validConfiguration)
}
}

@Test
fun `should fail with unknown file`(): Unit = runBlocking {
assertFailsWith<CsvConfigurationException> {
csvReaderAs<Example> { file = "unknown" }
}
}
}

@Nested
inner class CsvReaderAsAsync {
@Test
fun `should pass`(): Unit = awaitAndAssertIsValid {
csvReaderAsAsync<Example>(validConfiguration)
}
}

@Nested
inner class CsvReaderAsync {
@Test
Expand Down

0 comments on commit 5cb00f6

Please sign in to comment.