org.virtuslab.yaml.YamlDecoderCrossCompat.scala Maven / Gradle / Ivy
package org.virtuslab.yaml
import org.virtuslab.yaml.Node.*
import scala.compiletime.*
import scala.deriving.Mirror
private[yaml] trait YamlDecoderCompanionCrossCompat extends DecoderMacros {
inline def derived[T](using m: Mirror.Of[T]): YamlDecoder[T] = inline m match
case p: Mirror.ProductOf[T] => deriveProduct(p)
case s: Mirror.SumOf[T] => sumOf(s)
}
private[yaml] trait DecoderMacros {
protected def extractKeyValues(
mappings: Map[Node, Node]
): Either[ConstructError, Map[String, Node]] = {
val keyValueMap = mappings
.map { (k, v) =>
k match {
case ScalarNode(scalarKey, _) => Right((scalarKey, v))
case _ => Left(ConstructError(s"Parameter of a class must be a scalar value"))
}
}
val (error, valuesSeq) = keyValueMap.partitionMap(identity)
if (error.nonEmpty) Left(error.head)
else Right(valuesSeq.toMap)
}
protected def constructValues[T](
elemLabels: List[String],
instances: List[YamlDecoder[_]],
valuesMap: Map[String, Node],
p: Mirror.ProductOf[T]
) = {
val values = elemLabels.zip(instances).map { case (label, c) =>
valuesMap.get(label) match
case Some(value) => c.construct(value)
case None => 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]
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)
} yield (constructedValues)
case _ =>
Left(ConstructError(s"Expected MappingNode, got ${node.getClass.getSimpleName}"))
}
protected inline def sumOf[T](s: Mirror.SumOf[T]) =
val instances = summonSumOf[s.MirroredElemTypes].asInstanceOf[List[YamlDecoder[T]]]
new YamlDecoder[T]:
override def construct(
node: Node
)(using constructor: LoadSettings = LoadSettings.empty): Either[ConstructError, T] = LazyList
.from(instances)
.map(c => c.construct(node))
.collectFirst { case r @ Right(_) => r }
.getOrElse(Left(ConstructError(s"Cannot parse $node")))
protected inline def summonSumOf[T <: Tuple]: List[YamlDecoder[_]] = inline erasedValue[T] match
case _: (t *: ts) =>
summonFrom { case p: Mirror.ProductOf[`t`] =>
deriveProduct(p) :: summonSumOf[ts]
}
case _: EmptyTuple => Nil
protected inline def summonAll[T <: Tuple]: List[YamlDecoder[_]] = inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[YamlDecoder[t]] :: summonAll[ts]
protected inline def getElemLabels[T <: Tuple]: List[String] = inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (head *: tail) => constValue[head].toString :: getElemLabels[tail]
}