Skip to content
This repository has been archived by the owner on Feb 8, 2024. It is now read-only.

Typeful API in kotools.csv #14

Closed
7 tasks
LVMVRQUXL opened this issue Nov 3, 2022 · 5 comments
Closed
7 tasks

Typeful API in kotools.csv #14

LVMVRQUXL opened this issue Nov 3, 2022 · 5 comments
Assignees
Labels
csv Related to Kotools CSV. enhancement New feature or request. jvm Should work on JVM platform. wontfix This will not be worked on.

Comments

@LVMVRQUXL
Copy link
Contributor

LVMVRQUXL commented Nov 3, 2022

Description

Create a package kotools.csv containing all declarations of the package io.github.kotools.csv.
Then, deprecate all declarations of the old package.

Declarations in kotools.csv should use explicit types from Kotools Types for having a typeful design.

Also, the file property should be required in this new API for avoiding runtime checks in favor of compile-time checks.
Here is an exemple using the old API:

data class Person(val name: NotBlankString, val age: StrictlyPositiveInt, val isAdmin: Boolean = false)

suspend fun main() {
    csvWriter<Person>(file = "people".toNotBlankString()) {
        records { +Person("Nobody".toNotBlankString(), 25.toStrictlyPositiveInt()) }
    }
    val people: List<Person> = csvReader(file = "people".toNotBlankString())
    println(people)
}

Checklist

  • Implement a basic CSV reader.
  • Implement a reader that returns a custom type.
  • Deprecate the old reader.
  • Implement a basic CSV writer.
  • Implement a writer that takes a custom type.
  • Deprecate the old writer.
  • Update Work in progress section in changelog.
@LVMVRQUXL LVMVRQUXL added enhancement New feature or request. jvm Should work on JVM platform. csv Related to Kotools CSV. labels Nov 3, 2022
@LVMVRQUXL LVMVRQUXL changed the title Package kotools.csv Typeful API in kotools.csv Nov 3, 2022
This was referenced Nov 3, 2022
@LVMVRQUXL LVMVRQUXL added this to the CSV v2.3.0 milestone Nov 3, 2022
@LVMVRQUXL LVMVRQUXL assigned LVMVRQUXL and unassigned LVMVRQUXL Nov 4, 2022
@LVMVRQUXL
Copy link
Contributor Author

Here is an idea for the new reader API:

internal data class Record(val values: NotEmptyList<NotBlankString?>)

internal sealed interface CsvReaderResult {
    class Success(val records: NotEmptyList<Record>) : CsvReaderResult

    sealed class Error(message: NotBlankString) : CsvReaderResult,
        IllegalStateException(message.value) {
        object BlankFileName : Error(
            "Given file name is blank.".toNotBlankString()
        )

        class FileNotFound(file: NotBlankString) : Error(
            "Given file $file doesn't exist.".toNotBlankString()
        )

        class RecordsNotFoundInFile(file: NotBlankString) : Error(
            "The file $file doesn't have records.".toNotBlankString()
        )
    }
}

/**
 * Reads a CSV [file] according to the [configuration] and returns the records
 * wrapped in a [CsvReaderResult.Success] object, or returns a
 * [CsvReaderResult.Error] if the process failed.
 */
internal fun readCsv(file: String, configuration: () -> Unit): CsvReaderResult {
    println("> Call readCsv(file = $file, configuration = $configuration)")
    val fileName: NotBlankString = file.toNotBlankStringOrNull()
        ?: return CsvReaderResult.Error.BlankFileName
    val records: NotEmptyList<Record> = notEmptyListOf(
        Record(notEmptyListOf(null, NotBlankString("a"), NotBlankString("b"))),
        Record(notEmptyListOf(NotBlankString("c"), null, NotBlankString("d"))),
        Record(notEmptyListOf(NotBlankString("e"), NotBlankString("f"), null))
    )
    return CsvReaderResult.Success(records)
}

internal fun main(): Unit =
    when (val result: CsvReaderResult = readCsv(" ") {}) {
        is CsvReaderResult.Success -> println(result.records)
        is CsvReaderResult.Error -> throw result
    }

@LVMVRQUXL
Copy link
Contributor Author

LVMVRQUXL commented Nov 5, 2022

The issue of the last example is that unwrapping the CsvReaderResult type could generate boilerplate code...
A solution could be to return an Either<CsvReaderError, NotEmptyList<Record>> type instead, where the Either type and its operations are provided by Arrow Core.

Also, the API should use explicit types only for reducing the amount of runtime checks: the file parameter should be a NotBlankString.

@LVMVRQUXL
Copy link
Contributor Author

The issue of the last example is that unwrapping the CsvReaderResult type could generate boilerplate code... A solution could be to return an Either<CsvReaderError, NotEmptyList<Record>> type instead, where the Either type and its operations are provided by Arrow Core.

Also, the API should use explicit types only for reducing the amount of runtime checks: the file parameter should be a NotBlankString.

We will not use the Either type provided by Arrow Core. Instead, we will introduce a new CsvReaderResult type like proposed in the previous example.

@LVMVRQUXL LVMVRQUXL self-assigned this Nov 16, 2022
This was referenced Nov 16, 2022
@LVMVRQUXL
Copy link
Contributor Author

Will be implemented by #45 and #46.

@LVMVRQUXL LVMVRQUXL added the wontfix This will not be worked on. label Nov 16, 2022
@LVMVRQUXL
Copy link
Contributor Author

Reading a CSV file is an IO operation that should run inside a coroutine.
Here's a new example of the API:

import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotools.types.string.NotBlankString
import kotools.types.string.notBlankStringOrThrow

/**
 * Returns the [records][CsvReaderResult.Success.records] in the CSV file
 * matching this path, or returns a [CsvReaderResult.Exception.FileNotFound] if
 * no file matches this path, or returns a [CsvReaderResult.Exception.EmptyFile]
 * if the file matching this path is empty.
 */
internal suspend fun CsvPathResult.Success.read(): CsvReaderResult =
    withContext(CoroutineName("CsvReader") + Dispatchers.IO) { TODO() }

internal sealed interface CsvReaderResult {
    class Success(val records: List<Map<String, String>>) : CsvReaderResult

    sealed class Exception(reason: NotBlankString) :
        IllegalStateException("The file ${reason.value}."),
        CsvReaderResult {
        class EmptyFile(file: NotBlankString) : Exception(
            notBlankStringOrThrow("$file is empty")
        )

        class FileNotFound(file: NotBlankString) : Exception(
            notBlankStringOrThrow("$file doesn't exist")
        )
    }
}

@LVMVRQUXL LVMVRQUXL removed this from the CSV v2.3.0 milestone Dec 22, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
csv Related to Kotools CSV. enhancement New feature or request. jvm Should work on JVM platform. wontfix This will not be worked on.
Projects
None yet
Development

No branches or pull requests

1 participant