ShowArticleUseCase.kt

package com.example.realworldkotlinspringbootjdbc.usecase.article

import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.getOrHandle
import arrow.core.left
import arrow.core.right
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
import java.lang.UnsupportedOperationException

interface ShowArticleUseCase {
    fun execute(slug: String?, currentUser: Option<RegisteredUser> = None): Either<Error, CreatedArticleWithAuthor> = throw NotImplementedError()
    sealed interface Error : MyError {
        data class ValidationErrors(override val errors: List<MyError.ValidationError>) :
            Error,
            MyError.ValidationErrors

        data class NotFoundArticleBySlug(override val cause: MyError, val slug: Slug) :
            Error,
            MyError.MyErrorWithMyError

        data class NotFoundUser(override val cause: MyError, val user: RegisteredUser) :
            Error,
            MyError.MyErrorWithMyError
    }
}

@Service
class ShowArticleUseCaseImpl(
    val articleRepository: ArticleRepository,
    val profileRepository: ProfileRepository,
) : ShowArticleUseCase {
    override fun execute(
        slug: String?,
        currentUser: Option<RegisteredUser>
    ): Either<ShowArticleUseCase.Error, CreatedArticleWithAuthor> {
        /**
         * String -> Slug
         * 失敗 -> 早期return
         */
        val validatedSlug = Slug.new(slug).fold(
            { return ShowArticleUseCase.Error.ValidationErrors(it.all).left() },
            { it }
        )

        /**
         * 見つかった -> 作成済み記事
         * 見つからなかった -> 早期return
         * 観点となるユーザーが見つからなかった -> 早期return
         */
        val createdArticle = when (currentUser) {
            None -> articleRepository.findBySlug(validatedSlug).getOrHandle {
                return when (it) {
                    is ArticleRepository.FindBySlugError.NotFound -> ShowArticleUseCase.Error.NotFoundArticleBySlug(it, validatedSlug)
                }.left()
            }
            is Some -> articleRepository.findBySlugFromRegisteredUserViewpoint(validatedSlug, currentUser.value.userId).getOrHandle {
                return when (it) {
                    is ArticleRepository.FindBySlugFromRegisteredUserViewpointError.NotFoundArticle -> ShowArticleUseCase.Error.NotFoundArticleBySlug(it, validatedSlug)
                    is ArticleRepository.FindBySlugFromRegisteredUserViewpointError.NotFoundUser -> ShowArticleUseCase.Error.NotFoundUser(it, currentUser.value)
                }.left()
            }
        }

        /**
         * 著者の取得
         * - 必ず見つかるはず
         */
        val author = profileRepository.filterByUserIds(
            userIds = setOf(createdArticle.authorId),
            viewpointUserId = currentUser.map { it.userId }
        ).getOrHandle {
            throw UnsupportedOperationException("この分岐には入らない想定")
        }.first()

        return CreatedArticleWithAuthor(
            article = createdArticle,
            author = author,
        ).right()
    }
}