UpdateUserUseCase.kt

package com.example.realworldkotlinspringbootjdbc.usecase.user_and_authentication

import arrow.core.Either
import arrow.core.Either.Left
import arrow.core.Either.Right
import arrow.core.NonEmptyList
import arrow.core.left
import arrow.core.right
import com.example.realworldkotlinspringbootjdbc.domain.RegisteredUser
import com.example.realworldkotlinspringbootjdbc.domain.UpdatableRegisteredUser
import com.example.realworldkotlinspringbootjdbc.domain.UserRepository
import com.example.realworldkotlinspringbootjdbc.util.MyError
import org.springframework.stereotype.Service

interface UpdateUserUseCase {
    fun execute(
        currentUser: RegisteredUser,
        email: String?,
        username: String?,
        bio: String?,
        image: String?,
    ): Either<Error, RegisteredUser> = throw UnsupportedOperationException()

    sealed interface Error : MyError {
        data class InvalidAttributes(
            override val errors: NonEmptyList<MyError.ValidationError>,
            val currentUser: RegisteredUser,
        ) : Error, MyError.ValidationErrors
        data class AlreadyUsedEmail(
            override val cause: MyError,
            val updatableRegisteredUser: UpdatableRegisteredUser
        ) : Error, MyError.MyErrorWithMyError
        data class AlreadyUsedUsername(
            override val cause: MyError,
            val updatableRegisteredUser: UpdatableRegisteredUser
        ) : Error, MyError.MyErrorWithMyError
        data class NotFoundUser(
            override val cause: MyError,
            val currentUser: RegisteredUser
        ) : Error, MyError.MyErrorWithMyError
    }
}

@Service
class UpdateUserUseCaseImpl(
    val userRepository: UserRepository
) : UpdateUserUseCase {
    override fun execute(
        currentUser: RegisteredUser,
        email: String?,
        username: String?,
        bio: String?,
        image: String?
    ): Either<UpdateUserUseCase.Error, RegisteredUser> {
        /**
         * バリデーション -> 更新可能な登録済みユーザー
         * 失敗 -> 早期return
         */
        val updatableRegisteredUser = UpdatableRegisteredUser.new(currentUser, email, username, bio, image).fold(
            { return UpdateUserUseCase.Error.InvalidAttributes(errors = it, currentUser = currentUser).left() },
            { it }
        )

        return when (val updateResult = userRepository.update(updatableRegisteredUser)) {
            /**
             * 更新: 失敗
             */
            is Left -> when (val error = updateResult.value) {
                /**
                 * 原因: Emailは誰かが使っていた
                 */
                is UserRepository.UpdateError.AlreadyRegisteredEmail -> UpdateUserUseCase.Error.AlreadyUsedEmail(
                    cause = error,
                    updatableRegisteredUser = updatableRegisteredUser,
                ).left()
                /**
                 * 原因: Usernameは誰かが使っていた
                 */
                is UserRepository.UpdateError.AlreadyRegisteredUsername -> UpdateUserUseCase.Error.AlreadyUsedUsername(
                    cause = error,
                    updatableRegisteredUser = updatableRegisteredUser
                ).left()
                /**
                 * 原因: ユーザーが見つからなかった(ほぼ考えられない)
                 */
                is UserRepository.UpdateError.NotFound -> UpdateUserUseCase.Error.NotFoundUser(
                    cause = error,
                    currentUser = currentUser
                ).left()
            }
            /**
             * 更新: 成功
             */
            is Right -> RegisteredUser.newWithoutValidation(
                userId = updatableRegisteredUser.userId,
                email = updatableRegisteredUser.email,
                username = updatableRegisteredUser.username,
                bio = updatableRegisteredUser.bio,
                image = updatableRegisteredUser.image,
            ).right()
        }
    }
}