How to define a "fallback" implicit?

69 Views Asked by At

Consider a tapir.Codec object (this is how I encountered this problem, but I don't think it has much to do with tapir specifically, it is just about implicit resolution priority in general), it has a bunch of codecs defined for basic types, like Codec.long, Codec.int etc.

I was trying to define a "fallback" codec that could be used when no standard one is found (IRL, the type is more specific than just T, but actual implementation doesn't matter here):

implicit def fallbackCodec[T]: Codec[String, T, PlainText] = ??? 

But I can't figure out where I can put this definition. I tried adding it to one of the traits I am extending, or into its own trait, extended by other traits, or into its own object, and just importing Fallback._ at call site ...

Whatever I do, in(path[Foo]("foo")) works beautifully, but in(path[Long]("id")) also ends up using my fallback codec instead of Codec.long

I was thinking that since standard codecs are defined in the companion object, they would trump my custom definition, but looks like that is not the case. Is there some trick I am missing to make it do what I want?

2

There are 2 best solutions below

0
Dima On

Ok, I think, I found a solution, leaving it here in case someone else finds it helpful. This seems to work:

trait FallbackCodecs { 
   implicit def plaintextCodec[T]: Codec[String, T, TextPlain] = ???
}

trait DefaultCodecs extends FallbackCodecs { 
   implicit def defaultCodec[T](implicit c: Lazy[Codec[String, T, TextPlain]]) = c.value
}

class FooBar extends DefaultCodecs { 
    def foo[T](implicit c: Codec[String, T, TextPlain]) = println(c)

    foo[Int] // Codec.int
    foo[Foo] // plaintextCodec
 }

Another possibility, that also works, but without having to define defaultCodec and have an extra trait is to explicitly import sttp.tapir.Codec._ in FooBar.

It is bit fragile though, because if author of FooBar forgets to add the explicit import, it will compile, and silently replace all codecs with the fallback. Also, it generates an "unused import" warning.

0
Dmytro Mitin On

You can use shapeless.LowPriority

//libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"

implicit def fallbackCodec[T](implicit lowPriority: LowPriority): Codec[String, T, TextPlain] = ???

Once upon a time there was a library https://github.com/milessabin/export-hook for handling orphan instances of type classes but it's deprecated.