Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pickler deserialization when enum cases are not alphabetically sorted #4093

Merged
merged 2 commits into from
Oct 9, 2024
Merged
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
@@ -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
@@ -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
Original file line number Diff line number Diff line change
@@ -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
@@ -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)
}
}