Skip to content

Commit

Permalink
Don't invoke magnolia for built-in customs instances (e.g. encodeList)
Browse files Browse the repository at this point in the history
  • Loading branch information
note committed Mar 14, 2021
1 parent 6368343 commit c3aad0f
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 12 deletions.
17 changes: 17 additions & 0 deletions src/main/scala/pl/msitko/dhallj/generic/Exported.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package pl.msitko.dhallj.generic

// Workaround for issues with magnolia trying to derive instances having custom encoder/decoders
// Read more about that workaround here: https://github.com/propensive/magnolia/issues/107#issuecomment-589289260
case class Exported[A](instance: A) extends AnyVal

//import scala.language.higherKinds
import magnolia.Magnolia
import scala.reflect.macros.whitebox

object ExportedMagnolia {

def exportedMagnolia[TC[_], A: c.WeakTypeTag](c: whitebox.Context): c.Expr[Exported[TC[A]]] = {
val magnoliaTree = c.Expr[TC[A]](Magnolia.gen[A](c))
c.universe.reify(Exported(magnoliaTree.splice))
}
}
24 changes: 20 additions & 4 deletions src/main/scala/pl/msitko/dhallj/generic/decoder/auto.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package pl.msitko.dhallj.generic.decoder

import magnolia.{CaseClass, Magnolia, SealedTrait}
import magnolia.{CaseClass, SealedTrait}
import org.dhallj.codec.Decoder
import pl.msitko.dhallj.generic.GenericDecoder
import pl.msitko.dhallj.generic.{Exported, ExportedMagnolia, GenericDecoder}

