-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathArrivalValidator.scala
68 lines (56 loc) · 2.38 KB
/
ArrivalValidator.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
package com.psisoyev.train.station.arrival
import cats.{ FlatMap, Monad }
import com.psisoyev.train.station.Tracing.ops.TracingOps
import com.psisoyev.train.station.arrival.ArrivalValidator.ArrivalError.UnexpectedTrain
import com.psisoyev.train.station.arrival.ArrivalValidator.ValidatedArrival
import com.psisoyev.train.station.arrival.Arrivals.Arrival
import com.psisoyev.train.station.arrival.ExpectedTrains.ExpectedTrain
import com.psisoyev.train.station.{ Actual, Tracing, TrainId }
import derevo.derive
import derevo.tagless.applyK
import tofu.higherKind.Mid
import tofu.logging.Logging
import tofu.syntax.monadic._
import tofu.syntax.monoid.TofuSemigroupOps
import tofu.syntax.raise._
import tofu.{ Handle, Raise }
import scala.util.control.NoStackTrace
@derive(applyK)
trait ArrivalValidator[F[_]] {
def validate(arrival: Arrival): F[ValidatedArrival]
}
object ArrivalValidator {
sealed trait ArrivalError extends NoStackTrace
object ArrivalError {
type Handling[F[_]] = Handle[F, ArrivalError]
type Raising[F[_]] = Raise[F, ArrivalError]
case class UnexpectedTrain(id: TrainId) extends ArrivalError
}
case class ValidatedArrival(trainId: TrainId, time: Actual, expectedTrain: ExpectedTrain)
private class Log[F[_]: FlatMap: Logging] extends ArrivalValidator[Mid[F, *]] {
def validate(arrival: Arrival): Mid[F, ValidatedArrival] = { validation =>
F.info(s"Validating $arrival") *> validation <* F.info(s"Train ${arrival.trainId} validated")
}
}
private class Trace[F[_]: Tracing] extends ArrivalValidator[Mid[F, *]] {
def validate(arrival: Arrival): Mid[F, ValidatedArrival] = _.traced("train arrival: validation")
}
private class Impl[F[_]: Monad: ArrivalError.Raising](expectedTrains: ExpectedTrains[F]) extends ArrivalValidator[F] {
override def validate(arrival: Arrival): F[ValidatedArrival] =
expectedTrains
.get(arrival.trainId)
.flatMap { train =>
train
.map(ValidatedArrival(arrival.trainId, arrival.time, _))
.orRaise(UnexpectedTrain(arrival.trainId))
}
}
def make[F[_]: Monad: Logging: ArrivalError.Raising: Tracing](
expectedTrains: ExpectedTrains[F]
): ArrivalValidator[F] = {
val service = new Impl[F](expectedTrains)
val log: ArrivalValidator[Mid[F, *]] = new Log[F]
val trace: ArrivalValidator[Mid[F, *]] = new Trace[F]
(log |+| trace).attach(service)
}
}