Skip to content

Instantly share code, notes, and snippets.

@EECOLOR
Last active August 29, 2015 13:56
Show Gist options
  • Save EECOLOR/8926176 to your computer and use it in GitHub Desktop.
Save EECOLOR/8926176 to your computer and use it in GitHub Desktop.
HList as a basis for validation and conversion. Using fold on nested HList. Applying method on elements in HList tree
import shapeless._
import shapeless.ops.hlist._
import scala.util.Try
object validation {
trait Violation {
def message: String
}
abstract class SimpleViolation(val message: String) extends Violation
sealed trait Result[+S, +F <: Violation] {
def isSuccess: Boolean
}
case class Success[S](value: S) extends Result[S, Nothing] {
val isSuccess = true
}
case class Failure[F <: Violation](violation: F) extends Result[Nothing, F] {
val isSuccess = false
}
case class Mapping[I, O, V <: Violation](map: I => Result[O, V])
def mapping[O](f: String => Result[O, Violation]): Mapping[String, O, Violation] =
new Mapping(f)
def structure[P <: Product, L <: HList](p: P)(implicit gen: Generic.Aux[P, L]): L = gen.to(p)
def structure[I](i: (String, I)): ((String, I) :: HNil) = i :: HNil
trait DataProvider[T] {
def get(key: String): Option[Either[T, DataProvider[T]]]
}
case class FoldResult[I, L <: HList](
dataProvider: DataProvider[I], result: L)
object foldAndValidate extends Poly2 {
class StructureViolation(message: String) extends SimpleViolation("structure." + message)
case class ExpectedDataProvider[I](key: String, value: I) extends StructureViolation("expectedDataProvider")
case class ExpectedValue(key: String) extends StructureViolation("expectedValue")
case class ExpectedKey(key: String) extends StructureViolation("expectedKey")
implicit def caseHList[Acc <: HList, L <: HList, I](
implicit folder: RightFolder[L, (DataProvider[I], HNil), foldAndValidate.type]) =
at[(String, L), (DataProvider[I], Acc)] { (keyAndList, dataProviderAndAcc) =>
val (key, list) = keyAndList
val (dataProvider, acc) = dataProviderAndAcc
val result: Result[folder.Out, Violation] =
dataProvider.get(key) match {
case Some(Right(dataProvider)) =>
Success(list.foldRight(dataProvider -> (HNil: HNil))(foldAndValidate))
case Some(Left(value)) =>
Failure(ExpectedDataProvider(key, value))
case None =>
Failure(ExpectedKey(key))
}
(dataProvider, (key -> result) :: acc)
}
implicit def caseMapping[Acc <: HList, I, O] =
at[(String, Mapping[I, O, Violation]), (DataProvider[I], Acc)] { (keyAndMapping, dataProviderAndAcc) =>
val (key, mapping) = keyAndMapping
val (dataProvider, acc) = dataProviderAndAcc
val result: Result[O, Violation] =
dataProvider.get(key) match {
case Some(Left(value)) =>
mapping.map(value)
case Some(Right(dataProvider)) =>
Failure(ExpectedValue(key))
case None =>
Failure(ExpectedKey(key))
}
(dataProvider, (key -> result) :: acc)
}
}
object removeDataProvider extends Poly1 {
implicit def caseList[L <: HList, I, Out <: HList](
implicit mapper: Mapper.Aux[removeDataProvider.type, L, Out]) =
at[(String, Result[(DataProvider[I], L), Violation])] { entry =>
val (key, result) = entry
val newResult: Result[Out, Violation] =
result match {
case Success((_, list)) =>
Success(list.map(removeDataProvider))
case Failure(violation) => Failure(violation)
}
println(newResult)
key -> newResult
}
implicit def caseOther[O](
implicit ev: O <:!< (_, _)) =
at[(String, Result[O, Violation])] { entry =>
println("here2")
entry
}
}
def validate[L <: HList, I, Out <: HList](list: L, dataProvider: DataProvider[I])(
implicit folder: RightFolder.Aux[L, (DataProvider[I], HNil), foldAndValidate.type, (DataProvider[I], Out)],
mapper:Mapper[removeDataProvider.type, Out]): mapper.Out = {
val (_, validated) = list.foldRight(dataProvider -> (HNil: HNil))(foldAndValidate)
validated.map(removeDataProvider)
}
}
case object NotAnInt extends validation.SimpleViolation("notAnInt")
case object NotThree extends validation.SimpleViolation("notThree")
import validation.mapping
import validation.structure
import validation.{ Success, Failure }
val toInt =
mapping { s =>
Try(Success(s.toInt))
.recover { case t: Throwable => Failure(NotAnInt) }
.get
}
val isThree =
mapping { s =>
if (s == "three") Success(s)
else Failure(NotThree)
}
val list =
structure(
"test" -> structure("one" -> toInt),
"one" -> toInt,
"two" -> structure(
"three" -> isThree,
"four" -> toInt,
"noData" -> structure(
"noValue" -> toInt),
"nine" -> toInt,
"five" -> structure(
"six" -> isThree,
"seven" -> toInt),
"eight" -> isThree))
val data =
Map(
"one" -> "1",
"two" -> Map(
"three" -> "three",
"four" -> "4a",
"five" -> Map(
"six" -> "six",
"seven" -> "7"),
"eight" -> "eight"))
import validation.DataProvider
class MapDataProvider(map: Map[String, Object]) extends DataProvider[String] {
def get(key: String): Option[Either[String, DataProvider[String]]] =
map.get(key)
.map {
case value: String =>
Left(value)
case map: (Map[String, Object] @unchecked) =>
Right(new MapDataProvider(map))
}
}
val dataProvider: DataProvider[String] = new MapDataProvider(data)
val result = validation.validate(list, dataProvider)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment