Skip to content

Commit

Permalink
Merge pull request #450 from erikvanoosten/option-plus-sum-types
Browse files Browse the repository at this point in the history
Support Option of sum types
  • Loading branch information
bplommer authored Nov 23, 2022
2 parents 1d937ff + 4e935f4 commit bef0f4e
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 5 deletions.
29 changes: 27 additions & 2 deletions modules/core/src/main/scala/vulcan/Codec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -976,9 +976,34 @@ object Codec extends CodecCompanionCompat {
* @group General
*/
implicit final def option[A](implicit codec: Codec[A]): Codec[Option[A]] =
Codec
.union[Option[A]](alt => alt[None.type] |+| alt[Some[A]])
OptionCodec(codec)
.withTypeName("Option")
private[vulcan] final case class OptionCodec[A](codec: Codec[A]) extends Codec[Option[A]] {
type AvroType = Any
override def encode(a: Option[A]): Either[AvroError, Any] = {
a match {
case Some(value) => codec.encode(value)
case None => Codec.none.encode(None)
}
}

override def decode(value: Any, schema: Schema): Either[AvroError, Option[A]] = {
if (value == null) {
Right(None)
} else if (schema.isUnion) {
codec.schema.flatMap(codec.decode(value, _).map(Some.apply))
} else {
codec.decode(value, schema).map(Some.apply)
}
}

val schema = AvroError.catchNonFatal {
codec.schema.map { schema =>
val schemaTypes = if (schema.isUnion) schema.getTypes.asScala.toList else List(schema)
Schema.createUnion((Schema.create(Schema.Type.NULL) :: schemaTypes).asJava)
}
}
}

/**
* Returns a new record [[Codec]] for type `A`.
Expand Down
6 changes: 3 additions & 3 deletions modules/core/src/test/scala/vulcan/CodecSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1560,7 +1560,7 @@ final class CodecSpec extends BaseSpec with CodecSpecHelpers {

it("should capture errors on nested unions") {
assertSchemaError[Option[Option[Int]]] {
"""org.apache.avro.AvroRuntimeException: Nested union: ["null",["null","int"]]"""
"""org.apache.avro.AvroRuntimeException: Duplicate in union:null"""
}
}
}
Expand All @@ -1586,7 +1586,7 @@ final class CodecSpec extends BaseSpec with CodecSpecHelpers {
assertDecodeError[Option[Int]](
unsafeEncode(Option(1)),
unsafeSchema[String],
"Error decoding Option: Error decoding union: Exhausted alternatives for type java.lang.Integer"
"Error decoding Option: Error decoding Int: Got unexpected schema type STRING, expected schema type INT"
)
}

Expand Down Expand Up @@ -2454,7 +2454,7 @@ final class CodecSpec extends BaseSpec with CodecSpecHelpers {

it("should show the error if schema is unavailable") {
assert {
Codec[Option[Option[Int]]].show == """AvroError(org.apache.avro.AvroRuntimeException: Nested union: ["null",["null","int"]])"""
Codec[Option[Option[Int]]].show == """AvroError(org.apache.avro.AvroRuntimeException: Duplicate in union:null)"""
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ final class GenericDerivationCodecSpec extends CodecBase {
}
}

it(
"should support optional field of a sum type"
) {
assertSchemaIs[CaseClassOptionOfSumType] {
"""{"type":"record","name":"CaseClassOptionOfSumType","namespace":"vulcan.generic.examples","fields":[{"name":"field1","type":["null",{"type":"record","name":"FirstInSealedTraitCaseClassCustom","fields":[{"name":"value","type":"int"}]},"string"]}]}""".stripMargin
}
}

}

describe("encode") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package vulcan.generic.examples

import vulcan.Codec
import vulcan.generic._

case class CaseClassOptionOfSumType(field1: Option[SealedTraitCaseClassCustom])

object CaseClassOptionOfSumType {
implicit val codec: Codec[CaseClassOptionOfSumType] = Codec.derive
}

0 comments on commit bef0f4e

Please sign in to comment.