object auto {
object auto extends ExportedDecoder {
type Typeclass[T] = Decoder[T]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
Expand All @@ -13,5 +13,21 @@ object auto {
def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
GenericDecoder.dispatch(sealedTrait)

implicit def deriveDecoder[T]: Typeclass[T] = macro Magnolia.gen[T]
implicit def deriveDecoder[T]: Exported[Decoder[T]] = macro ExportedMagnolia.exportedMagnolia[Decoder, T]

// Those are definedin an object in dhallj and therefore cannot be brought by inheritance
// TODO: Open a PR in dhallj
implicit val decodeLong: Decoder[Long] = Decoder.decodeLong
implicit val decodeInt: Decoder[Int] = Decoder.decodeInt
implicit val decodeBigInt: Decoder[BigInt] = Decoder.decodeBigInt
implicit val decodeDouble: Decoder[Double] = Decoder.decodeDouble
implicit val decodeString: Decoder[String] = Decoder.decodeString
implicit val decodeBoolean: Decoder[Boolean] = Decoder.decodeBoolean
implicit def decodeOption[A: Decoder]: Decoder[Option[A]] = Decoder.decodeOption
implicit def decodeVector[A: Decoder]: Decoder[Vector[A]] = Decoder.decodeVector
implicit def decodeList[A: Decoder]: Decoder[List[A]] = Decoder.decodeList
}

trait ExportedDecoder {
implicit def exportedDecoder[A](implicit exported: Exported[Decoder[A]]): Decoder[A] = exported.instance
}
24 changes: 20 additions & 4 deletions src/main/scala/pl/msitko/dhallj/generic/encoder/auto.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package pl.msitko.dhallj.generic.encoder

import magnolia.{CaseClass, Magnolia, SealedTrait}
import magnolia.{CaseClass, SealedTrait}
import org.dhallj.codec.Encoder
import pl.msitko.dhallj.generic.GenericEncoder
import pl.msitko.dhallj.generic.{Exported, ExportedMagnolia, GenericEncoder}

object auto {
object auto extends ExportedEncoder {
type Typeclass[T] = Encoder[T]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
Expand All @@ -13,5 +13,21 @@ object auto {
def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
GenericEncoder.dispatch(sealedTrait)

implicit def deriveEncoder[T]: Typeclass[T] = macro Magnolia.gen[T]
implicit def deriveEncoder[T]: Exported[Encoder[T]] = macro ExportedMagnolia.exportedMagnolia[Encoder, T]

// Those are definedin an object in dhallj and therefore cannot be brought by inheritance
// TODO: Open a PR in dhallj
implicit val encodeLong: Encoder[Long] = Encoder.encodeLong
implicit val encodeInt: Encoder[Int] = Encoder.encodeInt
implicit val encodeBigInt: Encoder[BigInt] = Encoder.encodeBigInt
implicit val encodeDouble: Encoder[Double] = Encoder.encodeDouble
implicit val encodeString: Encoder[String] = Encoder.encodeString
implicit val encodeBoolean: Encoder[Boolean] = Encoder.encodeBoolean
implicit def encodeOption[A: Encoder]: Encoder[Option[A]] = Encoder.encodeOption
implicit def encodeVector[A: Encoder]: Encoder[Vector[A]] = Encoder.encodeVector
implicit def encodeList[A: Encoder]: Encoder[List[A]] = Encoder.encodeList
}

trait ExportedEncoder {
implicit def exportedEncoder[A](implicit exported: Exported[Encoder[A]]): Encoder[A] = exported.instance
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ class AutoDeriveDecoderSpec extends munit.FunSuite with Fixtures {
assertEquals(decoded, OnOrOff2.Off())
}

// It wasn't working without ExportedMagnolia trick (see https://github.com/propensive/magnolia/issues/107#issuecomment-589289260)
// The point is that it should use custom decoder defined in dhallj and not try to invoke magnolia derivation
test("Work if the top level type has custom encoder") {
val decoded =
"""
|[
| (<Error1 : {msg : Text} | Error2 : {code : Natural, code2 : Natural}>.Error1) {msg = "abc"},
| (<Error1 : {msg : Text} | Error2 : {code : Natural, code2 : Natural}>.Error2) {code = 123, code2 = 456}
|]""".stripMargin.decode[List[Error]]

assertEquals(decoded, List(Error1("abc"), Error2(code = 123, code2 = 456)))
}

test("Decoding error should be comprehensible for deeply nested case classes".ignore) {
val input =
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package pl.msitko.dhallj.generic.encoder

import org.dhallj.ast.{NaturalLiteral, RecordLiteral}
import org.dhallj.codec.Encoder
import org.dhallj.codec.syntax._
import org.dhallj.core.Expr
Expand Down Expand Up @@ -43,6 +44,21 @@ class AutoDeriveEncoderSpec extends munit.FunSuite with Fixtures {
"""{errors = [(<Error1 : {msg : Text} | Error2 : {code : Natural, code2 : Natural}>.Error1) {msg = "abc"}, (<Error1 : {msg : Text} | Error2 : {code : Natural, code2 : Natural}>.Error2) {code = 123, code2 = 456}]}""")
}

test("Do not invoke magnolia and use custom instance if one is provided manually") {
implicit val errorEncoder: Encoder[StatusCode] = new Encoder[StatusCode] {
override def encode(value: StatusCode, target: Option[Expr]): Expr =
RecordLiteral(Map("myStatusCode" -> NaturalLiteral(value.code).get))
override def dhallType(value: Option[StatusCode], target: Option[Expr]): Expr = ???
}

val res = encode(StatusCode(201))

assertEquals(
res,
"""{myStatusCode = 201}"""
)
}

test("Generate dhall type for sealed traits") {
val typeExpr = dhallType[Error]

Expand All @@ -52,11 +68,9 @@ class AutoDeriveEncoderSpec extends munit.FunSuite with Fixtures {
)
}

// It wasn't working without ExportedMagnolia trick (see https://github.com/propensive/magnolia/issues/107#issuecomment-589289260)
// The point is that it should use custom decoder defined in dhallj and not try to invoke magnolia derivation
test("Work if the top level type has custom encoder") {
// TODO: doesn't work without that import
import Encoder._

// BTW it wouldn't work if we don't `import org.dhallj.codec.Encoder._`
val typeExpr = dhallType[List[Error]]

assertEquals(
Expand Down

0 comments on commit c3aad0f

Please sign in to comment.