/
YamlDecoder.scala
173 lines (150 loc) · 6.01 KB
/
YamlDecoder.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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package org.virtuslab.yaml
import scala.reflect.ClassTag
import scala.util.Try
import org.virtuslab.yaml.Node
import org.virtuslab.yaml.Node._
/**
* A type class that provides a conversion from a [[Node]] into given type [[T]]
*/
trait YamlDecoder[T] {
def construct(node: Node)(implicit
settings: LoadSettings = LoadSettings.empty
): Either[ConstructError, T]
}
object YamlDecoder extends YamlDecoderCompanionCrossCompat {
def apply[T](
pf: PartialFunction[Node, Either[ConstructError, T]]
)(implicit classTag: ClassTag[T]): YamlDecoder[T] =
new YamlDecoder[T] {
override def construct(
node: Node
)(implicit settings: LoadSettings = LoadSettings.empty): Either[ConstructError, T] =
if (pf.isDefinedAt(node)) pf(node)
else
Left(
ConstructError.from(
s"""|Could't construct ${classTag.runtimeClass.getName} from ${node.tag}
|${node.pos.map(_.errorMsg).getOrElse("")}
|""".stripMargin
)
)
}
private def cannotParse(value: Any, tpe: String, node: Node) = ConstructError.from(
s"Cannot parse $value as $tpe",
node,
tpe
)
implicit def forInt: YamlDecoder[Int] = YamlDecoder { case s @ ScalarNode(value, _) =>
value.toIntOption.toRight(cannotParse(value, "Int", s))
}
implicit def forLong: YamlDecoder[Long] = YamlDecoder { case s @ ScalarNode(value, _) =>
value.toLongOption.toRight(cannotParse(value, "Long", s))
}
implicit def forDouble: YamlDecoder[Double] = YamlDecoder { case s @ ScalarNode(value, _) =>
value.toDoubleOption.toRight(cannotParse(value, "Double", s))
}
implicit def forFloat: YamlDecoder[Float] = YamlDecoder { case s @ ScalarNode(value, _) =>
value.toFloatOption.toRight(cannotParse(value, "Float", s))
}
implicit def forShort: YamlDecoder[Short] = YamlDecoder { case s @ ScalarNode(value, _) =>
value.toShortOption.toRight(cannotParse(value, "Short", s))
}
implicit def forByte: YamlDecoder[Byte] = YamlDecoder { case s @ ScalarNode(value, _) =>
value.toByteOption.toRight(cannotParse(value, "Byte", s))
}
implicit def forBoolean: YamlDecoder[Boolean] = YamlDecoder { case s @ ScalarNode(value, _) =>
value.toBooleanOption.toRight(cannotParse(value, "Boolean", s))
}
implicit def forAny: YamlDecoder[Any] = new YamlDecoder[Any] {
def construct(node: Node)(implicit settings: LoadSettings = LoadSettings.empty) = {
node match {
case ScalarNode(value, tag: CoreSchemaTag) if Tag.corePrimitives.contains(tag) =>
tag match {
case Tag.nullTag => Right(None)
case Tag.boolean => value.toBooleanOption.toRight(cannotParse(value, "Boolean", node))
case Tag.int =>
if (value.startsWith("0b"))
Try(Integer.parseInt(value.drop(2), 8)).toEither.left
.map(t => ConstructError.from(t, "Int", node))
else if (value.startsWith("0x"))
Try(Integer.parseInt(value.drop(2), 8)).toEither.left
.map(t => ConstructError.from(t, "Int", node))
else value.toIntOption.toRight(cannotParse(value, "Int", node))
case Tag.float =>
value.toDoubleOption.toRight(cannotParse(value, "Double", node))
case Tag.str => Right(value)
}
case MappingNode(mappings, Tag.map) =>
val decoder = implicitly[YamlDecoder[Map[Any, Any]]]
decoder.construct(node)
case SequenceNode(seq, Tag.seq) =>
val decoder = implicitly[YamlDecoder[Seq[Any]]]
decoder.construct(node)
case _ =>
settings.constructors.get(node.tag) match {
case Some(decoder) => decoder.construct(node)
case None =>
Left(
ConstructError.from(
s"""|Could't construct runtime instance of ${node.tag}
|${node.pos.map(_.errorMsg).getOrElse("")}
|If you're using custom datatype consider using yaml.as[MyType] instead of Any
|Or define LoadSettings where you'll specify how to construct ${node.tag}
|""".stripMargin
)
)
}
}
}
}
implicit def forOption[T](implicit c: YamlDecoder[T]): YamlDecoder[Option[T]] = YamlDecoder {
node =>
if (node.tag == Tag.nullTag) Right(None)
else c.construct(node).map(Option(_))
}
private def constructFromNodes[T](nodes: Seq[Node])(implicit
c: YamlDecoder[T]
): Either[ConstructError, Seq[T]] = {
val constructed = nodes.map(c.construct(_))
constructed.partitionMap(identity) match {
case (Nil, rights) => Right(rights)
case (lefts, _) => Left(lefts.head)
}
}
implicit def forList[T](implicit c: YamlDecoder[T]): YamlDecoder[List[T]] = YamlDecoder {
case SequenceNode(nodes, _) =>
constructFromNodes[T](nodes).map(_.toList)
}
implicit def forSeq[T](implicit c: YamlDecoder[T]): YamlDecoder[Seq[T]] = YamlDecoder {
case SequenceNode(nodes, _) =>
constructFromNodes[T](nodes)
}
implicit def forSet[T](implicit c: YamlDecoder[T]): YamlDecoder[Set[T]] = YamlDecoder {
case SequenceNode(nodes, _) =>
constructFromNodes[T](nodes).map(_.toSet)
}
implicit def forMap[K, V](implicit
keyDecoder: YamlDecoder[K],
valueDecoder: YamlDecoder[V]
): YamlDecoder[Map[K, V]] = YamlDecoder { case MappingNode(mappings, _) =>
val decoded: Seq[
Either[ConstructError, (K, V)]
] = mappings.toSeq
.map { case (key, value) =>
(keyDecoder.construct(key) -> valueDecoder.construct(value))
}
.map { case (key, value) =>
for {
k <- key
v <- value
} yield (k -> v)
}
decoded.partitionMap(identity) match {
case (lefts, _) if lefts.nonEmpty => Left(lefts.head)
case (_, rights) => Right(rights.toMap)
}
}
implicit def forString: YamlDecoder[String] = YamlDecoder { case ScalarNode(value, _) =>
Right(value)
}
}