article banner

Exercise: orThrow

In my everyday practice, I've noticed that I sometimes need a function that in the middle of a multiline expression can be used to throw an exception if a value is null. So I defined it and called it orThrow. This is how its usage looks like:

fun getUser(userId: String) = userRepository .getUser(userId) .orThrow { UserNotFoundException(userId) } .also { log("Found user: $it") } .toUserJson()

Your task is to implement orThrow function. It should throw the exception specified in its lambda argument if the value is null. Otherwise, it should return the value typed as non-nullable.

This problem can either be solved in the below playground or you can clone kotlin-exercises project and solve it locally. In the project, you can find code template for this exercise in functional/scope/orThrow.kt. You can find there unit tests.

Once you are done with the exercise, you can check your solution here.

Playground

import org.junit.Before import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFalse // TODO @Suppress("RedundantNullableReturnType") class OrThrowTest { @Test fun `should throw for null value`() { val value: String? = null val exception = RuntimeException("Value is null") val result = runCatching { value.orThrow { exception } } assertEquals(exception, result.exceptionOrNull()) } @Test fun `should return value for non-null value`() { val value: String? = "Hello" val result = value.orThrow { RuntimeException("Value is null") } assertEquals("Hello", result) } private val value: String? = "Hello" val result = value.orThrow { RuntimeException("Value is null") } @Test fun `should specify result type as non-nullable`() { assertFalse(::result.returnType.isMarkedNullable) } } class UserController( private val userRepository: UserRepository, private val logger: Logger, ) { fun getUser(userId: String) = userRepository .getUser(userId) .orThrow { UserNotFoundException(userId) } .also { logger.log("User with id ${it.id} found") } .toUserJson() } interface UserRepository { fun getUser(userId: String): User? } interface Logger { fun log(message: String) } data class User( val id: String, val name: String, ) data class UserJson( val id: String, val name: String, ) fun User.toUserJson() = UserJson( id = id, name = name ) class UserNotFoundException(userId: String) : Throwable("User $userId not found") class FakeLogger: Logger { var messages: List<String> = emptyList() override fun log(message: String) { this.messages += message } } class FakeUserRepository() : UserRepository { var users: List<User> = emptyList() fun addUser(user: User) { users += user } override fun getUser(userId: String): User? = users .find { it.id == userId } } class UserControllerTest { private val logger = FakeLogger() private val userRepository = FakeUserRepository() private val userController = UserController(userRepository, logger) @Before fun cleanup() { logger.messages = emptyList() userRepository.users = emptyList() } @Test(expected = UserNotFoundException::class) fun `should throw for non-existing user`() { userController.getUser("1") } @Test fun `should return user dto for existing user`() { userRepository.addUser(User("1", "John")) val result = userController.getUser("1") assertEquals(UserJson("1", "John"), result) } @Test fun `should log for existing user`() { userRepository.addUser(User("1", "John")) userController.getUser("1") assertEquals(listOf("User with id 1 found"), logger.messages) } }