Does anyone know how I can make a header lens that...
# http4k
a
Does anyone know how I can make a header lens that has a list of options, mapping to a property of an enum? This contrived example compiles, but the tests fail. I know how to use
map
on a
BiDiLensSpec
to convert between a DTO and a domain model, but I don't if or how I can use that to help here. I want the lens to be backed by an enum so that the openapi spec shows the options; simply validating a string isn't good enough.
Copy code
enum class MyEnumDto(val value: String) {
      A("aaaa"),
      B("bbbb"),
      C("cccc")
    }

    val lens: BiDiLens<HttpMessage, MyEnumDto> = Header
        .mapWithNewMeta(StringBiDiMappings.enum<MyEnumDto>(), ParamMeta.EnumParam(MyEnumDto::class))
        .defaulted("my-enum", MyEnumDto.A)

    @Test
    fun `parse enum lens`() {
        val request = Request(Method.GET, "/").header("my-enum", "bbbb")

        lens(request) shouldBe MyEnumDto.B
        /*
        header 'my-enum' must be string
        Caused by: java.lang.IllegalArgumentException: No enum constant com.cacmloud.config.camcapi.api.legacy.LegacyLensesTest.MyEnumDto.bbbb
            at java.base/java.lang.Enum.valueOf(Enum.java:273)
            at com.cacmloud.config.camcapi.api.legacy.LegacyLensesTest$MyEnumDto.valueOf(LegacyLensesTest.kt)
            at com.cacmloud.config.camcapi.api.legacy.LegacyLensesTest$special$$inlined$enum$1.invoke(BiDiMapping.kt:87)
            at com.cacmloud.config.camcapi.api.legacy.LegacyLensesTest$special$$inlined$enum$1.invoke(BiDiMapping.kt:87)
            at org.http4k.lens.BiDiMapping.asOut(BiDiMapping.kt:47)
            at org.http4k.lens.LensSpecKt$mapWithNewMeta$1.invoke(lensSpec.kt:249)
            at org.http4k.lens.LensGet$map$1.invoke(lensSpec.kt:270)
            at org.http4k.lens.LensGet$map$1.invoke(lensSpec.kt:19)
            at org.http4k.lens.LensGet$invoke$1.invoke(lensSpec.kt:17)
            at org.http4k.lens.LensGet$invoke$1.invoke(lensSpec.kt:17)
            at org.http4k.lens.BiDiLensSpec$defaulted$2.invoke(lensSpec.kt:165)
            at org.http4k.lens.Lens.invoke(lens.kt:15)
            ... 109 more
         */
    }

    @Test
    fun `write enum lens`() {
        val request = Request(Method.GET, "/")
            .with(lens of MyEnumDto.C)

        request.shouldHaveHeader("my-enum", "cccc")
        /*
        Header "my-enum": expected:<"cccc"> but was:<"C">
        Expected :"cccc"
        Actual   :"C"
         */
    }
s
If you want the mapping to be done based on an Enum property, you can do it like this:
Copy code
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.lens.Header

enum class MyEnumDto(val value: String) {
    A("aaaa"),
    B("bbbb"),
    C("cccc");

    companion object {
        fun fromValue(v: String) = 
            values().find { it.value == v } ?: error("Invalid value: $v")
    }
}

val lens = Header.map(MyEnumDto.Companion::fromValue, MyEnumDto::value)
    .required("my-enum")

val request = Request(Method.GET, "/").header("my-enum", "bbbb")

println(lens(request)) //B
a
So, that does work, but it doesn't make the options available in the openapi spec. Maybe this isn't even possible for headers: the regular
Header.enum()
doesn't seem to actually work, unlike for
Query
or
Path
.
Actually, I don't think I really need this to work. What I really wanted to do was make arcane
Accept
header options to replicate a legacy API, but I can just get that to slot in perfectly by copying and adapting the
ConfigurableJackson.autobody
, and then adding the different
returning
lenses to my contract meta.