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

Add scala 3 support for uPickle, Jackson and Circe #757

Open
wants to merge 3 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
@@ -0,0 +1,84 @@
/*
* Copyright 2015 Heiko Seeberger
*
* 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 de.heikoseeberger.akkahttpcirce

import akka.actor.ActorSystem
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.scaladsl.{ Sink, Source }
import io.circe.Encoder
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

final class CirceSupportSpec2
extends AsyncWordSpec
with Matchers
with BeforeAndAfterAll
with ScalaFutures
with EitherValues {

import CirceSupportSpec._

private implicit val system: ActorSystem = ActorSystem()

/**
* Specs common to both [[FailFastCirceSupport]] and [[ErrorAccumulatingCirceSupport]]
*/
private def commonCirceSupport(support: BaseCirceSupport) = {
import io.circe.generic.auto._
import support._

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

}

"FailFastCirceSupport" should {
behave like commonCirceSupport(FailFastCirceSupport)
}

"ErrorAccumulatingCirceSupport" should {
behave like commonCirceSupport(ErrorAccumulatingCirceSupport)

}

override protected def afterAll(): Unit = {
Await.ready(system.terminate(), 42.seconds)
super.afterAll()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2015 Heiko Seeberger
*
* 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 de.heikoseeberger.akkahttpcirce

import akka.actor.ActorSystem
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.scaladsl.{ Sink, Source }
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

final class CirceSupportSpec2

Choose a reason for hiding this comment

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

could you rename the file to CirceSupportSpec2.scala (likewise in scala-2 dir)?

extends AsyncWordSpec
with Matchers
with BeforeAndAfterAll
with ScalaFutures
with EitherValues {

import CirceSupportSpec._

private implicit val system: ActorSystem = ActorSystem()

/**
* Specs common to both [[FailFastCirceSupport]] and [[ErrorAccumulatingCirceSupport]]
*/
private def commonCirceSupport(support: BaseCirceSupport) = {
import io.circe.generic.auto._
import support._

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

}

"FailFastCirceSupport" should {
behave like commonCirceSupport(FailFastCirceSupport)
}

"ErrorAccumulatingCirceSupport" should {
behave like commonCirceSupport(ErrorAccumulatingCirceSupport)

}

override protected def afterAll(): Unit = {
Await.ready(system.terminate(), 42.seconds)
super.afterAll()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,18 @@ 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.stream.scaladsl.{ Sink, Source }
import akka.http.scaladsl.unmarshalling.{ Unmarshal, Unmarshaller }
import cats.data.{ NonEmptyList, ValidatedNel }
import io.circe.{ DecodingFailure, Encoder, ParsingFailure, Printer }
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 @@ -79,24 +72,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
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ object ExampleApp {
private final case class Foo(bar: String)

def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val system: ActorSystem = ActorSystem()

Http().newServerAt("127.0.0.1", 8000).bindFlow(route)

Expand All @@ -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 @@ -20,29 +20,27 @@ import akka.http.javadsl.common.JsonEntityStreamingSupport
import akka.http.javadsl.marshallers.jackson.Jackson
import akka.http.scaladsl.common.EntityStreamingSupport
import akka.http.scaladsl.marshalling._
import akka.http.scaladsl.model.{ ContentTypeRange, HttpEntity, MediaType, MessageEntity }
import akka.http.scaladsl.model.MediaTypes.`application/json`
import akka.http.scaladsl.model.{ ContentTypeRange, HttpEntity, MediaType, MessageEntity }
import akka.http.scaladsl.unmarshalling.{ FromEntityUnmarshaller, Unmarshal, Unmarshaller }
import akka.http.scaladsl.util.FastFuture
import akka.stream.scaladsl.{ Flow, Source }
import akka.util.ByteString
import com.fasterxml.jackson.core.`type`.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import java.lang.reflect.{ ParameterizedType, Type => JType }
import scala.collection.immutable.Seq
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.scala.{ ClassTagExtensions, DefaultScalaModule, JavaTypeable }

import scala.concurrent.Future
import scala.reflect.runtime.universe._
import scala.reflect.ClassTag
import scala.util.Try
import scala.util.control.NonFatal

/**
* Automatic to and from JSON marshalling/unmarshalling usung an in-scope Jackon's ObjectMapper
* Automatic to and from JSON marshalling/unmarshalling using an in-scope Jackson ObjectMapper
*/
object JacksonSupport extends JacksonSupport {

val defaultObjectMapper: ObjectMapper =
new ObjectMapper().registerModule(DefaultScalaModule)
val defaultObjectMapper: ObjectMapper with ClassTagExtensions =
JsonMapper.builder().addModule(DefaultScalaModule).build() :: ClassTagExtensions
}

/**
Expand All @@ -67,24 +65,6 @@ trait JacksonSupport {
case (data, charset) => data.decodeString(charset.nioCharset.name)
}

private def typeReference[T: TypeTag] = {
val t = typeTag[T]
val mirror = t.mirror
def mapType(t: Type): JType =
if (t.typeArgs.isEmpty)
mirror.runtimeClass(t)
else
new ParameterizedType {
def getRawType() = mirror.runtimeClass(t)
def getActualTypeArguments() = t.typeArgs.map(mapType).toArray
def getOwnerType() = null
}

new TypeReference[T] {
override def getType = mapType(t.tpe)
}
}

private def sourceByteStringMarshaller(
mediaType: MediaType.WithFixedCharset
): Marshaller[SourceOf[ByteString], MessageEntity] =
Expand Down Expand Up @@ -116,11 +96,10 @@ trait JacksonSupport {
/**
* HTTP entity => `A`
*/
implicit def unmarshaller[A](implicit
ct: TypeTag[A],
objectMapper: ObjectMapper = defaultObjectMapper
implicit def unmarshaller[A: JavaTypeable](implicit
objectMapper: ObjectMapper with ClassTagExtensions = defaultObjectMapper
): FromEntityUnmarshaller[A] =
jsonStringUnmarshaller.map(data => objectMapper.readValue(data, typeReference[A]))
jsonStringUnmarshaller.map(data => objectMapper.readValue[A](data))

/**
* `A` => HTTP entity
Expand All @@ -138,12 +117,11 @@ trait JacksonSupport {
* @return
* unmarshaller for any `A` value
*/
implicit def fromByteStringUnmarshaller[A](implicit
ct: TypeTag[A],
objectMapper: ObjectMapper = defaultObjectMapper
implicit def fromByteStringUnmarshaller[A: ClassTag](implicit
objectMapper: ObjectMapper with ClassTagExtensions = defaultObjectMapper
): Unmarshaller[ByteString, A] =
Unmarshaller { _ => bs =>
Future.fromTry(Try(objectMapper.readValue(bs.toArray, typeReference[A])))
Future.fromTry(Try(objectMapper.readValue[A](bs.toArray)))
}

/**
Expand All @@ -154,9 +132,8 @@ trait JacksonSupport {
* @return
* unmarshaller for `Source[A, _]`
*/
implicit def sourceUnmarshaller[A](implicit
ct: TypeTag[A],
objectMapper: ObjectMapper = defaultObjectMapper,
implicit def sourceUnmarshaller[A: ClassTag](implicit
objectMapper: ObjectMapper with ClassTagExtensions = defaultObjectMapper,
support: JsonEntityStreamingSupport = EntityStreamingSupport.json()
): FromEntityUnmarshaller[SourceOf[A]] =
Unmarshaller
Expand Down Expand Up @@ -187,7 +164,6 @@ trait JacksonSupport {
* marshaller for any `SourceOf[A]` value
*/
implicit def sourceMarshaller[A](implicit
ct: TypeTag[A],
objectMapper: ObjectMapper = defaultObjectMapper,
support: JsonEntityStreamingSupport = EntityStreamingSupport.json()
): ToEntityMarshaller[SourceOf[A]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import akka.http.scaladsl.model.HttpRequest
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.scaladsl.Source

import scala.concurrent.Await
import scala.concurrent.duration._
import scala.io.StdIn
Expand All @@ -31,7 +32,7 @@ object ExampleApp {
final case class Foo(bar: String)

def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val system: ActorSystem = ActorSystem()

// provide an implicit ObjectMapper if you want serialization/deserialization to use it
// instead of a default ObjectMapper configured only with DefaultScalaModule provided
Expand Down Expand Up @@ -64,7 +65,7 @@ object ExampleApp {
}
} ~ pathPrefix("stream") {
post {
entity(as[SourceOf[Foo]]) { fooSource: SourceOf[Foo] =>
entity(as[SourceOf[Foo]]) { (fooSource: SourceOf[Foo]) =>
complete(fooSource.throttle(1, 2.seconds))
}
} ~ get {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import akka.stream.scaladsl.{ Sink, Source }
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec

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

Expand All @@ -40,7 +41,7 @@ final class JacksonSupportSpec extends AsyncWordSpec with Matchers with BeforeAn
import JacksonSupport._
import JacksonSupportSpec._

private implicit val system = ActorSystem()
private implicit val system: ActorSystem = ActorSystem()

"JacksonSupport" should {
"should enable marshalling and unmarshalling of case classes" in {
Expand Down
Loading