Repository: http4s/http4s
Pull request #3965 (2020-11-28 18:01:15)

ISSUE-3948 Port circe to cats-effect-3
Still need to migrate to munit

benderpremier: I will try to do the same migration to MUnit as is #3963 , what's the right way to do it though ? Should I wait for 3963 to be merged to the `0.21` branch and then merge those in the `cats-effect-3` branch? Or should I copy the change done in the `Http4sSuite.scala` ?
rossabaker: We want to go series/0.21 -> main -> cats-effect-3.  That first step is going to be a nightmare, but @cquiroz is working on that.

It looks like I'm wrong about munit being the (immediate) solution to that `checkAll`.  I think we might be able to bring this in for a landing just by running scalafmt, and we can get it all moved over to munit in a subsequent step.


benderpremier: I think we still have an issue with a dependency on https://github.com/ChristopherDavenport/vault which we have in `org.http4s.Message`, the [newKey](https://github.com/http4s/http4s/blob/cats-effect-3/core/src/main/scala/org/http4s/Message.scala#L498-L501) methods need an implicit `Sync`. We probably need to publish vault against CE3 right ?
rossabaker: Oh, drat.  You're right.  I helped with the Dotty release of that, but we need CE3:

https://github.com/ChristopherDavenport/vault/issues/159
rossabaker: #4040 effectively gives us a CE3 vault.  If we merge in the cats-effect-3 branch, we should be able to finally get this finished up.  @benderpremier, do you still have time and interest in finishing this one up?  Thanks!
benderpremier: Hi @rossabaker , yes I'd love to finish that one. I will have some time in the coming days to do so.

Patch:
diff --git a/build.sbt b/build.sbt
index d665145070..5452c9c38a 100644
--- a/build.sbt
+++ b/build.sbt
@@ -36,8 +36,8 @@ lazy val modules: List[ProjectReference] = List(
   theDsl,
    jawn,
    argonaut,
-  boopickle,
-  // circe,
+   boopickle,
+   circe,
    json4s,
    json4sNative,
    json4sJackson,
@@ -421,7 +421,7 @@ lazy val circe = libraryProject("circe")
     libraryDependencies ++= Seq(
       circeCore,
       circeJawn,
-      circeTesting % Test,
+      circeTesting % Test
     )
   )
   .dependsOn(core, testing % "test->test", jawn % "compile;test->test")
diff --git a/circe/src/main/scala/org/http4s/circe/CirceEntityDecoder.scala b/circe/src/main/scala/org/http4s/circe/CirceEntityDecoder.scala
index e35e433bf7..0a931c3107 100644
--- a/circe/src/main/scala/org/http4s/circe/CirceEntityDecoder.scala
+++ b/circe/src/main/scala/org/http4s/circe/CirceEntityDecoder.scala
@@ -16,14 +16,14 @@
 
 package org.http4s.circe
 
-import cats.effect.Sync
+import cats.effect.Concurrent
 import io.circe.Decoder
 import org.http4s.EntityDecoder
 
 /** Derive [[EntityDecoder]] if implicit [[Decoder]] is in the scope without need to explicitly call `jsonOf`
   */
 trait CirceEntityDecoder {
-  implicit def circeEntityDecoder[F[_]: Sync, A: Decoder]: EntityDecoder[F, A] = jsonOf[F, A]
+  implicit def circeEntityDecoder[F[_]: Concurrent, A: Decoder]: EntityDecoder[F, A] = jsonOf[F, A]
 }
 
 object CirceEntityDecoder extends CirceEntityDecoder
diff --git a/circe/src/main/scala/org/http4s/circe/CirceInstances.scala b/circe/src/main/scala/org/http4s/circe/CirceInstances.scala
index 2240fc23ad..6bde1ec816 100644
--- a/circe/src/main/scala/org/http4s/circe/CirceInstances.scala
+++ b/circe/src/main/scala/org/http4s/circe/CirceInstances.scala
@@ -20,7 +20,7 @@ package circe
 import java.nio.ByteBuffer
 
 import cats.data.NonEmptyList
-import cats.effect.Sync
+import cats.effect.Concurrent
 import cats.syntax.either._
 import fs2.{Chunk, Pull, Stream}
 import io.circe._
