rho
rho copied to clipboard
Implicit EntityEncoder for circe Encoder breaks compilation
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.
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 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.
@bryce-anderson the problem resolves if I disable partial unification
Interesting.
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 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.
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
I have the same problem. circe.generic.auto works fine for EntityDecoders but not Encoders with Rho...
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 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 did you happen to find a workaround? )
Unfortunately not... I converted the problematic code to pure Http4s and left the rest in Rho.
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
}
Perhaps someone can open a PR to the rho
docs for this?
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.
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.
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
Yeah that's because with IO you can directly pull the concrete IO dsl, which isn't possible with just F : Effect
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 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.