Last active
September 25, 2025 22:11
-
-
Save erdeszt/09340fc8a2fcb2bc72d6cdb1d7405725 to your computer and use it in GitHub Desktop.
Kotlin checked exceptions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.gl.common | |
| sealed interface Result<out Error, out Value> { | |
| fun <Value1> map(f: (Value) -> Value1): Result<Error, Value1> = when (this) { | |
| is Failure -> Failure(error) | |
| is Success -> Success(f(value)) | |
| } | |
| fun <Error1> leftMap(f: (Error) -> Error1): Result<Error1, Value> = when (this) { | |
| is Failure -> Failure(f(error)) | |
| is Success -> Success(value) | |
| } | |
| } | |
| class Failure<out Error, out Value>(val error: Error) : Result<Error, Value> | |
| class Success<out Error, out Value>(val value: Value) : Result<Error, Value> | |
| class Raise<in Error : Exception> private constructor() { | |
| companion object { | |
| suspend fun <Error : Exception, Out> safely( | |
| klass: Class<Error>, | |
| body: Raise<Error>.() -> Out, | |
| handler: (Error) -> Out | |
| ): Out { | |
| val raiseInstance = Raise<Error>() | |
| return with(raiseInstance) { | |
| raiseInstance.recover(body, handler, { klass.isAssignableFrom(it.javaClass) }) | |
| } | |
| } | |
| } | |
| fun raise(error: Error): Nothing { | |
| throw error | |
| } | |
| operator fun invoke(error: Error): Nothing { | |
| raise(error) | |
| } | |
| fun <R> liftEither(either: Result<Error, R>): R { | |
| return when (either) { | |
| is Failure -> throw either.error | |
| is Success -> either.value | |
| } | |
| } | |
| // TODO: Consider restricting Error0=Error | |
| context(_: Raise<Error>) | |
| @Suppress("UNCHECKED_CAST") | |
| suspend fun <Error0 : Exception, Out> recover( | |
| body: suspend Raise<Error0>.() -> Out, | |
| mapper: (Error0) -> Out, | |
| verifier: (Exception) -> Boolean | |
| ): Out { | |
| try { | |
| with(Raise<Error0>()) { | |
| return@recover body() | |
| } | |
| } catch (error: Exception) { | |
| if (verifier(error)) { | |
| return@recover mapper(error as Error0) | |
| } else { | |
| throw error | |
| } | |
| } | |
| } | |
| context(raiseInstance: Raise<Error>) | |
| suspend fun <Error0 : Exception, Out> handle( | |
| body: suspend Raise<Error0>.() -> Out, | |
| mapper: (Error0) -> Error, | |
| verifier: (Exception) -> Boolean | |
| ): Out { | |
| return recover(body, { | |
| raiseInstance.raise(mapper(it)) | |
| }, verifier) | |
| } | |
| } | |
| context(raiseInstance: Raise<Error>) | |
| fun <Error : Exception> Error.raise(): Nothing { | |
| raiseInstance(this) | |
| } | |
| context(raiseInstance: Raise<Error>) | |
| suspend inline fun <reified Error0 : Exception, Error : Exception, T> ((Error0) -> Error).handles(noinline body: suspend Raise<Error0>.() -> T): T { | |
| return raiseInstance.handle(body, this, { it is Error0 }) | |
| } | |
| context(raiseInstance: Raise<Error>) | |
| suspend inline fun <reified Error0 : Exception, Error : Exception, T> ((Error0) -> T).recovers(noinline body: suspend Raise<Error0>.() -> T): T { | |
| return raiseInstance.recover(body, this, { it is Error0 }) | |
| } | |
| context(raiseInstance: Raise<Error>) | |
| fun <Error : Exception, R> Result<Error, R>.toRaise(): R { | |
| return raiseInstance.liftEither(this) | |
| } | |
| // --------------- Example -------------- | |
| context(_: Raise<EmailAlreadyInUseError>) | |
| override suspend fun register(email: Email, password: PlainPassword): User { | |
| val user = { _: TestError -> EmailAlreadyInUseError(email) }.handles { | |
| userService.create(email, password) | |
| } | |
| val user2 = { _: TestError -> EmailAlreadyInUseError(email) }.recovers { | |
| null | |
| } | |
| val user3 = { _: EmailAlreadyInUseError -> EmailAlreadyInUseError(email) }.recovers { | |
| register(email, password) | |
| } | |
| val euser: Result<TestError, String> = Failure(TestError()) | |
| val euser1 = euser.mapError { EmailAlreadyInUseError(email) }.toRaise() | |
| return user | |
| } | |
| context(_: Raise<EmailAlreadyInUseError>, _: Raise<TestError>) | |
| override suspend fun create(email: Email, password: PlainPassword): User { | |
| val existingUserWithSameEmail = repo.getByEmail(email) | |
| if (existingUserWithSameEmail != null) { | |
| EmailAlreadyInUseError(email).raise() | |
| } | |
| val hashedPassword = passwordHasher.hash(password) | |
| val user = repo.create(email, hashedPassword) | |
| return user | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Raise<in T : Throwable> { | |
| operator fun invoke(exception: T): Nothing { | |
| throw exception | |
| } | |
| } | |
| sealed class BusinessError(message: String, cause: Throwable? = null) : Exception(message, cause) | |
| data class IncorrectValueError(val value: String) : BusinessError("Incorrect value: ${value}") | |
| class OtherError() : BusinessError("Some other mistake") | |
| context(raise: Raise<IncorrectValueError>) | |
| fun blExample(value: String): Unit { | |
| if (value == "yo") { | |
| return | |
| } else { | |
| raise(IncorrectValueError("Not a good value: ${value}")) | |
| } | |
| } | |
| context(raise: Raise<OtherError>) | |
| fun blOther(): Unit { | |
| raise(OtherError()) | |
| } | |
| context(_: Raise<IncorrectValueError>, _: Raise<OtherError>) | |
| fun blBoth(): Unit { | |
| blExample("yo") | |
| blOther() | |
| } | |
| context(_: Raise<BusinessError>) | |
| fun blUpperBound(): Unit { | |
| blExample("yo") | |
| blOther() | |
| } | |
| interface X { | |
| context(raise: Raise<BusinessError>) | |
| fun blah(): Unit { | |
| raise(OtherError()) | |
| } | |
| } | |
| fun main(args: Array<String>) { | |
| with(Raise<BusinessError>()) { | |
| blExample("yo") | |
| blOther() | |
| blBoth() | |
| blUpperBound() | |
| object : X {}.blah() | |
| } | |
| println("Hello") | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment