Skip to content

Commit

Permalink
Fix pickler deserialization when enum cases are not alphabetically so…
Browse files Browse the repository at this point in the history
…rted (#4093)
  • Loading branch information
adamw authored Oct 9, 2024
1 parent 9f765c8 commit 9fcc53b
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ class CreateDerivedEnumerationPickler[T: ClassTag](
val tapirPickle = new TapirPickle[T] {
override lazy val reader: Reader[T] = {
val readersForPossibleValues: Seq[TaggedReader[T]] =
validator.possibleValues.zip(childReadWriters.map(_._1)).map { case (enumValue, reader) =>
TaggedReader.Leaf[T](encode(enumValue).toString, reader.asInstanceOf[LeafWrapper[_]].r.asInstanceOf[Reader[T]])
childReadWriters.map { case (enumValue, reader, _) =>
TaggedReader.Leaf[T](encode(enumValue.asInstanceOf[T]).toString, reader.asInstanceOf[LeafWrapper[_]].r.asInstanceOf[Reader[T]])
}
new TaggedReader.Node[T](readersForPossibleValues: _*)
}

override lazy val writer: Writer[T] =
new TaggedWriter.Node[T](childReadWriters.map(_._2.asInstanceOf[TaggedWriter[T]]): _*) {
new TaggedWriter.Node[T](childReadWriters.map(_._3.asInstanceOf[TaggedWriter[T]]): _*) {
override def findWriterWithKey(v: Any): (String, String, ObjectWriter[T]) =
val (tagKey, tagValue, writer) = super.findWriterWithKey(v)
// Here our custom encoding transforms the value of a singleton object
Expand All @@ -57,15 +57,17 @@ class CreateDerivedEnumerationPickler[T: ClassTag](
new Pickler[T](tapirPickle, schema)
}

private inline def buildEnumerationReadWriters[T: ClassTag, Cases <: Tuple]: List[(Types#Reader[_], Types#Writer[_])] =
private inline def buildEnumerationReadWriters[T: ClassTag, Cases <: Tuple]: List[(Any, Types#Reader[_], Types#Writer[_])] =
inline erasedValue[Cases] match {
case _: (enumerationCase *: enumerationCasesTail) =>
val processedHead = readWriterForEnumerationCase[enumerationCase]
val (reader, writer) = readWriterForEnumerationCase[enumerationCase]
val processedTail = buildEnumerationReadWriters[T, enumerationCasesTail]
(processedHead +: processedTail)
((productValue[enumerationCase], reader, writer) +: processedTail)
case _: EmptyTuple.type => Nil
}

private inline def productValue[E] = summonFrom { case m: Mirror.ProductOf[E] => m.fromProduct(EmptyTuple) }

/** Enumeration cases and case objects in an enumeration need special writers and readers, which are generated here, instead of being
* taken from child picklers. For example, for enum Color and case values Red and Blue, a Writer should just use the object Red or Blue
* and serialize it to "Red" or "Blue". If user needs to encode the singleton object using a custom function, this happens on a higher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,7 @@ object Fixtures:
sealed trait NotAllSealedVariant
case object NotAllSealedVariantA extends NotAllSealedVariant
case class NotAllSealedVariantB(innerField: Int) extends NotAllSealedVariant

enum NotAlphabetical:
case Xyz
case Fgh
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,18 @@ class PicklerEnumTest extends AnyFlatSpec with Matchers {
18 -> picklerMagenta
)""")
}

it should "encode and decode an enum where the cases are not alphabetically sorted" in {
// given
import generic.auto.* // for Pickler auto-derivation

// when
val testPickler = Pickler.derived[NotAlphabetical]
val codec = testPickler.toCodec
val encoded = codec.encode(NotAlphabetical.Xyz)

// then
encoded shouldBe """"Xyz""""
codec.decode(encoded) shouldBe Value(NotAlphabetical.Xyz)
}
}

0 comments on commit 9fcc53b

Please sign in to comment.