https://kotlinlang.org logo
#strikt
Title
# strikt
e

Eric

05/31/2023, 2:11 PM
I'm trying to create a couple strikt assertions for this `Result`: https://github.com/michaelbull/kotlin-result I'd like to replace usages like
.isA<Ok<Foo>>().get { value }
with
.isSuccess<Foo>()
but I'm having trouble w/ the generic signatures.
Copy code
fun <V> Assertion.Builder<Result<V, *>>.isSuccess(): Assertion.Builder<V> = isA<Ok<V>>().get { value }
fun <E> Assertion.Builder<Result<*, E>>.isFailure(): Assertion.Builder<E> = isA<Err<E>>().get { error }
IntelliJ is yelling at me. Any ideas on what these ext funs should look like? Thanks.
These work, but I was hoping to restrict the usage to things of type
Result
Copy code
fun <V> Assertion.Builder<*>.isOk(): Assertion.Builder<V> = isA<Ok<V>>().get { value }
fun <E> Assertion.Builder<*>.isErr(): Assertion.Builder<E> = isA<Err<E>>().get { error }
r

robfletcher

05/31/2023, 5:14 PM
This works for me (making both type arguments of the receiver wildcard)
Copy code
fun <V> Assertion.Builder<Result<*, *>>.isSuccess(): Assertion.Builder<V> = isA<Ok<V>>().get { value }
fun <E> Assertion.Builder<Result<*, *>>.isFailure(): Assertion.Builder<E> = isA<Err<E>>().get { error }
I think that’s because of the type signature on
isA
but I’m not 100% sure
e

Eric

05/31/2023, 5:19 PM
Still not sure why I can't get that to work, using your definitions
r

robfletcher

05/31/2023, 5:22 PM
hmm, it’s working for me when I explicitly type
result
as
Result<*, *>
. If I change that to a concrete type, or let it use inference I see the same as you
here’s my dumb scratch file
Copy code
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import strikt.api.Assertion
import strikt.api.expect
import strikt.assertions.isA
import strikt.assertions.isEqualTo

fun <V> Assertion.Builder<Result<*, *>>.isSuccess(): Assertion.Builder<V> = isA<Ok<V>>().get { value }
fun <E> Assertion.Builder<Result<*, *>>.isFailure(): Assertion.Builder<E> = isA<Err<E>>().get { error }

val result: Result<*, *> = Err(IllegalStateException("o noes"))

try {
  expect {
    that(result).isSuccess<String>().isEqualTo("hi")
  }
} catch (e: Throwable) {
  println (e)
}
that is weird
and this is fine
that(result).isA<Ok<String>>().get { value }.isEqualTo("hi")
e

Eric

05/31/2023, 5:29 PM
right... where is the type inference being dropped?
r

robfletcher

05/31/2023, 5:29 PM
so it’s got to be due to the type variance of that type on
Ok
/
Err
because the difference is it’s a type parameter of the
isSuccess
function rather than being specified directly
IIRC getting the equivalent function to work for
kotlin.Result
was a pain
and you can do something horrible like
Copy code
(that(result) as Assertion.Builder<Result<String, *>>).isSuccess<String>().isEqualTo("hi")
and it works
oh, I think it’s the other parameter. It also works if I declare
result
as
Result<String, *>
I still can’t get it to work, but I think that’s a clue
e

Eric

05/31/2023, 5:42 PM
ok, these seem to work
Copy code
fun <V : Any, E : Any> Assertion.Builder<Result<V, E>>.isOk(): Assertion.Builder<V> = isA<Ok<V>>().get { value }
fun <V : Any, E : Any> Assertion.Builder<Result<V, E>>.isErr(): Assertion.Builder<E> = isA<Err<E>>().get { error }
but then the call site doesn't have a type param... not sure if that's an issue
that(result)._isOk_()._isEqualTo_(lead)
(changed the names to avoid collisions w/ the current isSuccess/isFailure)
I don't think that's an issue... type inference is working
r

robfletcher

05/31/2023, 5:44 PM
hmm, but it doesn’t enable you to narrow the type of the result
which may or may not be a big deal
e

Eric

05/31/2023, 5:45 PM
it does, though
r

robfletcher

05/31/2023, 5:47 PM
right, but if the inferred type of result was say
Result<Superclass, E>
you don’t get to narrow it to a subclass
e

Eric

05/31/2023, 5:47 PM
oh... yeah
r

robfletcher

05/31/2023, 5:47 PM
probably an edge case
still feels like it should work
e

Eric

05/31/2023, 5:48 PM
close enough for me. thanks for the help
r

robfletcher

05/31/2023, 5:49 PM
any time. Gonna be scratching my head about this one
e

Eric

05/31/2023, 5:50 PM
i almost had to back to java at the new gig 🙂
r

robfletcher

05/31/2023, 5:50 PM
I was gonna ask why you’re using that result library rather than Kotlin’s
Result
type
e

Eric

05/31/2023, 5:51 PM
i hate that kotlin's requires the RHS to be a throwable...
r

robfletcher

05/31/2023, 5:51 PM
ah
yeah, that seemed like the one difference
e

Eric

05/31/2023, 5:52 PM
my functions can have return types like this, so i can read the success/failure part easily
: Result<Lead, RecaptchaVerificationFailure> { ... }
and the regular sealed interface solution hides the success type, which i like to see for the happy path
finally got these working:
Copy code
inline fun <reified E : Any> Assertion.Builder<*>.isErrResult(): Assertion.Builder<E> =
    isA<Result<*, E>>().isA<Err<E>>().get { error }.isA<E>()
// these could have a nice description
inline fun <reified V : Any> Assertion.Builder<*>.isOkResult(): Assertion.Builder<V> =
    isA<Result<V, *>>().isA<Ok<V>>().get { value }.isA<V>()
but, it doesn't save me anything where I'm doing the assertion:
Copy code
expectThat(result).isErrResult<InvalidToken>()
// is the same as this:
expectThat(result).isErr().isA<InvalidToken>()
So, I might not use it after all.
20 Views