Why does `query` `Codec` uses `List[String]` as the low-level value?

Hey.

While implementing a Codec for my custom data type I was surprised that a Codec[String, MyType, CodecFormat.TextPlain] could not be used for query input.

Turns out query is defined like this:

def query[T: Codec[List[String], *, TextPlain]](name: String): EndpointInput.Query[T]

I understand there can be more than one query param in the URI, but why isn’t it this instead?

def query[T: Codec[String, *, TextPlain]](name: String): EndpointInput.Query[List[T]]

This way the same Codec that can be used for path segments would be useable for query parameters, which makes sense to me. I am not even sure how the List[String] codec should behave. Can it be passed Nil if query parameter wasn’t provided in the URI, or is it guaranteed to always have at least 1 item? (Something like cats NonEmptyList would be useful here then).

With:

def query[T: Codec[String, *, TextPlain]](name: String): EndpointInput.Query[List[T]]

the input would always yield a list of values, even if you always need one (which is a common case). In the tapir approach, when you have:

def query[T: Codec[List[String], *, TextPlain]](name: String): EndpointInput.Query[T]

it means that the codec needs to handle decoding of a list of values of the query parameter (as query parameters can be repeated). The codec might return errors if there are multiple, or not enough occurrences of the query parameter. That’s all handled via the DecodeResult value, which is used in Codec.map. More on codecs: Codecs — tapir 1.x documentation

In your case, if you have val myCodec: Codec[String, MyType, CodecFormat.TextPlain], you can use Codec.listHead(myCodec), which will give you a Codec[List[String], MyType, CodecFormat.TextPlain]. This will be applied automatically, if the myCodec value is available in the implicit scope, and you simply create the query parameter description as query[MyType].

You can also use a NonEmptyList, if required, using the cats integration: Datatypes integrations — tapir 1.x documentation. If you provide a Codec[String, MyType, TextPlain] in the implicit scope, you’ll also be able to create a query[NonEmptyList[MyType]], provided that the integration is in scope (imported).

1 Like