Last active
August 29, 2015 13:56
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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