forked from fd4s/vulcan
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0018130
commit ad7c916
Showing
5 changed files
with
188 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
modules/generic/src/main/scala/vulcan/generic/AvroFieldDefault.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright 2019-2023 OVO Energy Limited | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package vulcan.generic | ||
|
||
import scala.annotation.StaticAnnotation | ||
|
||
/** | ||
* Annotation which can be used to set a default value for an Avro field. | ||
* | ||
* The annotation can be used in the following situations.<br> | ||
* - Annotate a field in a case class using `Codec.derive` | ||
* | ||
* @see https://avro.apache.org/docs/1.8.1/spec.html#Unions | ||
* | ||
* (Note that when a default value is specified for a record field whose type is a union, | ||
* the type of the default value must match the first element of the union. | ||
* Thus, for unions containing "null", the "null" is usually listed first, since the default value of such unions is typically null.) | ||
*/ | ||
final class AvroFieldDefault[A](final val value: A) extends StaticAnnotation { | ||
override final def toString: String = | ||
s"AvroFieldDefault($value)" | ||
} | ||
|
||
private[vulcan] object AvroFieldDefault { | ||
final def unapply[A](avroFieldDefault: AvroFieldDefault[A]): Some[A] = | ||
Some(avroFieldDefault.value) | ||
} |
137 changes: 137 additions & 0 deletions
137
modules/generic/src/test/scala/vulcan/generic/AvroFieldDefaultSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* Copyright 2019-2023 OVO Energy Limited | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package vulcan.generic | ||
|
||
import vulcan.{AvroError, Codec} | ||
|
||
final class AvroFieldDefaultSpec extends CodecBase { | ||
|
||
sealed trait Enum extends Product { | ||
self => | ||
def value: String = self.productPrefix | ||
} | ||
|
||
object Enum { | ||
case object A extends Enum | ||
|
||
case object B extends Enum | ||
|
||
implicit val codec: Codec[Enum] = deriveEnum( | ||
symbols = List(A.value, B.value), | ||
encode = _.value, | ||
decode = { | ||
case "A" => Right(A) | ||
case "B" => Right(B) | ||
case other => Left(AvroError(s"Invalid S: $other")) | ||
} | ||
) | ||
} | ||
|
||
sealed trait Union | ||
|
||
object Union { | ||
case class A(a: Int) extends Union | ||
|
||
case class B(b: String) extends Union | ||
|
||
implicit val codec: Codec[Union] = Codec.derive | ||
} | ||
|
||
describe("AvroFieldDefault") { | ||
it("should create a schema with a default for a field") { | ||
case class Foo( | ||
@AvroFieldDefault(1) a: Int, | ||
@AvroFieldDefault("foo") b: String, | ||
) | ||
|
||
object Foo { | ||
implicit val codec: Codec[Foo] = Codec.derive | ||
} | ||
|
||
assert(Foo.codec.schema.exists(_.getField("a").defaultVal() == 1)) | ||
assert(Foo.codec.schema.exists(_.getField("b").defaultVal() == "foo")) | ||
} | ||
|
||
it("should fail when the default value is not of the correct type") { | ||
case class InvalidDefault( | ||
@AvroFieldDefault("foo") a: Int | ||
) | ||
object InvalidDefault { | ||
implicit val codec: Codec[InvalidDefault] = Codec.derive | ||
} | ||
|
||
assertSchemaError[InvalidDefault] | ||
} | ||
|
||
it("should fail when annotating an Option") { | ||
case class InvalidDefault2( | ||
@AvroFieldDefault(Some("foo")) a: Option[String] | ||
) | ||
object InvalidDefault2 { | ||
implicit val codec: Codec[InvalidDefault2] = Codec.derive | ||
} | ||
|
||
assertSchemaError[InvalidDefault2] | ||
} | ||
|
||
it("should succeed when annotating an enum first element") { | ||
case class HasSFirst( | ||
@AvroFieldDefault(Enum.A) s: Enum | ||
) | ||
object HasSFirst { | ||
implicit val codec: Codec[HasSFirst] = Codec.derive | ||
} | ||
|
||
assert(HasSFirst.codec.schema.exists(_.getField("s").defaultVal() == "A")) | ||
} | ||
|
||
it("should succeed when annotating an enum second element") { | ||
case class HasSSecond( | ||
@AvroFieldDefault(Enum.B) s: Enum | ||
) | ||
object HasSSecond { | ||
implicit val codec: Codec[HasSSecond] = Codec.derive | ||
} | ||
|
||
assert(HasSSecond.codec.schema.exists(_.getField("s").defaultVal() == "B")) | ||
} | ||
|
||
it("should succeed with the first member of a union"){ | ||
case class HasUnion( | ||
@AvroFieldDefault(Union.A(1)) u: Union | ||
) | ||
object HasUnion { | ||
implicit val codec: Codec[HasUnion] = Codec.derive | ||
} | ||
|
||
case class Empty() | ||
object Empty { | ||
implicit val codec: Codec[Empty] = Codec.derive | ||
} | ||
|
||
assertSchemaIs[HasUnion]( | ||
"""{"type":"record","name":"HasUnion","namespace":"vulcan.generic.AvroFieldDefaultSpec.<local AvroFieldDefaultSpec>","fields":[{"name":"u","type":[{"type":"record","name":"A","namespace":"vulcan.generic.AvroFieldDefaultSpec.Union","fields":[{"name":"a","type":"int"}]},{"type":"record","name":"B","namespace":"vulcan.generic.AvroFieldDefaultSpec.Union","fields":[{"name":"b","type":"string"}]}],"default":{"a":1}}]}""" | ||
) | ||
|
||
val result = unsafeDecode[HasUnion](unsafeEncode[Empty](Empty())) | ||
|
||
assert(result == HasUnion(Union.A(1))) | ||
|
||
} | ||
|
||
it("should fail with the second member of a union"){ | ||
case class HasUnionSecond( | ||
@AvroFieldDefault(Union.B("foo")) u: Union | ||
) | ||
object HasUnionSecond { | ||
implicit val codec: Codec[HasUnionSecond] = Codec.derive | ||
} | ||
|
||
assertSchemaError[HasUnionSecond] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters