Skip to content

Commit

Permalink
Merge pull request #28 from ChrisMcD1/enumeratum-value-enums
Browse files Browse the repository at this point in the history
Add Enumeratum ValueEnum codecs
  • Loading branch information
valentiay authored Oct 13, 2024
2 parents c11f34a + f35323c commit 38e3831
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 1 deletion.
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,12 @@ lazy val `enumeratum` =
.settings(commonSettings("enumeratum"))
.settings(
commonDependencies,
scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _)) => List( "-Yretain-trees")
case _ => Nil
}),
libraryDependencies ++= Seq(
"com.beachape" %% "enumeratum" % "1.7.3",
"com.beachape" %% "enumeratum" % "1.7.5",
),
)
.jvmPlatform(scala3Versions)
Expand Down
117 changes: 117 additions & 0 deletions modules/enumeratum/src/main/scala/phobos/enumeratum/XmlValueEnum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package phobos.enumeratum

import phobos.decoding._
import phobos.encoding._

import enumeratum.values._

sealed trait XmlValueEnum[V, E <: ValueEnumEntry[V]] {
_enum: ValueEnum[V, E] =>

implicit def elementEncoder: ElementEncoder[E]
implicit def attributeEncoder: AttributeEncoder[E]
implicit def textEncoder: TextEncoder[E]
implicit def elementDecoder: ElementDecoder[E]
implicit def attributeDecoder: AttributeDecoder[E]
implicit def textDecoder: TextDecoder[E]

}

trait IntXmlValueEnum[E <: IntEnumEntry] extends XmlValueEnum[Int, E] {
self: IntEnum[E] =>
implicit val elementEncoder: ElementEncoder[E] = XmlValueEnum.elementEncoder[Int, E]
implicit val attributeEncoder: AttributeEncoder[E] = XmlValueEnum.attributeEncoder[Int, E]
implicit val textEncoder: TextEncoder[E] = XmlValueEnum.textEncoder[Int, E]
implicit val elementDecoder: ElementDecoder[E] = XmlValueEnum.elementDecoder(self)
implicit val attributeDecoder: AttributeDecoder[E] = XmlValueEnum.attributeDecoder(self)
implicit val textDecoder: TextDecoder[E] = XmlValueEnum.textDecoder(self)
}

trait LongXmlValueEnum[E <: LongEnumEntry] extends XmlValueEnum[Long, E] {
self: LongEnum[E] =>
implicit val elementEncoder: ElementEncoder[E] = XmlValueEnum.elementEncoder[Long, E]
implicit val attributeEncoder: AttributeEncoder[E] = XmlValueEnum.attributeEncoder[Long, E]
implicit val textEncoder: TextEncoder[E] = XmlValueEnum.textEncoder[Long, E]
implicit val elementDecoder: ElementDecoder[E] = XmlValueEnum.elementDecoder(self)
implicit val attributeDecoder: AttributeDecoder[E] = XmlValueEnum.attributeDecoder(self)
implicit val textDecoder: TextDecoder[E] = XmlValueEnum.textDecoder(self)
}

trait ShortXmlValueEnum[E <: ShortEnumEntry] extends XmlValueEnum[Short, E] {
self: ShortEnum[E] =>
implicit val elementEncoder: ElementEncoder[E] = XmlValueEnum.elementEncoder[Short, E]
implicit val attributeEncoder: AttributeEncoder[E] = XmlValueEnum.attributeEncoder[Short, E]
implicit val textEncoder: TextEncoder[E] = XmlValueEnum.textEncoder[Short, E]
implicit val elementDecoder: ElementDecoder[E] = XmlValueEnum.elementDecoder(self)
implicit val attributeDecoder: AttributeDecoder[E] = XmlValueEnum.attributeDecoder(self)
implicit val textDecoder: TextDecoder[E] = XmlValueEnum.textDecoder(self)
}

