diff --git a/avro/src/main/scala/magnolify/avro/AvroType.scala b/avro/src/main/scala/magnolify/avro/AvroType.scala index 7ede81ff..d7802ffe 100644 --- a/avro/src/main/scala/magnolify/avro/AvroType.scala +++ b/avro/src/main/scala/magnolify/avro/AvroType.scala @@ -27,7 +27,7 @@ import org.joda.{time => joda} import java.nio.{ByteBuffer, ByteOrder} import java.time._ import java.{util => ju} -import scala.annotation.implicitNotFound +import scala.annotation.{implicitNotFound, nowarn} import scala.collection.concurrent import scala.collection.compat._ import scala.jdk.CollectionConverters._ @@ -253,6 +253,31 @@ object AvroField { } } + implicit def afEither[A, B](implicit + fa: AvroField[A], + fb: AvroField[B] + ): AvroField[Either[A, B]] = + new Aux[Either[A, B], Any, Any] { + override protected def buildSchema(cm: CaseMapper): Schema = + Schema.createUnion(fa.schema(cm), fb.schema(cm)) + // `Either[A, B]` is a `UNION` of `[A, B]` and must default to first type `A` + override def makeDefault(d: Either[A, B])(cm: CaseMapper): fa.ToT = { + require(d.isLeft, "Either[A, B] can only default to Left[A]") + fa.to(d.left.get)(cm): @nowarn + } + override def from(v: Any)(cm: CaseMapper): Either[A, B] = { + GenericData.get().resolveUnion(schema(cm), v) match { + case 0 => Left(fa.from(v.asInstanceOf[fa.FromT])(cm)) + case 1 => Right(fb.from(v.asInstanceOf[fb.FromT])(cm)) + } + } + + override def to(v: Either[A, B])(cm: CaseMapper): Any = v match { + case Left(a) => fa.to(a)(cm) + case Right(b) => fb.to(b)(cm) + } + } + implicit def afIterable[T, C[_]](implicit f: AvroField[T], ti: C[T] => Iterable[T], diff --git a/avro/src/test/scala/magnolify/avro/AvroTypeSuite.scala b/avro/src/test/scala/magnolify/avro/AvroTypeSuite.scala index 16df0a6d..6bfc22c0 100644 --- a/avro/src/test/scala/magnolify/avro/AvroTypeSuite.scala +++ b/avro/src/test/scala/magnolify/avro/AvroTypeSuite.scala @@ -317,6 +317,7 @@ class AvroTypeSuite extends MagnolifySuite { val inner = DefaultInner( 2, Some(2), + Right("2"), List(2, 2), Map("b" -> 2), JavaEnums.Color.GREEN, @@ -337,6 +338,7 @@ class AvroTypeSuite extends MagnolifySuite { val inner = DefaultInner( 3, Some(3), + Right("3"), List(3, 3), Map("c" -> 3), JavaEnums.Color.BLUE, @@ -359,6 +361,7 @@ class AvroTypeSuite extends MagnolifySuite { } testFail(AvroType[DefaultSome])("Option[T] can only default to None") + testFail(AvroType[DefaultEither])("Either[A, B] can only default to Left[A]") { implicit val at: AvroType[LowerCamel] = AvroType[LowerCamel](CaseMapper(_.toUpperCase)) @@ -493,6 +496,7 @@ case class DoubleFieldDoc(@doc("doc1") @doc("doc2") i: Int) case class DefaultInner( i: Int = 1, o: Option[Int] = None, + e: Either[Int, String] = Left(1), l: List[Int] = List(1, 1), m: Map[String, Int] = Map("a" -> 1), je: JavaEnums.Color = JavaEnums.Color.RED, @@ -508,6 +512,7 @@ case class DefaultOuter( i: DefaultInner = DefaultInner( 2, None, + Left(2), List(2, 2), Map("b" -> 2), JavaEnums.Color.GREEN, @@ -521,7 +526,9 @@ case class DefaultOuter( ), o: Option[DefaultInner] = None ) + case class DefaultSome(o: Option[Int] = Some(1)) +case class DefaultEither(e: Either[Int, String] = Right("1")) case class DefaultBytes( a: Array[Byte] = Array(2, 2)