Skip to content

Commit

Permalink
ConfDecoder: add decoding Either values
Browse files Browse the repository at this point in the history
  • Loading branch information
kitbellew committed Aug 16, 2022
1 parent 4b0d474 commit b71c0d8
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ object ConfDecoder {
case _ => ev.read(conf).map(Some(_))
}

implicit def canBuildEither[A, B](
implicit evA: ConfDecoder[A],
evB: ConfDecoder[B]
): ConfDecoder[Either[A, B]] =
orElse(evA.map(x => Left(x)), evB.map(x => Right(x)))

// XXX: remove this method when MIMA no longer an issue
@deprecated("Use canBuildFromAnyMapWithStringKey instead", "0.9.2")
implicit def canBuildFromMapWithStringKey[A](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,29 @@ object ConfDecoderExT {
case _ => ev.read(state.flatten, conf).map(Some.apply)
}

implicit def canBuildEitherT[S, A, B](
implicit evA: ConfDecoderExT[S, A],
evB: ConfDecoderExT[S, B]
): ConfDecoderExT[S, Either[A, B]] =
evA.map[Either[A, B]](Left.apply).orElse(evB.map[Either[A, B]](Right.apply))

implicit def canBuildEither[A, B](
implicit evA: ConfDecoderEx[A],
evB: ConfDecoderEx[B]
): ConfDecoderEx[Either[A, B]] =
(state, conf) => {
@inline def asA(s: Option[A]) = evA.read(s, conf).map(x => Left(x))
@inline def asB(s: Option[B]) = evB.read(s, conf).map(x => Right(x))
state.fold {
asA(None).recoverWithOrCombine(asB(None))
} {
_.fold(
a => asA(Some(a)).recoverWithOrCombine(asB(None)),
b => asB(Some(b)).recoverWithOrCombine(asA(None))
)
}
}

implicit def canBuildStringMapT[S, A, CC[_, _]](
implicit ev: ConfDecoderExT[S, A],
factory: Factory[(String, A), CC[String, A]],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,54 @@ class DeriveConfDecoderExSuite extends munit.FunSuite {
assert(obtained == expected)
}

test("either") {
implicit val decoderOneParam: ConfDecoderEx[OneParam] =
generic.deriveDecoderEx[OneParam](OneParam()).noTypos
implicit val decoderHasOption: ConfDecoderEx[HasOption] =
generic.deriveDecoderEx[HasOption](HasOption()).noTypos
val either = implicitly[ConfDecoderEx[Either[OneParam, HasOption]]]
assertEquals(
either.read(None, Obj("param" -> Num(2))).get,
Left(OneParam(2))
)
assertEquals(
either.read(Some(Left(OneParam(1))), Obj("param" -> Num(2))).get,
Left(OneParam(2))
)
assertEquals(
either.read(Some(Right(HasOption())), Obj("param" -> Num(2))).get,
Left(OneParam(2))
)

assertEquals(
either.read(None, Obj("b" -> Num(2))).get,
Right(HasOption(Some(2)))
)
assertEquals(
either.read(Some(Left(OneParam(1))), Obj("b" -> Num(2))).get,
Right(HasOption(Some(2)))
)
assertEquals(
either.read(Some(Right(HasOption())), Obj("b" -> Num(2))).get,
Right(HasOption(Some(2)))
)

def getMsg(c: Configured[_]) = c.toEither.left.get.msg

assertEquals(
getMsg(either.read(None, Obj("c" -> Num(3)))),
"found option 'c' which wasn't expected, or isn't valid in this context."
)
assertEquals(
getMsg(either.read(Some(Left(OneParam(1))), Obj("c" -> Num(3)))),
"found option 'c' which wasn't expected, or isn't valid in this context."
)
assertEquals(
getMsg(either.read(Some(Right(HasOption())), Obj("c" -> Num(3)))),
"found option 'c' which wasn't expected, or isn't valid in this context."
)
}

case class MissingSurface(b: Int)

def checkOption(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,26 @@ class DeriveConfDecoderSuite extends munit.FunSuite {
assert(obtained == expected)
}

test("either") {
implicit val decoderOneParam: ConfDecoder[OneParam] =
generic.deriveDecoder[OneParam](OneParam()).noTypos
implicit val decoderHasOption: ConfDecoder[HasOption] =
generic.deriveDecoder[HasOption](HasOption()).noTypos
val either = implicitly[ConfDecoder[Either[OneParam, HasOption]]]
assertEquals(
either.read(Obj("param" -> Num(2))).get,
Left(OneParam(2))
)
assertEquals(
either.read(Obj("b" -> Num(2))).get,
Right(HasOption(Some(2)))
)
assertEquals(
either.read(Obj("c" -> Num(3))).toEither.left.get.msg,
"found option 'c' which wasn't expected, or isn't valid in this context."
)
}

case class MissingSurface(b: Int)

def checkOption(conf: Conf, expected: HasOption)(
Expand Down

0 comments on commit b71c0d8

Please sign in to comment.