/
YamlDecoderCrossCompat.scala
105 lines (92 loc) · 3.89 KB
/
YamlDecoderCrossCompat.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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.from(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[_]],
optionalTypes: List[Boolean],
valuesMap: Map[String, Node],
p: Mirror.ProductOf[T]
) = {
val values = elemLabels.zip(instances).zip(optionalTypes).map { case ((label, c), isOptional) =>
valuesMap.get(label) match
case Some(value) => c.construct(value)
case None =>
if (isOptional) Right(None)
else Left(ConstructError.from(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 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,
optionalTypes,
valuesMap,
p
)
} yield (constructedValues)
case _ =>
Left(ConstructError.from(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.from(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]
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]
}