diff --git a/build.sbt b/build.sbt
index 365dca6..008d953 100644
--- a/build.sbt
+++ b/build.sbt
@@ -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)
diff --git a/modules/enumeratum/src/main/scala/phobos/enumeratum/XmlValueEnum.scala b/modules/enumeratum/src/main/scala/phobos/enumeratum/XmlValueEnum.scala
new file mode 100644
index 0000000..10545fc
--- /dev/null
+++ b/modules/enumeratum/src/main/scala/phobos/enumeratum/XmlValueEnum.scala
@@ -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)
+
+}
diff --git a/modules/enumeratum/src/test/scala/phobos/enumeratum/EnumeratumValueTest.scala b/modules/enumeratum/src/test/scala/phobos/enumeratum/EnumeratumValueTest.scala
new file mode 100644
index 0000000..7a34fe7
--- /dev/null
+++ b/modules/enumeratum/src/test/scala/phobos/enumeratum/EnumeratumValueTest.scala
@@ -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 =
+ """
+ |
+ |
+ | d value
+ |
+ | 1
+ |
+ | e
+ |
+ """.stripMargin.minimized
+ val string2 =
+ """
+ |
+ |
+ | d value
+ |
+ | 2
+ |
+ | e
+ |
+ """.stripMargin.minimized
+ val string3 =
+ """
+ |
+ |
+ | another one value
+ |
+ | 3
+ |
+ | v
+ |
+ """.stripMargin.minimized
+ val string4 =
+ """
+ |
+ | 2
+ """.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 =
+ """
+ |
+ | d value
+ | 1
+ | e
+ |
+ """.stripMargin
+ val string2 =
+ """
+ |
+ | d value
+ | 2
+ | e
+ |
+ """.stripMargin
+ val string3 =
+ """
+ |
+ | another one value
+ | 3
+ | v
+ |
+ """.stripMargin
+ val string4 =
+ """
+ | 2
+ """.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)
+ }
+}