trait StringXmlValueEnum[E <: StringEnumEntry] extends XmlValueEnum[String, E] {
self: StringEnum[E] =>
implicit val elementEncoder: ElementEncoder[E] = XmlValueEnum.elementEncoder[String, E]
implicit val attributeEncoder: AttributeEncoder[E] = XmlValueEnum.attributeEncoder[String, E]
implicit val textEncoder: TextEncoder[E] = XmlValueEnum.textEncoder[String, E]
implicit val elementDecoder: ElementDecoder[E] = XmlValueEnum.elementDecoder(self)
implicit val attributeDecoder: AttributeDecoder[E] = XmlValueEnum.attributeDecoder(self)
implicit val textDecoder: TextDecoder[E] = XmlValueEnum.textDecoder(self)
}

trait CharXmlValueEnum[E <: CharEnumEntry] extends XmlValueEnum[Char, E] {
self: CharEnum[E] =>
implicit val elementEncoder: ElementEncoder[E] = XmlValueEnum.elementEncoder[Char, E]
implicit val attributeEncoder: AttributeEncoder[E] = XmlValueEnum.attributeEncoder[Char, E]
implicit val textEncoder: TextEncoder[E] = XmlValueEnum.textEncoder[Char, E]
implicit val elementDecoder: ElementDecoder[E] = XmlValueEnum.elementDecoder(self)
implicit val attributeDecoder: AttributeDecoder[E] = XmlValueEnum.attributeDecoder(self)
implicit val textDecoder: TextDecoder[E] = XmlValueEnum.textDecoder(self)
}

trait ByteXmlValueEnum[E <: ByteEnumEntry] extends XmlValueEnum[Byte, E] {
self: ByteEnum[E] =>
implicit val elementEncoder: ElementEncoder[E] = XmlValueEnum.elementEncoder[Byte, E]
implicit val attributeEncoder: AttributeEncoder[E] = XmlValueEnum.attributeEncoder[Byte, E]
implicit val textEncoder: TextEncoder[E] = XmlValueEnum.textEncoder[Byte, E]
implicit val elementDecoder: ElementDecoder[E] = XmlValueEnum.elementDecoder(self)
implicit val attributeDecoder: AttributeDecoder[E] = XmlValueEnum.attributeDecoder(self)
implicit val textDecoder: TextDecoder[E] = XmlValueEnum.textDecoder(self)
}

