/
RowDecoderF.scala
154 lines (128 loc) · 5.93 KB
/
RowDecoderF.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
/*
* Copyright 2023 Lucas Satabin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fs2.data.csv
import cats._
import cats.data.{NonEmptyList, NonEmptyMap}
import cats.syntax.all._
import scala.annotation.tailrec
/** Describes how a row can be decoded to the given type.
*
* `RowDecoderF` provides convenient methods such as `map`, `emap`, or `flatMap`
* to build new decoders out of more basic one.
*
* Actually, `RowDecoderF` has a [[https://typelevel.org/cats/api/cats/MonadError.html cats `MonadError`]]
* instance. To get the full power of it, import `cats.syntax.all._`.
*/
@FunctionalInterface trait RowDecoderF[H[+a] <: Option[a], T, Header] {
def apply(row: RowF[H, Header]): DecoderResult[T]
/** Map the parsed value.
* @param f the mapping function
* @tparam T2 the result type
* @return a row decoder reading the mapped type
*/
def map[T2](f: T => T2): RowDecoderF[H, T2, Header] =
row => apply(row).map(f)
/** Map the parsed value to a new decoder, which in turn will be applied to
* the parsed value.
* @param f the mapping function
* @tparam T2 the result type
* @return a row decoder reading the mapped type
*/
def flatMap[T2](f: T => RowDecoderF[H, T2, Header]): RowDecoderF[H, T2, Header] =
row => apply(row).flatMap(f(_)(row))
/** Map the parsed value, potentially failing.
* @param f the mapping function
* @tparam T2 the result type
* @return a row decoder reading the mapped type
*/
def emap[T2](f: T => DecoderResult[T2]): RowDecoderF[H, T2, Header] =
row => apply(row).flatMap(f)
/** Fail-over. If this decoder fails, try the supplied other decoder.
* @param cd the fail-over decoder
* @tparam TT the return type
* @return a decoder combining this and the other decoder
*/
def or[TT >: T](cd: => RowDecoderF[H, TT, Header]): RowDecoderF[H, TT, Header] =
row =>
apply(row) match {
case Left(_) => cd(row)
case r @ Right(_) => r.leftCast[DecoderError]
}
/** Similar to [[or]], but return the result as an Either signaling which row decoder succeeded. Allows for parsing
* an unrelated type in case of failure.
* @param cd the alternative decoder
* @tparam B the type the alternative decoder returns
* @return a decoder combining both decoders
*/
def either[B](cd: RowDecoderF[H, B, Header]): RowDecoderF[H, Either[T, B], Header] =
row =>
apply(row) match {
case Left(_) =>
cd(row) match {
case l @ Left(_) => l.rightCast[Either[T, B]]
case r @ Right(_) => r.leftCast[T].asRight
}
case Right(value) => Right(Left(value))
}
}
object RowDecoderF extends ExportedRowDecoderFs {
implicit def identityRowDecoderF[H[+a] <: Option[a], Header]: RowDecoderF[H, RowF[H, Header], Header] = _.asRight
implicit def RowDecoderFInstances[H[+a] <: Option[a], Header]
: MonadError[RowDecoderF[H, *, Header], DecoderError] with SemigroupK[RowDecoderF[H, *, Header]] =
new MonadError[RowDecoderF[H, *, Header], DecoderError] with SemigroupK[RowDecoderF[H, *, Header]] {
override def map[A, B](fa: RowDecoderF[H, A, Header])(f: A => B): RowDecoderF[H, B, Header] =
fa.map(f)
def flatMap[A, B](fa: RowDecoderF[H, A, Header])(f: A => RowDecoderF[H, B, Header]): RowDecoderF[H, B, Header] =
fa.flatMap(f)
def handleErrorWith[A](fa: RowDecoderF[H, A, Header])(
f: DecoderError => RowDecoderF[H, A, Header]): RowDecoderF[H, A, Header] =
row => fa(row).leftFlatMap(f(_)(row))
def pure[A](x: A): RowDecoderF[H, A, Header] =
_ => Right(x)
def raiseError[A](e: DecoderError): RowDecoderF[H, A, Header] =
_ => Left(e)
def tailRecM[A, B](a: A)(f: A => RowDecoderF[H, Either[A, B], Header]): RowDecoderF[H, B, Header] = {
@tailrec
def step(row: RowF[H, Header], a: A): DecoderResult[B] =
f(a)(row) match {
case left @ Left(_) => left.rightCast[B]
case Right(Left(a)) => step(row, a)
case Right(right @ Right(_)) => right.leftCast[DecoderError]
}
row => step(row, a)
}
def combineK[A](x: RowDecoderF[H, A, Header], y: RowDecoderF[H, A, Header]): RowDecoderF[H, A, Header] = x or y
}
implicit def toMapCsvRowDecoder[Header]: CsvRowDecoder[Map[Header, String], Header] =
CsvRowDecoder.instance(_.toMap.asRight)
implicit def toNonEmptyMapCsvRowDecoder[Header: Order]: CsvRowDecoder[NonEmptyMap[Header, String], Header] =
CsvRowDecoder.instance(_.toNonEmptyMap.asRight)
implicit val toListRowDecoder: RowDecoder[List[String]] =
RowDecoder.instance(_.values.toList.asRight)
implicit val toNelRowDecoder: RowDecoder[NonEmptyList[String]] =
RowDecoder.instance(_.values.asRight)
// the following two can't be unified va RowDecoderF because type inference fails us
implicit def decodeResultCsvRowDecoder[Header, T](implicit
dec: CsvRowDecoder[T, Header]): CsvRowDecoder[DecoderResult[T], Header] =
r => Right(dec(r))
implicit def decodeResultRowDecoder[T](implicit dec: RowDecoder[T]): RowDecoder[DecoderResult[T]] =
r => Right(dec(r))
}
trait ExportedRowDecoderFs {
implicit def exportedRowDecoders[A](implicit exported: Exported[RowDecoder[A]]): RowDecoder[A] = exported.instance
implicit def exportedCsvRowDecoders[A](implicit
exported: Exported[CsvRowDecoder[A, String]]): CsvRowDecoder[A, String] = exported.instance
}