FavoriteUseCase.kt

package com.example.realworldkotlinspringbootjdbc.usecase.favorite

import arrow.core.Either
import arrow.core.Either.Left
import arrow.core.Either.Right
import arrow.core.getOrHandle
import arrow.core.left
import arrow.core.right
import arrow.core.toOption
import com.example.realworldkotlinspringbootjdbc.domain.ArticleRepository
import com.example.realworldkotlinspringbootjdbc.domain.ProfileRepository
import com.example.realworldkotlinspringbootjdbc.domain.RegisteredUser
import com.example.realworldkotlinspringbootjdbc.domain.article.Slug
import com.example.realworldkotlinspringbootjdbc.usecase.shared_model.CreatedArticleWithAuthor
import com.example.realworldkotlinspringbootjdbc.util.MyError
import org.springframework.stereotype.Service

interface FavoriteUseCase {
    fun execute(slug: String, currentUser: RegisteredUser): Either<Error, CreatedArticleWithAuthor> =
        throw NotImplementedError()

    sealed interface Error : MyError {
        data class InvalidSlug(override val errors: List<MyError.ValidationError>) : Error, MyError.ValidationErrors
        data class NotFound(override val cause: MyError) :
            Error,
            MyError.MyErrorWithMyError
    }
}

@Service
class FavoriteUseCaseImpl(
    val articleRepository: ArticleRepository,
    val profileRepository: ProfileRepository
) : FavoriteUseCase {
    override fun execute(
        slug: String,
        currentUser: RegisteredUser
    ): Either<FavoriteUseCase.Error, CreatedArticleWithAuthor> {
        /**
         * Slug のバリデーション
         * Invalid -> 早期リターン
         */
        val validatedSlug = Slug.new(slug).fold(
            { return FavoriteUseCase.Error.InvalidSlug(it).left() },
            { it }
        )

        val favoriteResult = when (val favoriteResult = articleRepository.favorite(validatedSlug, currentUser.userId)) {
            /**
             * お気に入り追加 失敗
             */
            is Left -> when (val favoriteError = favoriteResult.value) {
                /**
                 * 原因: 作成済記事が見つからなかった
                 */
                is ArticleRepository.FavoriteError.NotFoundCreatedArticleBySlug -> return FavoriteUseCase.Error.NotFound(
                    favoriteError
                ).left()
            }
            /**
             * お気に入り追加 成功
             */
            is Right -> favoriteResult
        }

        /**
         * author を取得
         * 必ず 1 件見つかるため、first を指定している
         */
        val author =
            profileRepository.filterByUserIds(setOf(favoriteResult.value.authorId), currentUser.userId.toOption())
                .getOrHandle { throw UnsupportedOperationException("現在この分岐に入ることは無い") }.first()

        /**
         * author を followedUsers から取得
         */
        return CreatedArticleWithAuthor(
            article = favoriteResult.value,
            author = author
        ).right()
    }
}