pheymann/typedapi

Strange api transformation behaviour

wookievx opened this issue · 1 comments

Recently i encountered very strange bug, that was very hard to pinpoint. I will provide example (maybe not minimal but this exact combination causes the failure, in this case UUID as param type and Server.Match element) that should reproduce on version 0.2.0.
I have the following endpoint definition:

val Api = := :> Segment[UUID]("param") :> Server.Match[String]("headers") :> ReqBody[Json, TypeA] :> Put[Json, TypeB]

Now i work with it as always, so i define serialization/deserialization mechanism for request/response, have to define ValueExtractor for UUID and then derive endpoint from it and mount it (using http4s) as backend (i wrote custom integration with the newest version but it is exposing the same types as the standard one):

val endpoint = derive[F](Api).from(func)
val sm = ServerManager(BlazeServerBuilder[F], "0.0.0.0", 8080)
mount(sm, endpoint)

The following code does not compile resulting with compiler error like the following:
could not find implicit value for parameter executor: typedapi.server.EndpointExecutor.Aux[Req,typedapi.shared.SegmentInput :: typedapi.shared.ServerHeaderMatchInput :: shapeless.HNil,String("param") :: String("headers") :: Symbol with shapeless.tag.Tagged[String("body")] with shapeless.labelled.KeyTag[typedapi.dsl.MT.application/json.type,Symbol with shapeless.tag.Tagged[String("body")]] :: shapeless.HNil,java.util.UUID :: scala.collection.immutable.Map[String,String] :: TypeA :: shapeless.HNil,typedapi.shared.PutWithBodyCall,this.Out,F,TypeB,Resp]

I started digging into this error and what i found out is that somehow this.Out is infered to the wrong type by the compiler, or something like that because when i wrote the following custom http4s executor, the same endpoint compiled :

trait SimpleExecutor[R, VIn <: HList, Rout, F[_], FOut, Out] {
  def execute(req: R, eReq: EndpointRequest, endpoint: Endpoint[_, _, VIn, _, Rout, F, FOut]): Either[ExtractionError, Out]
}

//instance very similar to the default http4s executor instance
implicit def noReqBodySimple[VIn <: HList, ROut, F[_], FOut](implicit
    encoder: EntityEncoder[F, FOut],
    ME: Effect[F]
  ): SimpleExecutor[Request[F], VIn, ROut, F, FOut, F[Response[F]]] = ???

//with body instance
implicit def withReqBodySimple[VIn <: HList, ROut <: HList, Bd, F[_], FOut](implicit
    encoder: EntityEncoder[F, FOut],
    ME: Effect[F],
    decoder: EntityDecoder[F, Bd],
    _prepend: Prepend[ROut, Bd :: HNil]
  ): SimpleExecutor[Request[F], VIn, (BodyType[Bd], ROut), F, FOut, F[Response[F]]] = ???

//mounting function, again analogous
def mountHttp4s[VIn <: HList, ROut, M <: MethodType, F[_], FOut](
    server: ServerManager[BlazeServerBuilder[F]],
    endpoint: Endpoint[_, _, VIn, M, ROut, F, FOut]
  )(implicit
    executor: SimpleExecutor[Request[F], VIn, ROut, F, FOut, F[Response[F]]],
    mounting: MountEndpoints.Aux[BlazeServerBuilder[F], Request[F], F[Response[F]], Resource[F, Server[F]]]
  ): Resource[F, Server[F]] = ???

The most important thing i changed is that i erased the information that Rout is equal to VIn and simply casted the value when needed (assuming that it would be equal anyway which only made sense).
Now using mountHttp4s code compiles and runs but what is happening is that Rout is actually a reversal of VIn so i get the following error message when trying to invoke this endpoint:
scala.collection.immutable.Map$EmptyMap$ cannot be cast to java.util.UUID
I checked the original definition of executor for http4s and did not found any part which "reversed" input argument.

I am really puzzled here because the issue seems to be non-existent when using Get method (standard mount works great), it's not only request body issue because for example Delete also fails to compile (and fails at runtime using my special Executor implementation)

On the side note, i think that all this type noise is not necessary when it comes down to executor, it does only need to know that Endpoint accepts hlist of arguments of type VIn and that it may take request body as well. This is what i tried to achieve in my simplified implementation.