Tag.kt

package com.example.realworldkotlinspringbootjdbc.domain.article

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

interface Tag {
    val value: String

    /**
     * 実装
     */
    private data class ValidatedTag(override val value: String) : Tag
    private data class TagWithoutValidation(override val value: String) : Tag

    /**
     * Factory メソッド
     */
    companion object {
        /**
         * Validation 無し
         */
        fun newWithoutValidation(tag: String): Tag = TagWithoutValidation(tag)

        /**
         * Validation 有り
         */
        fun new(tag: String?): ValidatedNel<ValidationError, Tag> =
            when (val result = ValidationError.Required.check(tag)) {
                is Invalid -> result.value.invalidNel()
                is Valid -> {
                    ValidationError.RequiredNotBlank.check(result.value).zip(
                        Semigroup.nonEmptyList(),
                        ValidationError.TooLong.check(result.value),
                    ) { _, _ -> ValidatedTag(result.value) }
                }
            }
    }

    /**
     * ドメインルール
     */
    sealed interface ValidationError : MyError.ValidationError {
        override val key: String get() = Tag::class.simpleName.toString()

        /**
         * Nullは駄目
         */
        object Required : ValidationError {
            override val message: String get() = "tagを入力してください。"
            fun check(tag: String?): Validated<Required, String> =
                Option.fromNullable(tag).fold(
                    { Invalid(Required) },
                    { Valid(it) }
                )
        }

        /**
         * 空白のみは駄目
         */
        object RequiredNotBlank : ValidationError {
            override val message: String get() = "tagを入力してください"
            fun check(tag: String): ValidatedNel<RequiredNotBlank, Unit> =
                if (tag.isNotBlank()) {
                    Unit.valid()
                } else {
                    RequiredNotBlank.invalidNel()
                }
        }

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