@@ -43,13 +43,13 @@ trait CirceInstances extends JawnInstances {
   protected def jsonDecodeError: (Json, NonEmptyList[DecodingFailure]) => DecodeFailure =
     CirceInstances.defaultJsonDecodeError
 
-  def jsonDecoderIncremental[F[_]: Sync]: EntityDecoder[F, Json] =
+  def jsonDecoderIncremental[F[_]: Concurrent]: EntityDecoder[F, Json] =
     this.jawnDecoder[F, Json]
 
-  def jsonDecoderByteBuffer[F[_]: Sync]: EntityDecoder[F, Json] =
+  def jsonDecoderByteBuffer[F[_]: Concurrent]: EntityDecoder[F, Json] =
     EntityDecoder.decodeBy(MediaType.application.json)(jsonDecoderByteBufferImpl[F])
 
-  private def jsonDecoderByteBufferImpl[F[_]: Sync](m: Media[F]): DecodeResult[F, Json] =
+  private def jsonDecoderByteBufferImpl[F[_]: Concurrent](m: Media[F]): DecodeResult[F, Json] =
     EntityDecoder.collectBinary(m).subflatMap { chunk =>
       val bb = ByteBuffer.wrap(chunk.toArray)
       if (bb.hasRemaining)
@@ -62,10 +62,10 @@ trait CirceInstances extends JawnInstances {
     }
 
   // default cutoff value is based on benchmarks results
-  implicit def jsonDecoder[F[_]: Sync]: EntityDecoder[F, Json] =
+  implicit def jsonDecoder[F[_]: Concurrent]: EntityDecoder[F, Json] =
     jsonDecoderAdaptive(cutoff = 100000, MediaType.application.json)
 
-  def jsonDecoderAdaptive[F[_]: Sync](
+  def jsonDecoderAdaptive[F[_]: Concurrent](
       cutoff: Long,
       r1: MediaRange,
       rs: MediaRange*): EntityDecoder[F, Json] =
@@ -77,11 +77,11 @@ trait CirceInstances extends JawnInstances {
       }
     }
 
-  def jsonOf[F[_]: Sync, A: Decoder]: EntityDecoder[F, A] =
+  def jsonOf[F[_]: Concurrent, A: Decoder]: EntityDecoder[F, A] =
     jsonOfWithMedia(MediaType.application.json)
 
   def jsonOfWithMedia[F[_], A](r1: MediaRange, rs: MediaRange*)(implicit
-      F: Sync[F],
+      F: Concurrent[F],
       decoder: Decoder[A]): EntityDecoder[F, A] =
     jsonDecoderAdaptive[F](cutoff = 100000, r1, rs: _*).flatMapR { json =>
       decoder
@@ -97,7 +97,9 @@ trait CirceInstances extends JawnInstances {
     * In case of a failure, returns an [[InvalidMessageBodyFailure]] with the cause containing
     * a [[DecodingFailures]] exception, from which the errors can be extracted.
     */
-  def accumulatingJsonOf[F[_], A](implicit F: Sync[F], decoder: Decoder[A]): EntityDecoder[F, A] =
+  def accumulatingJsonOf[F[_], A](implicit
+      F: Concurrent[F],
+      decoder: Decoder[A]): EntityDecoder[F, A] =
     jsonDecoder[F].flatMapR { json =>
       decoder
         .decodeAccumulating(json.hcursor)
@@ -287,10 +289,10 @@ object CirceInstances {
     def asJsonDecode[A](implicit F: JsonDecoder[F], decoder: Decoder[A]): F[A] =
       F.asJsonDecode(req)
 
-    def decodeJson[A](implicit F: Sync[F], decoder: Decoder[A]): F[A] =
+    def decodeJson[A](implicit F: Concurrent[F], decoder: Decoder[A]): F[A] =
       req.as(F, jsonOf[F, A])
 
-    def json(implicit F: Sync[F]): F[Json] =
+    def json(implicit F: Concurrent[F]): F[Json] =
       req.as(F, jsonDecoder[F])
   }
 }
diff --git a/circe/src/main/scala/org/http4s/circe/JsonDecoder.scala b/circe/src/main/scala/org/http4s/circe/JsonDecoder.scala
index 61d7a30db7..b4be61d78d 100644
--- a/circe/src/main/scala/org/http4s/circe/JsonDecoder.scala
+++ b/circe/src/main/scala/org/http4s/circe/JsonDecoder.scala
@@ -16,7 +16,7 @@
 
 package org.http4s.circe
 
-import cats.effect.Sync
+import cats.effect.Concurrent
 import org.http4s._
 import io.circe._
 
@@ -33,7 +33,7 @@ trait JsonDecoder[F[_]] {
 object JsonDecoder {
   def apply[F[_]](implicit ev: JsonDecoder[F]): JsonDecoder[F] = ev
 
-  implicit def impl[F[_]: Sync]: JsonDecoder[F] =
+  implicit def impl[F[_]: Concurrent]: JsonDecoder[F] =
     new JsonDecoder[F] {
       def asJson(m: Message[F]): F[Json] = m.as[Json]
       def asJsonDecode[A: Decoder](m: Message[F]): F[A] = m.decodeJson
diff --git a/circe/src/main/scala/org/http4s/circe/middleware/JsonDebugErrorHandler.scala b/circe/src/main/scala/org/http4s/circe/middleware/JsonDebugErrorHandler.scala
index fcdfaf3015..37e610ff93 100644
--- a/circe/src/main/scala/org/http4s/circe/middleware/JsonDebugErrorHandler.scala
+++ b/circe/src/main/scala/org/http4s/circe/middleware/JsonDebugErrorHandler.scala
@@ -32,7 +32,7 @@ object JsonDebugErrorHandler {
     org.log4s.getLogger("org.http4s.circe.middleware.jsondebugerrorhandler.service-errors")
 
   // Can be parametric on my other PR is merged.
-  def apply[F[_]: Sync, G[_]](
+  def apply[F[_]: Concurrent, G[_]](
       service: Kleisli[F, Request[G], Response[G]],
       redactWhen: CIString => Boolean = Headers.SensitiveHeaders.contains
   ): Kleisli[F, Request[G], Response[G]] =


Test patch:
diff --git a/circe/src/test/scala/org/http4s/circe/CirceSpec.scala b/circe/src/test/scala/org/http4s/circe/CirceSpec.scala
index 04aa7110be..26823439ef 100644
--- a/circe/src/test/scala/org/http4s/circe/CirceSpec.scala
+++ b/circe/src/test/scala/org/http4s/circe/CirceSpec.scala
@@ -17,17 +17,16 @@
 package org.http4s
 package circe.test // Get out of circe package so we can import custom instances
 
-import cats.effect.IO
-import cats.effect.laws.util.TestContext
-import cats.effect.laws.util.TestInstances._
-import cats.instances.boolean._
-import io.circe._
-import io.circe.testing.instances._
-import org.http4s.circe._
-import org.http4s.laws.discipline.EntityCodecTests
+//import cats.effect.IO
+//import cats.effect.testkit.TestContext
+//import cats.instances.boolean._
+//import io.circe._
+//import io.circe.testing.instances._
+//import org.http4s.circe._
+//import org.http4s.laws.discipline.EntityCodecTests
 
 // Originally based on ArgonautSpec
 class CirceSpec extends Http4sSpec {
-  implicit val testContext = TestContext()
-  checkAll("EntityCodec[IO, Json]", EntityCodecTests[IO, Json].entityCodec)
+//  implicit val testContext = TestContext()
+//  checkAll("EntityCodec[IO, Json]", EntityCodecTests[IO, Json].entityCodec)
 }
diff --git a/circe/src/test/scala/org/http4s/circe/CirceSuite.scala b/circe/src/test/scala/org/http4s/circe/CirceSuite.scala
index 65fc392e9d..2a3bcdfe5e 100644
--- a/circe/src/test/scala/org/http4s/circe/CirceSuite.scala
+++ b/circe/src/test/scala/org/http4s/circe/CirceSuite.scala
@@ -1,7 +1,17 @@
 /*
- * Copyright 2013-2020 http4s.org
+ * Copyright 2015 http4s.org
  *
- * SPDX-License-Identifier: Apache-2.0
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.http4s
@@ -9,7 +19,7 @@ package circe.test // Get out of circe package so we can import custom instances
 
 import cats.data.NonEmptyList
 import cats.effect.IO
-import cats.effect.laws.util.TestContext
+import cats.effect.testkit.TestContext
 import cats.syntax.all._
 import fs2.Stream
 import io.circe._