<@U0RM4EPC7> What's these LazyMaterialized wrapper...
# arrow
d
I'm trying to figure out if this might be useful for wrapping WireMock...
s
Those are two convenience methods that should still be back-ported to arrow-kotest IMO.
LazyMaterialized
comes from Kotest. It's short-hand for
install(resource { }.extension())
.
Doesn't Kotest have special integration for WireMock? 🤔
d
Yeah, but all the configs, and getting the port/setting up the Ktor client with it...
The integration is just to start/stop the server in the test or spec scope.
So those functions are just used to wrap extensions into Arrow Resources?
s
It's so you can do:
Copy code
suspend fun ResourceScope.hikariDataSource(...): HikariDataSource = TODO()

class MyTest : StringSpec() {
  val dataSource = install { hikariDataSource(...) }
}
d
It looks like there is something in the kotest assertions module: commonMain/io/kotest/assertions/arrow/fx/coroutines/ResourceExtension.kt
s
Instead of:
Copy code
suspend fun ResourceScope.hikariDataSource(...): HikariDataSource = TODO()

class MyTest : StringSpec() {
  val dataSource = install(resource { hikariDataSource(...) }.extension())
}
d
Why not:
Copy code
suspend fun hikariDataSource(...): Resource<HikariDataSource> = resource { TODO() }
s
Right, that's also 100% valid but not the function I have in that repo 😄 Which is why I added the helper function.
d
Which is the recommended style? (This also applies to Either or Raise<>.xxx(), etc...) is there some kind of accepted standard in this?
BTW, an extra docs section for "Functional/Arrow Code Style" 😉
s
There is not really a recommended style, I've been thinking about this since this question has come back a few times. I would say it depends on personal preference since from a technical point of view both 99,99% the same, or at least you achieve the same by using both. A lot of people seem to like, including myself, are very excited about
Raise
especially with upcoming context receivers which sadly are not stable yet 😞 But on the other side a lot of people prefer
Either
, which can also be useful when interopting with Java or Swift.
Either
is also much more widely known, and thus might introduce less friction.
d
I mean:
Copy code
suspend fun foo(): Either<.., Bar> = either { }

// vs.

suspend fun Raise<..>.foo(): Bar = ...
or in the resource case using the
ResourceScope....
vs.
resource { }
directly in the function
s
Right, same answer applies. Without context receivers
Raise<..>
is not always possible, since you're limited to 1 context receiver. So for example in the webinar of last week I used
suspend fun ResourceScope.postgres(..): Either<Invalid, Postgres>
d
Oh.. so it's just a matter of preference why you chose:
suspend fun ResourceScope.hikariDataSource(...): HikariDataSource = TODO()
... I guess that when context recievers will be available, it would be easier to migrate with that...
s
Yes, it's just a matter of preference. Some arguments in favor of this pattern are: • No wrappers in return type, so only happy path appears in return type • No
bind
or
flatMap
necessary to compose different values, simply function invocation is sufficient • No allocations, albeit that is completely neglectable IMO unless it occurs in some hot loop.
d
I tried something like this:
Copy code
fun findRandomOpenPort(): Int {
    ServerSocket(0).use { socket -> return socket.localPort }
}

data class WireMockWrapper(
    val wm: WireMockServer,
    val port: Int,
    val client: HttpClient,
)

fun WireMockWrapper(): Resource<WireMockWrapper> = resource {
    val port = findRandomOpenPort()
    val wm = install({ WireMockServer(options().port(port).notifier(ConsoleNotifier(true))) }, { wm, _ ->
        wm.shutdownServer()
    })
    val client = autoCloseable {
        HttpClient {
            install(DefaultRequest) { url("<http://localhost>:$port/") }
            install(ContentNegotiation) { json() }
        }
    }
    WireMockWrapper(wm, port, client)
}
but when I get to using it in Kotest...
Copy code
class HandleServiceFailuresTest : FreeSpec() {
    init {
        val wmw = extension(WireMockWrapper().extension())
   
        // This can't be run w/o runBlocking...
        wmw.resource.use {
            extension(WireMockListener(it.wm, ListenerMode.PER_TEST))
        }
...
on the other hand, using just the resource extension won't reset the endpoints setup before each test... it'll only handle closing the server at the end of the spec.
s
You can pass
LifecycleMode.EveryTest
to
extension
,
WireMockWrapper().extension(LifecycleMode.EveryTest)
.
d
Then it would recreate all those resources every time instead of just resetting the endpoints configs... I could do that though...
s
Uhm.. perhaps I didn't understand your question correctly. Do you only need
WireMockListener
to get reset? That is not a resource in this case?
d
I guess the simple solution for the best of both worlds is:
Copy code
beforeTest { 
            wmw.resource.use { it.wm.resetAll() }
        }
then the extension would take down the server only at the end of each spec, and reset the connections. I think I'm starting to understand things a bit better... resources are just for managing the successful start/close of a resource, not it's whole lifecycle, so the Resource.extension() is made for start/close per test/spec... the reason why I was trying to do this was to reuse resources already used in my service code... like my client or service classes based on it. Using the standard Kotest wiremock listener doesn't bridge between those two worlds.