diff --git a/okapi-exposed/src/main/kotlin/com/softwaremill/okapi/exposed/ExposedConnectionProvider.kt b/okapi-exposed/src/main/kotlin/com/softwaremill/okapi/exposed/ExposedConnectionProvider.kt index 0c5419b..cf1851f 100644 --- a/okapi-exposed/src/main/kotlin/com/softwaremill/okapi/exposed/ExposedConnectionProvider.kt +++ b/okapi-exposed/src/main/kotlin/com/softwaremill/okapi/exposed/ExposedConnectionProvider.kt @@ -13,12 +13,16 @@ import java.sql.Connection * `transaction(database) { }` block completes — so this provider performs no cleanup. * * Use when your application manages transactions via Exposed (e.g. Ktor + Exposed apps). - * Must be called from within an active Exposed transaction; otherwise - * `TransactionManager.current()` throws. + * Must be called from within an active Exposed transaction; otherwise [withConnection] + * throws an [IllegalStateException] pointing the caller at the missing `transaction { }` + * block, instead of letting Exposed's own less specific error surface. */ class ExposedConnectionProvider : ConnectionProvider { override fun withConnection(block: (Connection) -> T): T { - val connection = TransactionManager.current().connection.connection as Connection - return block(connection) + val transaction = TransactionManager.currentOrNull() + ?: throw IllegalStateException( + "ExposedConnectionProvider.withConnection must be called within an Exposed transaction { } block", + ) + return block(transaction.connection.connection as Connection) } } diff --git a/okapi-exposed/src/test/kotlin/com/softwaremill/okapi/exposed/ExposedConnectionProviderTest.kt b/okapi-exposed/src/test/kotlin/com/softwaremill/okapi/exposed/ExposedConnectionProviderTest.kt new file mode 100644 index 0000000..b75fde7 --- /dev/null +++ b/okapi-exposed/src/test/kotlin/com/softwaremill/okapi/exposed/ExposedConnectionProviderTest.kt @@ -0,0 +1,34 @@ +package com.softwaremill.okapi.exposed + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import org.jetbrains.exposed.v1.jdbc.Database +import org.jetbrains.exposed.v1.jdbc.transactions.transaction + +class ExposedConnectionProviderTest : FunSpec({ + + val provider = ExposedConnectionProvider() + + test("throws IllegalStateException with actionable message when called outside an Exposed transaction") { + val ex = shouldThrow { + provider.withConnection { /* unreachable */ } + } + ex.message shouldContain "ExposedConnectionProvider.withConnection" + ex.message shouldContain "Exposed transaction { } block" + } + + test("supplies the active Exposed transaction's connection to the block") { + val db = Database.connect( + "jdbc:h2:mem:exposed_provider_test_${System.nanoTime()};DB_CLOSE_DELAY=-1", + driver = "org.h2.Driver", + ) + + val connectionWasOpen: Boolean = transaction(db) { + provider.withConnection { conn -> !conn.isClosed } + } + + connectionWasOpen shouldBe true + } +})