Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect use defaults #424

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/src/main/scala/io/circe/examples/WithoutDefaults.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.circe.examples

import cats.kernel.Eq
import org.scalacheck.Arbitrary

case class WithoutDefaults(i: Int, j: Int = 1, k: Option[Int] = Some(5))

object WithoutDefaults {
implicit val arbitraryWithoutDefaults: Arbitrary[WithoutDefaults] = Arbitrary(
for {
i <- Arbitrary.arbitrary[Int]
j <- Arbitrary.arbitrary[Int]
k <- Arbitrary.arbitrary[Option[Int]]
} yield WithoutDefaults(i, j, k)
)

implicit val eqWithoutDefaults: Eq[WithoutDefaults] = Eq.fromUniversalEquals
}
Original file line number Diff line number Diff line change
Expand Up @@ -839,10 +839,14 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat {
{
val field = c.downField($realFieldName)

${repr.decoder(member.tpe).name}.tryDecode(field) match {
case r @ _root_.scala.Right(_) if !_root_.io.circe.derivation.DerivationMacros.isKeyMissingNone(r) => r
case l @ _root_.scala.Left(_) if field.succeeded && !field.focus.exists(_.isNull) => l
case _ => _root_.scala.Right($defaultValue)
if ($useDefaults) {
${repr.decoder(member.tpe).name}.tryDecode(field) match {
case r @ _root_.scala.Right(_) if !_root_.io.circe.derivation.DerivationMacros.isKeyMissingNone(r) => r
case l @ _root_.scala.Left(_) if field.succeeded && !field.focus.exists(_.isNull) => l
case _ => _root_.scala.Right($defaultValue)
}
} else {
${repr.decoder(member.tpe).name}.tryDecode(field)
}
}
"""
Expand All @@ -865,11 +869,15 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat {
{
val field = c.downField($realFieldName)

${repr.decoder(member.tpe).name}.tryDecodeAccumulating(field) match {
case v @ _root_.cats.data.Validated.Valid(_)
if !_root_.io.circe.derivation.DerivationMacros.isKeyMissingNoneAccumulating(v) => v
case i @ _root_.cats.data.Validated.Invalid(_) if field.succeeded && !field.focus.exists(_.isNull) => i
case _ => _root_.cats.data.Validated.Valid($defaultValue)
if ($useDefaults) {
${repr.decoder(member.tpe).name}.tryDecodeAccumulating(field) match {
case v @ _root_.cats.data.Validated.Valid(_)
if !_root_.io.circe.derivation.DerivationMacros.isKeyMissingNoneAccumulating(v) => v
case i @ _root_.cats.data.Validated.Invalid(_) if field.succeeded && !field.focus.exists(_.isNull) => i
case _ => _root_.cats.data.Validated.Valid($defaultValue)
}
} else {
${repr.decoder(member.tpe).name}.tryDecodeAccumulating(field)
}
}
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ object DerivationSuiteCodecs extends Serializable {
implicit val encodeWithDefaults: Encoder[WithDefaults] = deriveEncoder(identity, None)
val codecForWithDefaults: Codec[WithDefaults] = deriveCodec(identity, true, None)

implicit val decodeWithoutDefaults: Decoder[WithoutDefaults] = deriveDecoder(identity, false, None)
implicit val encodeWithoutDefaults: Encoder[WithoutDefaults] = deriveEncoder(identity, None)
val codecForWithoutDefaults: Codec[WithoutDefaults] = deriveCodec(identity, false, None)

implicit val decodeWithJson: Decoder[WithJson] = deriveDecoder(identity, true, None)
implicit val encodeWithJson: Encoder[WithJson] = deriveEncoder(identity, None)
val codecForWithJson: Codec[WithJson] = deriveCodec(identity, true, None)
Expand Down Expand Up @@ -360,6 +364,16 @@ class DerivationSuite extends CirceSuite {
).codecAgreement
)

checkAll(
"CodecAgreementWithCodec[WithoutDefaults]",
CodecAgreementTests[WithoutDefaults](
codecForWithoutDefaults,
codecForWithoutDefaults,
decodeWithoutDefaults,
encodeWithoutDefaults
).codecAgreement
)

checkAll(
"CodecAgreementWithCodec[WithJson]",
CodecAgreementTests[WithJson](
Expand Down Expand Up @@ -390,7 +404,7 @@ class DerivationSuite extends CirceSuite {
).codecAgreement
)

"useDefaults" should "cause defaults to be used for missing fields" in {
"useDefaults" should "cause defaults to be used for missing fields when true" in {
val expectedBothDefaults = WithDefaults(0, 1, List(""))
val expectedOneDefault = WithDefaults(0, 1, Nil)

Expand All @@ -413,6 +427,40 @@ class DerivationSuite extends CirceSuite {
assert(codecForWithDefaults.decodeAccumulating(j3.hcursor) === Validated.validNel(expectedBothDefaults))
}

"useDefaults" should "cause defaults to be ingored for missing fields when false" in {
val expectedEmptyDefault = WithoutDefaults(0, 2, None)
val expectedNoDefaults = WithoutDefaults(0, 2, Some(2))

val j1 = Json.obj("i" := 0)
val j2 = Json.obj("i" := 0, "j" := 2)
val j3 = Json.obj("i" := 0, "j" := 2, "k" := Json.Null)
val j4 = Json.obj("i" := 0, "j" := 2, "k" := Some(2))

val histories1 = List(CursorOp.DownField("j"))

assert(decodeWithoutDefaults.decodeJson(j1).leftMap(_.history) === Left(histories1))
assert(codecForWithoutDefaults.decodeJson(j1).leftMap(_.history) === Left(histories1))
assert(decodeWithoutDefaults.decodeJson(j2) == Right(expectedEmptyDefault))
assert(codecForWithoutDefaults.decodeJson(j2) === Right(expectedEmptyDefault))
assert(decodeWithoutDefaults.decodeJson(j3) == Right(expectedEmptyDefault))
assert(codecForWithoutDefaults.decodeJson(j3) === Right(expectedEmptyDefault))
assert(decodeWithoutDefaults.decodeJson(j4) === Right(expectedNoDefaults))
assert(codecForWithoutDefaults.decodeJson(j4) === Right(expectedNoDefaults))

val historiesNel1 = NonEmptyList.of[List[CursorOp]](
List(CursorOp.DownField("j"))
)

assert(decodeWithoutDefaults.decodeAccumulating(j1.hcursor).leftMap(_.map(_.history)) === Validated.invalid(historiesNel1))
assert(codecForWithoutDefaults.decodeAccumulating(j1.hcursor).leftMap(_.map(_.history)) === Validated.invalid(historiesNel1))
assert(decodeWithoutDefaults.decodeAccumulating(j2.hcursor).leftMap(_.map(_.history)) === Validated.valid(expectedEmptyDefault))
assert(codecForWithoutDefaults.decodeAccumulating(j2.hcursor).leftMap(_.map(_.history)) === Validated.valid(expectedEmptyDefault))
assert(decodeWithoutDefaults.decodeAccumulating(j3.hcursor).leftMap(_.map(_.history)) === Validated.valid(expectedEmptyDefault))
assert(codecForWithoutDefaults.decodeAccumulating(j3.hcursor).leftMap(_.map(_.history)) === Validated.valid(expectedEmptyDefault))
assert(decodeWithoutDefaults.decodeAccumulating(j4.hcursor).leftMap(_.map(_.history)) === Validated.valid(expectedNoDefaults))
assert(codecForWithoutDefaults.decodeAccumulating(j4.hcursor).leftMap(_.map(_.history)) === Validated.valid(expectedNoDefaults))
}

"Derived ADT decoders" should "preserve error accumulation" in {
val j = Json.obj("AdtFoo" := Json.obj("s" := Json.fromInt(0))).hcursor
val histories = NonEmptyList.of[List[CursorOp]](
Expand Down