Skip to content
This repository has been archived by the owner on Jul 30, 2024. It is now read-only.

http-circe: scala 3 #838

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ final class ArgonautSupportSpec extends AsyncWordSpec with Matchers with BeforeA
val `application/json-home` =
MediaType.applicationWithFixedCharset("json-home", HttpCharsets.`UTF-8`, "json-home")

final object CustomArgonautSupport extends ArgonautSupport {
object CustomArgonautSupport extends ArgonautSupport {
override def unmarshallerContentTypes = List(`application/json`, `application/json-home`)
}
import CustomArgonautSupport._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import scala.io.StdIn

object ExampleApp {

final object Foo {
object Foo {
implicit val fooCodec: CodecJson[Foo] =
casecodec1(Foo.apply, (f: Foo) => Option(f.bar))("bar")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ trait FailFastCirceSupport extends BaseCirceSupport with FailFastUnmarshaller
*/
object ErrorAccumulatingCirceSupport extends ErrorAccumulatingCirceSupport {
final case class DecodingFailures(failures: NonEmptyList[DecodingFailure]) extends Exception {
override def getMessage = failures.toList.map(_.show).mkString("\n")
override def getMessage: String = failures.toList.map(_.show).mkString("\n")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,20 @@ package de.heikoseeberger.akkahttpcirce

import akka.actor.ActorSystem
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model.{
ContentTypeRange,
HttpCharsets,
HttpEntity,
MediaType,
RequestEntity,
ResponseEntity
}
import akka.http.scaladsl.model.ContentTypes.{ `application/json`, `text/plain(UTF-8)` }
import akka.http.scaladsl.unmarshalling.{ Unmarshal, Unmarshaller }
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling.Unmarshaller.UnsupportedContentTypeException
import akka.http.scaladsl.unmarshalling.{ Unmarshal, Unmarshaller }
import akka.stream.scaladsl.{ Sink, Source }
import cats.data.{ NonEmptyList, ValidatedNel }
import io.circe.{ DecodingFailure, Encoder, ParsingFailure, Printer }
import cats.implicits.toShow
import io.circe.CursorOp.DownField
import org.scalatest.{ BeforeAndAfterAll, EitherValues }
import io.circe.{ DecodingFailure, ParsingFailure, Printer }
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec
import org.scalatest.{ BeforeAndAfterAll, EitherValues }

import scala.concurrent.Await
import scala.concurrent.duration.DurationInt

Expand Down Expand Up @@ -67,7 +62,7 @@ final class CirceSupportSpec
/**
* Specs common to both [[FailFastCirceSupport]] and [[ErrorAccumulatingCirceSupport]]
*/
private def commonCirceSupport(support: BaseCirceSupport) = {
private def commonCirceSupport(support: BaseCirceSupport): Unit = {
import io.circe.generic.auto._
import support._

Expand All @@ -79,24 +74,6 @@ final class CirceSupportSpec
.map(_ shouldBe foo)
}

"enable streamed marshalling and unmarshalling for json arrays" in {
val foos = (0 to 100).map(i => Foo(s"bar-$i")).toList

// Don't know why, the encoder is not resolving alongside the marshaller
// this only happens if we use the implicits from BaseCirceSupport
// so, tried to create it before and guess what? it worked.
// not sure if this is a bug, but, the error is this:
// diverging implicit expansion for type io.circe.Encoder[A]
// [error] starting with lazy value encodeZoneOffset in object Encoder
implicit val e = implicitly[Encoder[Foo]]

Marshal(Source(foos))
.to[ResponseEntity]
.flatMap(entity => Unmarshal(entity).to[SourceOf[Foo]])
.flatMap(_.runWith(Sink.seq))
.map(_ shouldBe foos)
}

"provide proper error messages for requirement errors" in {
val entity = HttpEntity(`application/json`, """{ "bar": "baz" }""")
Unmarshal(entity)
Expand Down Expand Up @@ -145,6 +122,15 @@ final class CirceSupportSpec

behave like commonCirceSupport(FailFastCirceSupport)

"enable streamed marshalling and unmarshalling for json arrays" in {
val foos = (0 to 100).map(i => Foo(s"bar-$i")).toList
Marshal(Source(foos))
.to[ResponseEntity]
.flatMap(entity => Unmarshal(entity).to[SourceOf[Foo]])
.flatMap(_.runWith(Sink.seq))
.map(_ shouldBe foos)
}

"fail with a ParsingFailure when unmarshalling empty entities with safeUnmarshaller" in {
val entity = HttpEntity.empty(`application/json`)
Unmarshal(entity)
Expand All @@ -156,27 +142,30 @@ final class CirceSupportSpec

"fail-fast and return only the first unmarshalling error" in {
val entity = HttpEntity(`application/json`, """{ "a": 1, "b": 2 }""")
val error = DecodingFailure("String", List(DownField("a")))
val error =
DecodingFailure("Got value '1' with wrong type, expecting string", List(DownField("a")))
Unmarshal(entity)
.to[MultiFoo]
.failed
.map(_ shouldBe error)
.map(_.getMessage shouldBe error.getMessage())
}

"fail-fast and return only the first unmarshalling error with safeUnmarshaller" in {
val entity = HttpEntity(`application/json`, """{ "a": 1, "b": 2 }""")
val error = DecodingFailure("String", List(DownField("a")))
val error: io.circe.Error =
DecodingFailure("Got value '1' with wrong type, expecting string", List(DownField("a")))
Unmarshal(entity)
.to[Either[io.circe.Error, MultiFoo]]
.futureValue
.left
.value shouldBe error
.value
.getMessage shouldBe error.getMessage
}

"allow unmarshalling with passed in Content-Types" in {
val foo = Foo("bar")

final object CustomCirceSupport extends FailFastCirceSupport {
object CustomCirceSupport extends FailFastCirceSupport {
override def unmarshallerContentTypes: List[ContentTypeRange] =
List(`application/json`, `application/json-home`)
}
Expand All @@ -193,6 +182,16 @@ final class CirceSupportSpec

behave like commonCirceSupport(ErrorAccumulatingCirceSupport)

"enable streamed marshalling and unmarshalling for json arrays" in {
val foos = (0 to 100).map(i => Foo(s"bar-$i")).toList
Marshal(Source(foos))
.to[ResponseEntity]
.flatMap(entity => Unmarshal(entity).to[SourceOf[Foo]])
.flatMap(_.runWith(Sink.seq))
.map(_ shouldBe foos)

}

"fail with a NonEmptyList of Errors when unmarshalling empty entities with safeUnmarshaller" in {
val entity = HttpEntity.empty(`application/json`)
Unmarshal(entity)
Expand All @@ -207,8 +206,8 @@ final class CirceSupportSpec
val entity = HttpEntity(`application/json`, """{ "a": 1, "b": 2 }""")
val errors =
NonEmptyList.of(
DecodingFailure("String", List(DownField("a"))),
DecodingFailure("String", List(DownField("b")))
DecodingFailure("Got value '1' with wrong type, expecting string", List(DownField("a"))),
DecodingFailure("Got value '2' with wrong type, expecting string", List(DownField("b")))
)
val errorMessage = ErrorAccumulatingCirceSupport.DecodingFailures(errors).getMessage
Unmarshal(entity)
Expand All @@ -219,24 +218,31 @@ final class CirceSupportSpec

"accumulate and return all unmarshalling errors with safeUnmarshaller" in {
val entity = HttpEntity(`application/json`, """{ "a": 1, "b": 2 }""")
val errors =
val errors: NonEmptyList[DecodingFailure] =
NonEmptyList.of(
DecodingFailure("String", List(DownField("a"))),
DecodingFailure("String", List(DownField("b")))
DecodingFailure("Got value '1' with wrong type, expecting string", List(DownField("a"))),
DecodingFailure("Got value '2' with wrong type, expecting string", List(DownField("b")))
)
val errorMessage = ErrorAccumulatingCirceSupport.DecodingFailures(errors).getMessage
Unmarshal(entity)

val result: String = Unmarshal(entity)
.to[ValidatedNel[io.circe.Error, MultiFoo]]
.futureValue
.toEither
.left
.value shouldBe errors
.value
.collect { case df: DecodingFailure =>
df.show
}
.mkString("\n")

errorMessage shouldBe result
}

"allow unmarshalling with passed in Content-Types" in {
val foo = Foo("bar")

final object CustomCirceSupport extends ErrorAccumulatingCirceSupport {
object CustomCirceSupport extends ErrorAccumulatingCirceSupport {
override def unmarshallerContentTypes: List[ContentTypeRange] =
List(`application/json`, `application/json-home`)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ object ExampleApp {
}
} ~ pathPrefix("stream") {
post {
entity(as[SourceOf[Foo]]) { fooSource: SourceOf[Foo] =>
entity(as[SourceOf[Foo]]) { (fooSource: SourceOf[Foo]) =>
import sys._

Marshal(Source.single(Foo("a"))).to[RequestEntity]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ object Json4sSupport extends Json4sSupport {

sealed abstract class ShouldWritePretty

final object ShouldWritePretty {
final object True extends ShouldWritePretty
final object False extends ShouldWritePretty
object ShouldWritePretty {
object True extends ShouldWritePretty
object False extends ShouldWritePretty
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ final class Json4sSupportSpec extends AsyncWordSpec with Matchers with BeforeAnd
val `application/json-home` =
MediaType.applicationWithFixedCharset("json-home", HttpCharsets.`UTF-8`, "json-home")

final object CustomJson4sSupport extends Json4sSupport {
object CustomJson4sSupport extends Json4sSupport {
override def unmarshallerContentTypes = List(`application/json`, `application/json-home`)
}
import CustomJson4sSupport._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ final class JsoniterScalaSupportSpec extends AsyncWordSpec with Matchers with Be
val `application/json-home` =
MediaType.applicationWithFixedCharset("json-home", HttpCharsets.`UTF-8`, "json-home")

final object CustomJsoniterScalaSupport extends JsoniterScalaSupport {
object CustomJsoniterScalaSupport extends JsoniterScalaSupport {
override def unmarshallerContentTypes: List[ContentTypeRange] =
List(`application/json`, `application/json-home`)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ trait ZioJsonSupport {
Unmarshaller(_ =>
bs => {
val decoded = jd.decodeJsonStreamInput(ZStream.fromIterable(bs))
Unsafe.unsafeCompat(implicit u => rt.unsafe.runToFuture(decoded))
Unsafe.unsafe(implicit u => rt.unsafe.runToFuture(decoded))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gabriele83 I'm interested in using your changes in https://github.com/pjfanning/pekko-http-json.

Could you help by explaining why this ZIO change is useful? The rest of the PR is about Circe but I'd like to apply this change to pekko-http too if it is an improvement.

}
)

Expand Down
10 changes: 5 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ inThisBuild(
)

val withScala3 = Seq(
crossScalaVersions += "3.2.1",
crossScalaVersions += "3.3.1",
)

// *****************************************************************************
Expand Down Expand Up @@ -86,7 +86,7 @@ lazy val `akka-http-argonaut` =
lazy val `akka-http-circe` =
project
.enablePlugins(AutomateHeaderPlugin)
.settings(commonSettings)
.settings(commonSettings, withScala3)
.settings(
libraryDependencies ++= Seq(
library.akkaHttp,
Expand Down Expand Up @@ -227,11 +227,11 @@ lazy val commonSettings =
lazy val library =
new {
object Version {
val akka = "2.6.20"
val akkaHttp = "10.2.10"
val akka = "2.8.5"
val akkaHttp = "10.5.3"
val argonaut = "6.3.8"
val avro4s = "4.0.12"
val circe = "0.14.1"
val circe = "0.14.6"
val jacksonModuleScala = "2.13.1"
val json4s = "4.0.6"
val jsoniterScala = "2.17.9"
Expand Down