DeleteCommentUseCase.kt

package com.example.realworldkotlinspringbootjdbc.usecase.comment

import arrow.core.Either
import arrow.core.Either.Left
import arrow.core.Either.Right
import arrow.core.left
import com.example.realworldkotlinspringbootjdbc.domain.CommentRepository
import com.example.realworldkotlinspringbootjdbc.domain.RegisteredUser
import com.example.realworldkotlinspringbootjdbc.domain.article.Slug
import com.example.realworldkotlinspringbootjdbc.domain.comment.CommentId
import com.example.realworldkotlinspringbootjdbc.domain.user.UserId
import com.example.realworldkotlinspringbootjdbc.util.MyError
import org.springframework.stereotype.Service

/**
 * 作成済記事のコメントの削除
 *
 * - コメントの作成者じゃないと削除できない
 */
interface DeleteCommentUseCase {
    /**
     * 実行
     *
     * @param slug Slug
     * @param commentId コメント ID
     * @param currentUser リクエストしたユーザー(コメントの作成者である必要がある)
     * @return エラー or 削除成功
     */
    fun execute(slug: String?, commentId: Int?, currentUser: RegisteredUser): Either<Error, Unit> =
        throw NotImplementedError()

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

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

        data class NotFoundCommentByCommentId(override val cause: MyError, val commentId: CommentId) :
            Error,
            MyError.MyErrorWithMyError

        data class NotAuthorizedDeleteComment(override val cause: MyError, val currentUserId: UserId) :
            Error,
            MyError.MyErrorWithMyError
    }
}

@Service
class DeleteCommentUseCaseImpl(
    val commentRepository: CommentRepository
) : DeleteCommentUseCase {
    override fun execute(
        slug: String?,
        commentId: Int?,
        currentUser: RegisteredUser
    ): Either<DeleteCommentUseCase.Error, Unit> {
        /**
         * Slug のバリデーション
         * Invalid -> 早期リターン
         */
        val validatedSlug = Slug.new(slug).fold(
            { return DeleteCommentUseCase.Error.InvalidSlug(it).left() },
            { it }
        )

        /**
         * CommentId のバリデーション
         * Invalid -> 早期リターン
         */
        val validateCommentId = CommentId.new(commentId).fold(
            { return DeleteCommentUseCase.Error.InvalidCommentId(it).left() },
            { it }
        )

        /**
         * コメント削除
         */
        return when (
            val deleteResult =
                commentRepository.delete(validatedSlug, validateCommentId, currentUser.userId)
        ) {
            /**
             * コメント削除 失敗
             */
            is Left -> when (val error = deleteResult.value) {
                /**
                 * 原因: Slug に該当する記事が見つからなかった
                 */
                is CommentRepository.DeleteError.NotFoundArticleBySlug -> DeleteCommentUseCase.Error.NotFoundArticleBySlug(
                    error,
                    validatedSlug
                ).left()
                /**
                 * 原因: CommentId に該当するコメントが見つからなかった
                 */
                is CommentRepository.DeleteError.NotFoundCommentByCommentId -> DeleteCommentUseCase.Error.NotFoundCommentByCommentId(
                    error,
                    validateCommentId
                ).left()
                /**
                 * 原因: 認可されていない(削除しようとしたが実行ユーザー(CurrentUserId)のものじゃなかった)
                 */
                is CommentRepository.DeleteError.NotAuthorizedDeleteComment -> DeleteCommentUseCase.Error.NotAuthorizedDeleteComment(
                    error,
                    currentUser.userId
                ).left()
            }
            /**
             * コメント削除 成功
             */
            is Right -> deleteResult
        }
    }
}