Skip to content

Commit

Permalink
Parses a nonexistent key as None (#234)
Browse files Browse the repository at this point in the history
* Parses a nonexistent key as None

Implicitly parses a nonexistent key as None in case of key type is Option[_]

* scalafmt
  • Loading branch information
andrelfpinto committed Jul 12, 2023
1 parent 93fd560 commit 9054aff
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 9 deletions.
Expand Up @@ -31,31 +31,41 @@ private[yaml] trait DecoderMacros {
protected def constructValues[T](
elemLabels: List[String],
instances: List[YamlDecoder[_]],
optionalTypes: List[Boolean],
valuesMap: Map[String, Node],
p: Mirror.ProductOf[T]
) = {
val values = elemLabels.zip(instances).map { case (label, c) =>
val values = elemLabels.zip(instances).zip(optionalTypes).map { case ((label, c), isOptional) =>
valuesMap.get(label) match
case Some(value) => c.construct(value)
case None => Left(ConstructError(s"Key $label doesn't exist in parsed document"))
case None =>
if (isOptional) Right(None)
else Left(ConstructError(s"Key $label doesn't exist in parsed document"))
}
val (left, right) = values.partitionMap(identity)
if left.nonEmpty then Left(left.head)
else Right(p.fromProduct(Tuple.fromArray(right.toArray)))
}

protected inline def deriveProduct[T](p: Mirror.ProductOf[T]) =
val instances = summonAll[p.MirroredElemTypes]
val elemLabels = getElemLabels[p.MirroredElemLabels]
val instances = summonAll[p.MirroredElemTypes]
val elemLabels = getElemLabels[p.MirroredElemLabels]
val optionalTypes = getOptionalTypes[p.MirroredElemTypes]
new YamlDecoder[T] {
override def construct(node: Node)(using
constructor: LoadSettings = LoadSettings.empty
): Either[ConstructError, T] =
node match
case Node.MappingNode(mappings, _) =>
for {
valuesMap <- extractKeyValues(mappings)
constructedValues <- constructValues(elemLabels, instances, valuesMap, p)
valuesMap <- extractKeyValues(mappings)
constructedValues <- constructValues(
elemLabels,
instances,
optionalTypes,
valuesMap,
p
)
} yield (constructedValues)
case _ =>
Left(ConstructError(s"Expected MappingNode, got ${node.getClass.getSimpleName}"))
Expand Down Expand Up @@ -87,4 +97,9 @@ private[yaml] trait DecoderMacros {
case _: EmptyTuple => Nil
case _: (head *: tail) => constValue[head].toString :: getElemLabels[tail]

protected inline def getOptionalTypes[T <: Tuple]: List[Boolean] = inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (Option[_] *: tail) => true :: getOptionalTypes[tail]
case _: (_ *: tail) => false :: getOptionalTypes[tail]

}
Expand Up @@ -42,8 +42,12 @@ class DecoderSuite extends munit.FunSuite:

test("option") {
// todo Option YamlEncoder
case class OptionTypes(double: Option[Double], float: Option[Float], int: Option[Int])
derives YamlDecoder
case class OptionTypes(
double: Option[Double],
float: Option[Float],
int: Option[Int],
short: Option[Short]
) derives YamlDecoder

val numberYaml =
s"""double: ${Double.MaxValue}
Expand All @@ -54,7 +58,8 @@ class DecoderSuite extends munit.FunSuite:
val expectedNumber = OptionTypes(
double = Some(Double.MaxValue),
float = None,
int = None
int = None,
short = None
)

assertEquals(numberYaml.as[OptionTypes], Right(expectedNumber))
Expand Down

0 comments on commit 9054aff

Please sign in to comment.