object XmlValueEnum {

def elementDecoder[V, E <: ValueEnumEntry[V]](
e: ValueEnum[V, E],
)(implicit baseDecoder: ElementDecoder[V]): ElementDecoder[E] =
baseDecoder.emap(decodeFromValueType(e))

def attributeDecoder[V, E <: ValueEnumEntry[V]](
e: ValueEnum[V, E],
)(implicit baseDecoder: AttributeDecoder[V]): AttributeDecoder[E] =
baseDecoder.emap(decodeFromValueType(e))

def textDecoder[V, E <: ValueEnumEntry[V]](
e: ValueEnum[V, E],
)(implicit baseDecoder: TextDecoder[V]): TextDecoder[E] =
baseDecoder.emap(decodeFromValueType(e))

def decodeFromValueType[V, E <: ValueEnumEntry[V]](
e: ValueEnum[V, E],
)(history: List[String], value: V): Either[DecodingError, E] =
e.withValueOpt(value) match {
case Some(member) => Right(member)
case _ => Left(DecodingError(s"'$value' in not a member of enum $this", history, None))
}

def elementEncoder[V, E <: ValueEnumEntry[V]](
implicit baseEncoder: ElementEncoder[V],
): ElementEncoder[E] = baseEncoder.contramap(_.value)

def attributeEncoder[V, E <: ValueEnumEntry[V]](
implicit baseEncoder: AttributeEncoder[V],
): AttributeEncoder[E] = baseEncoder.contramap(_.value)

def textEncoder[V, E <: ValueEnumEntry[V]](
implicit baseEncoder: TextEncoder[V],
): TextEncoder[E] = baseEncoder.contramap(_.value)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package phobos.enumeratum

import phobos.decoding.XmlDecoder
import phobos.derivation.semiauto._
import phobos.encoding.XmlEncoder
import phobos.syntax._
import phobos.testString._

import enumeratum.values._
import org.scalatest.Assertion
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

sealed abstract class Foo(override val value: Int) extends IntEnumEntry with Product with Serializable
object Foo extends IntEnum[Foo] with IntXmlValueEnum[Foo] {
val values = findValues

case object Foo1 extends Foo(1)
case object Foo2 extends Foo(2)
case object Foo3 extends Foo(3)
}
import Foo._
case class Bar(d: String, foo: Foo, e: Char)
object Bar {
implicit lazy val xmlEncoder: XmlEncoder[Bar] = deriveXmlEncoder[Bar]("bar")
implicit lazy val xmlDecoder: XmlDecoder[Bar] = deriveXmlDecoder[Bar]("bar")
}
case class Baz(@attr f: Foo, @text text: Foo)
object Baz {
implicit lazy val xmlEncoder: XmlEncoder[Baz] = deriveXmlEncoder[Baz]("baz")
implicit lazy val xmlDecoder: XmlDecoder[Baz] = deriveXmlDecoder[Baz]("baz")
}

class EnumeratumValueTest extends AnyWordSpec with Matchers {
"Enum codecs" should {
"encode enums" in {
val bar1 = Bar("d value", Foo1, 'e')
val bar2 = Bar("d value", Foo2, 'e')
val bar3 = Bar("another one value", Foo3, 'v')
val baz = Baz(Foo1, Foo2)
val xml1 = XmlEncoder[Bar].encode(bar1)
val xml2 = XmlEncoder[Bar].encode(bar2)
val xml3 = XmlEncoder[Bar].encode(bar3)
val xml4 = XmlEncoder[Baz].encode(baz)
val string1 =
"""
| <?xml version='1.0' encoding='UTF-8'?>
| <bar>
| <d>d value</d>
| <foo>
| 1
| </foo>
| <e>e</e>
| </bar>
""".stripMargin.minimized
val string2 =
"""
| <?xml version='1.0' encoding='UTF-8'?>
| <bar>
| <d>d value</d>
| <foo>
| 2
| </foo>
| <e>e</e>
| </bar>
""".stripMargin.minimized
val string3 =
"""
| <?xml version='1.0' encoding='UTF-8'?>
| <bar>
| <d>another one value</d>
| <foo>
| 3
| </foo>
| <e>v</e>
| </bar>
""".stripMargin.minimized
val string4 =
"""
| <?xml version='1.0' encoding='UTF-8'?>
| <baz f="1">2</baz>
""".stripMargin.minimized
assert(
xml1 == Right(string1) &&
xml2 == Right(string2) &&
xml3 == Right(string3) &&
xml4 == Right(string4),
)
}

def pure(str: String): List[Array[Byte]] =
List(str.getBytes("UTF-8"))

def fromIterable(str: String): List[Array[Byte]] =
str.toList.map(c => Array(c.toByte))

def decodeEnums(toList: String => List[Array[Byte]]): Assertion = {
val bar1 = Bar("d value", Foo1, 'e')
val bar2 = Bar("d value", Foo2, 'e')
val bar3 = Bar("another one value", Foo3, 'v')
val baz = Baz(Foo1, Foo2)

val string1 =
"""<?xml version='1.0' encoding='UTF-8'?>
| <bar>
| <d>d value</d>
| <foo>1</foo>
| <e>e</e>
| </bar>
""".stripMargin
val string2 =
"""<?xml version='1.0' encoding='UTF-8'?>
| <bar>
| <d>d value</d>
| <foo>2</foo>
| <e>e</e>
| </bar>
""".stripMargin
val string3 =
"""<?xml version='1.0' encoding='UTF-8'?>
| <bar>
| <d>another one value</d>
| <foo>3</foo>
| <e>v</e>
| </bar>
""".stripMargin
val string4 =
"""<?xml version='1.0' encoding='UTF-8'?>
| <baz f="1">2</baz>
""".stripMargin
val decoded1 = XmlDecoder[Bar].decodeFromIterable(toList(string1))
val decoded2 = XmlDecoder[Bar].decodeFromIterable(toList(string2))
val decoded3 = XmlDecoder[Bar].decodeFromIterable(toList(string3))
val decoded4 = XmlDecoder[Baz].decodeFromIterable(toList(string4))
assert(decoded1 == Right(bar1) && decoded2 == Right(bar2) && decoded3 == Right(bar3) && decoded4 == Right(baz))
}

"decode enums sync" in decodeEnums(pure)
"decode enums async" in decodeEnums(fromIterable)
}
}

0 comments on commit 38e3831

Please sign in to comment.