rho icon indicating copy to clipboard operation
rho copied to clipboard

Implicit EntityEncoder for circe Encoder breaks compilation

Open DStranger opened this issue 7 years ago • 20 comments

Hi guys,

The following code doesn't compile with a could not find implicit value for parameter hltf: org.http4s.rho.bits.HListToFunc[F,String :: shapeless.HNil,String => F[Test.this.Ok.T[String]]].

import cats.effect.Effect
import io.circe.Encoder
import org.http4s._
import org.http4s.rho.RhoService
import org.http4s.rho.swagger.SwaggerSyntax

trait Auto

class Test[F[_]](swagger: SwaggerSyntax[F])(implicit E: Effect[F]) extends RhoService[F] {
  import swagger._

  private implicit def autoEncoder[A <: Auto: Encoder]: EntityEncoder[F, A] = ???

  "This route allows your to post stuff" **
    POST / "post" ^ EntityDecoder.text[F] |>> { body: String =>
      Ok("you posted " + body)
  }
}

Removing autoEncoder, or removing the io.circe.Encoder constraint fixes the problem.

DStranger avatar Feb 06 '18 14:02 DStranger

Curious, since I believe the interesting part is that there isn't any need for the Auto entity encoder anyway. Do you need to remove the io.circe.Encoder import, or just the constraint in the autoEncoder method?

bryce-anderson avatar Feb 06 '18 17:02 bryce-anderson

@bryce-anderson in this code there's indeed no need for Auto, but I've adapted it from some code that finds this construct useful (adapted from the swagger example). Removing just the constraint works.

DStranger avatar Feb 06 '18 18:02 DStranger

@bryce-anderson the problem resolves if I disable partial unification

DStranger avatar Feb 15 '18 10:02 DStranger

Interesting.

bryce-anderson avatar Feb 15 '18 16:02 bryce-anderson

that EntityEncoder should not be defined like that. I don't know if it's related to this specific problem or not (probably not), but it might be worth having a look at this regardless: https://github.com/http4s/http4s/issues/1648

SystemFw avatar Feb 15 '18 16:02 SystemFw

@SystemFw yes, I don't think that's related, the error in http4s/http4s#1648 was caused by jsonEncoderOf requiring an instance of EntityEncoder[F, String], here it's not the case.

DStranger avatar Feb 16 '18 14:02 DStranger

The puzzling thing here is the interaction with partial unification. I have it seen it happen e.g. wrt order of type parameters, but never sub typing constraints

SystemFw avatar Feb 16 '18 14:02 SystemFw

I have the same problem. circe.generic.auto works fine for EntityDecoders but not Encoders with Rho...

Igosuki avatar Apr 25 '18 09:04 Igosuki

I fixed it by hard encoding the import statements in the right order : import swaggerSyntax._ import io.circe.generic.auto._ import org.http4s.circe._ import org.http4s.rho.bits._

It's a conflict of implicits between rho.bits, circe and http4s.circe

Igosuki avatar Jun 07 '18 17:06 Igosuki

@Igosuki could you post a full example? I'm just running into the same problem and I can't seem to figure it out... Or anybody else with a workaround for that matter :wink:

nightscape avatar Jun 22 '18 15:06 nightscape

@nightscape did you happen to find a workaround? )

DStranger avatar Sep 03 '18 12:09 DStranger

Unfortunately not... I converted the problematic code to pure Http4s and left the rest in Rho.

nightscape avatar Sep 03 '18 21:09 nightscape

Two things to do @nightscape @DStranger

  • use scala 2.12.6
  • use proper import order :
import swaggerSyntax._
  import io.circe.generic.auto._
  import org.http4s.circe._
  import your.package.Encoders._
  import your.package.Decoders._
  import org.http4s.rho.bits._

here's the encoders def I'm using :

trait GenericEncoders {
   // Generic
  implicit def jsonEncoder[F[_]: Sync, A <: Product: Encoder]
    : EntityEncoder[F, A] = jsonEncoderOf[F, A]
  implicit def cJsonEncoder[F[_]: Sync]: EntityEncoder[F, Json] =
    jsonEncoderOf[F, Json]
  implicit def valueClassEncoder[A: UnwrappedEncoder]: Encoder[A] = implicitly
}

Igosuki avatar Sep 03 '18 21:09 Igosuki

Perhaps someone can open a PR to the rho docs for this?

