Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implicit EntityEncoder for circe Encoder breaks compilation #216

Open
DStranger opened this issue Feb 6, 2018 · 20 comments
Open

Implicit EntityEncoder for circe Encoder breaks compilation #216

DStranger opened this issue Feb 6, 2018 · 20 comments

Comments

@DStranger
Copy link

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.

@bryce-anderson
Copy link
Member

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?

@DStranger
Copy link
Author

@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
Copy link
Author

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

@bryce-anderson
Copy link
Member

Interesting.

@SystemFw
Copy link
Member

SystemFw commented Feb 15, 2018

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:
http4s/http4s#1648

@DStranger
Copy link
Author

@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.

@SystemFw
Copy link
Member

SystemFw commented Feb 16, 2018

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

@Igosuki
Copy link
Contributor

Igosuki commented Apr 25, 2018

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

@Igosuki
Copy link
Contributor

Igosuki commented Jun 7, 2018

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

@nightscape
Copy link
Contributor

nightscape commented Jun 22, 2018

@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 😉

@DStranger
Copy link
Author

@nightscape did you happen to find a workaround? )

@nightscape
Copy link
Contributor

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

@Igosuki
Copy link
Contributor

Igosuki commented Sep 3, 2018

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
}

@SystemFw
Copy link
Member

SystemFw commented Sep 3, 2018

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

@Igosuki
Copy link
Contributor

Igosuki commented Sep 3, 2018

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
Copy link
Contributor

Igosuki commented Sep 3, 2018

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.

@chuwy
Copy link
Contributor

chuwy commented Sep 20, 2018

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

@Igosuki
Copy link
Contributor

Igosuki commented Oct 9, 2018

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

@ChetanBhasin
Copy link

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"))
        }
    }

@zarthross
Copy link
Member

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants