FeedParameters.kt
package com.example.realworldkotlinspringbootjdbc.domain.article
import arrow.core.Option
import arrow.core.ValidatedNel
import arrow.core.invalid
import arrow.core.invalidNel
import arrow.core.validNel
import arrow.core.zip
import arrow.typeclasses.Semigroup
import com.example.realworldkotlinspringbootjdbc.util.MyError
/**
* フィード用パラメータ
*
* 特定のユーザーがお気に入りの人全員のそれぞれの最新記事一覧用のパラメータ郡
*
* limit: 1度に表示する記事の最大値
* offset: 何個目から記事を表示するか(100個あるうち、offset=10で11個目から等)
*/
interface FeedParameters {
val limit: Int
val offset: Int
/**
* 実装
*/
private data class ValidatedFeedParameters(
override val limit: Int,
override val offset: Int
) : FeedParameters
/**
* Factory メソッド
*/
companion object {
/**
* new
*
* @param limit
* @param offset
* @return バリデーションエラー or フィード用パラメーター
*/
fun new(
limit: String? = null,
offset: String? = null,
): ValidatedNel<ValidationError, FeedParameters> {
val convertToValidatedLimit: (String?) -> ValidatedNel<ValidationError, Int> = { input ->
Option.fromNullable(input).fold(
{ ValidationError.LimitError.DEFAULT.validNel() },
{ str ->
Option.fromNullable(str.toIntOrNull()).fold(
{ ValidationError.LimitError.FailedConvertToInteger(str).invalidNel() },
{ it.validNel() }
).fold(
{ it.invalid() },
{
when {
it < ValidationError.LimitError.MINIMUM -> ValidationError.LimitError.RequireMinimumOrOver(it).invalidNel()
it > ValidationError.LimitError.MAXIMUM -> ValidationError.LimitError.RequireMaximumOrUnder(it).invalidNel()
else -> it.validNel()
}
}
)
}
)
}
val convertToValidatedOffset: (String?) -> ValidatedNel<ValidationError, Int> = { input ->
Option.fromNullable(input).fold(
{ ValidationError.OffsetError.DEFAULT.validNel() },
{ str ->
Option.fromNullable(str.toIntOrNull()).fold(
{ ValidationError.OffsetError.FailedConvertToInteger(str).invalidNel() },
{ it.validNel() }
).fold(
{ it.invalid() },
{
when {
it < ValidationError.OffsetError.MINIMUM -> ValidationError.OffsetError.RequireMinimumOrOver(it).invalidNel()
else -> it.validNel()
}
}
)
}
)
}
/**
* 引数 -> ValidatedNel<ValidationError, Int>
*/
return convertToValidatedLimit(limit).zip(
Semigroup.nonEmptyList(),
convertToValidatedOffset(offset)
) { a, b ->
ValidatedFeedParameters(
limit = a,
offset = b
)
}
}
}
/**
* ドメインルール
*/
sealed interface ValidationError : MyError.ValidationError {
sealed interface LimitError : ValidationError {
companion object {
const val DEFAULT = 20
const val MINIMUM = 1
const val MAXIMUM = 100
}
override val key: String get() = LimitError::class.simpleName.toString()
data class FailedConvertToInteger(val value: String) : LimitError {
override val message: String get() = "数値に変換できる数字にしてください"
}
data class RequireMinimumOrOver(val value: Int) : LimitError {
override val message: String get() = "${MINIMUM}以上である必要があります"
}
data class RequireMaximumOrUnder(val value: Int) : LimitError {
override val message: String get() = "${MAXIMUM}以下である必要があります"
}
}
sealed interface OffsetError : ValidationError {
companion object {
const val DEFAULT = 0
const val MINIMUM = 0
// const val MAXIMUM = Int.MAX_VALUE
}
override val key: String get() = LimitError::class.simpleName.toString()
data class FailedConvertToInteger(val value: String) : LimitError {
override val message: String get() = "数値に変換できる数字にしてください"
}
data class RequireMinimumOrOver(val value: Int) : LimitError {
override val message: String get() = "${MINIMUM}以上である必要があります"
}
/**
* MAXIMUM = Int.MAX_VALUEなので実装しない
*/
}
}
}