Password.kt

package com.example.realworldkotlinspringbootjdbc.domain.user

import arrow.core.Option
import arrow.core.Validated
import arrow.core.ValidatedNel
import arrow.core.invalid
import arrow.core.invalidNel
import arrow.core.valid
import arrow.core.zip
import arrow.typeclasses.Semigroup
import com.example.realworldkotlinspringbootjdbc.util.MyError

interface Password {
    val value: String

    /**
     * 実装
     */
    private data class PasswordImpl(override val value: String) : Password

    /**
     * Factory メソッド
     */
    companion object {
        /**
         * Validation 無し
         */
        fun newWithoutValidation(password: String): Password = PasswordImpl(password)

        /**
         * Validation 有り
         */
        fun new(password: String?): ValidatedNel<ValidationError, Password> =
            when (val result = ValidationError.Required.check(password)) {
                is Validated.Invalid -> result.value.invalidNel()
                is Validated.Valid -> {
                    val existedPassword = result.value
                    ValidationError.TooShort.check(existedPassword).zip(
                        Semigroup.nonEmptyList(),
                        ValidationError.TooLong.check(existedPassword)
                    ) { _, _ -> PasswordImpl(existedPassword) }
                }
            }

        /**
         * Login用 パスワード
         */
        fun newForLogin(password: String?): ValidatedNel<ValidationError, Password> =
            when (val result = ValidationError.Required.check(password)) {
                is Validated.Invalid -> result.value.invalidNel()
                is Validated.Valid -> PasswordImpl(result.value).valid()
            }
    }

    /**
     * ドメインルール
     */
    sealed interface ValidationError : MyError.ValidationError {
        override val key: String get() = Password::class.simpleName.toString()
        /**
         * Nullは駄目
         */
        object Required : ValidationError {
            override val message: String get() = "パスワードを入力してください。"
            fun check(password: String?): Validated<Required, String> =
                Option.fromNullable(password).fold(
                    { Required.invalid() },
                    { it.valid() }
                )
        }

        /**
         * 短すぎては駄目
         */
        data class TooShort(val password: String) : ValidationError {
            companion object {
                private const val minimum: Int = 8
                fun check(password: String): ValidatedNel<ValidationError, Unit> =
                    if (minimum <= password.length) { Unit.valid() } else { TooShort(password).invalidNel() }
            }
            override val message: String get() = "パスワードは${minimum}文字以上にしてください。"
        }

        /**
         * 長すぎては駄目
         */
        data class TooLong(val password: String) : ValidationError {
            companion object {
                private const val maximum: Int = 32
                fun check(password: String): ValidatedNel<ValidationError, Unit> =
                    if (password.length <= maximum) { Unit.valid() } else { TooLong(password).invalidNel() }
            }
            override val message: String get() = "パスワードは${maximum}文字以下にしてください。"
        }
    }
}