SystemFw avatar Sep 03 '18 21:09 SystemFw

I haven't investigated where the problem comes from exactly since I didn't know the codebase until my last PR... I suspect it has to do with Circe's macros which conflict with rho's Kleisli's and shapeless.

Igosuki avatar Sep 03 '18 21:09 Igosuki

Typically, you can get a similar type of error if you use circe.generic.auto and sealed traits with noKnownSubclasses until scala 2.12.4 it's a question of what class gets resolved first when the compiler reaches a certain phase.

Igosuki avatar Sep 03 '18 21:09 Igosuki

Not sure if it is exactly same, but I see could not find implicit value for parameter hltf compliation error for any endpoint that can return more than one type of HTTP response:

class MinimalService[F[_]: Sync](swaggerSyntax: SwaggerSyntax[F]) extends RhoService[F] {
  import swaggerSyntax._

  "Example route" **
    POST / "endpoint" ^ jsonDecoder[F] |>> { _: Json =>
    if (true) Ok("ok") else Forbidden("forbidden")
  }
}
[error] /Users/chuwy/workspace/server/src/main/scala//server/service/MinimalService.scala:16:40: could not find implicit value for parameter hltf: org.http4s.rho.bits.HListToFunc[F,io.circe.Json :: shapeless.HNil,io.circe.Json => F[_ >: MinimalService.this.Ok.T[String] with MinimalService.this.Forbidden.T[String] <: org.http4s.rho.Result[F,Nothing,Nothing,Nothing,String,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,String,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing]]]
[error]     POST / "endpoint" ^ jsonDecoder[F] |>> { _: Json =>
[error]                                        ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed

Works fine either without decoding or with single HTTP status.

UPD: tying MinimalService to IO solved the problem. UPD2: my issue was not related to the one raised here. Changing F[_] to covariant F[+_] solved the problem properly

chuwy avatar Sep 20 '18 12:09 chuwy

Yeah that's because with IO you can directly pull the concrete IO dsl, which isn't possible with just F : Effect

Igosuki avatar Oct 09 '18 23:10 Igosuki

So I'm having the same issue, I think.

The following code works, but if I try to do something like

class ProductRoutes[M[+_]: Effect, F[_]](swaggerSyntax: SwaggerSyntax[M])(F: ProductSyntax[M]) extends RhoRoutes[M] {

  import swaggerSyntax._
  import io.circe.generic.auto._
  import org.http4s.circe.{jsonOf, jsonEncoderOf}

  implicit def productEntityEncoder: EntityEncoder[M, ProductRepresentation] = jsonEncoderOf[M, ProductRepresentation]
  implicit def errorEntityEncoder: EntityEncoder[M, ServiceError] = jsonEncoderOf[M, ServiceError]

  implicit def productEntityDecoder: EntityDecoder[M, ProductRepresentation] = jsonOf[M, ProductRepresentation]
  implicit def errorEntityDecoder: EntityDecoder[M, ServiceError] = jsonOf[M, ServiceError]

  "We don't want to have a real 'root' route anyway..." **
    GET |>> TemporaryRedirect(Uri(path = "/swagger-ui"))

  "This route allows you to post stuff" **
    POST / "post" ^ EntityDecoder.text[M] |>> {
      body: String => Ok("you posted " + body)
    }

  "This route allows you to get the product" **
    GET / "product" / pathVar[String] |>> {
    id: String => F.getProduct(id)
  }

  "Endpoint for creating product" **
    PUT / "product" ^ EntityDecoder[M, ProductRepresentation] |>> {
      product: ProductRepresentation => F.createProduct(product)
    }

}

But it doesn't work if I modify the code to do something like this:

 "This route allows you to get the product" **
    GET / "product" / pathVar[String] |>> {
      id: String =>
        F.getProduct(id) map {
          case Some(prod) => Ok(prod)
          case None => NotFound(ServiceError(s"$id does not exist"))
        }
    }

ChetanBhasin avatar Jan 16 '19 17:01 ChetanBhasin

@ChetanBhasin your issue is Ok and NotFound return an M[Result[...*]], so when you do F.getProduct.map ... you should really be using flatMap so you aren't doing M[M[Result[...*]].

Also, just random note, class ProductRoutes[M[+_]: Effect, F[_]] the F[_] is unused in your class.

zarthross avatar Jan 19 '19 17:01 zarthross