Last active
November 12, 2016 14:43
-
-
Save TomTriple/4a7abea85fa2fb3720a1435fd6527534 to your computer and use it in GitHub Desktop.
combinator library android validation
This file contains hidden or 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
// the highlevel validation combinators reside at the bottom of this gist e.g. "def crown" or "def trunk" | |
protected object Validation { | |
sealed abstract class VRes[+A] | |
case class Vok[+A](get:A) extends VRes[A] | |
case class Verr(err:String) extends VRes[Nothing] | |
type V[+A] = TextView => VRes[A] | |
class Ops[+A](v:V[A]) { | |
def map[B](f:A => B):V[B] = _map(v)(f) | |
def ||[B >: A](v2:V[B]):V[B] = _or(v, v2) | |
def flatMap[B](f:A => V[B]):V[B] = _flatMap(v)(f) | |
} | |
type BetweenError = (Double, Double) => String | |
private val betweenError:BetweenError = "Wert muss >= " + _ + " und <= " + _ + " sein" | |
class OpsDouble(v:V[Double]) extends Ops(v) { | |
def between(min:Double, max:Double, error:BetweenError = betweenError) = _between(v)(min, max, error(min, max)) | |
} | |
class OpsInt(v:V[Int]) extends Ops(v) { | |
def between(min:Int, max:Int, error:BetweenError= betweenError) = _between(v)(min, max, error(min, max)) | |
} | |
implicit def ops[A](v:V[A]) = new Ops(v) | |
implicit def opsInt(v:V[Int]) = new OpsInt(v) | |
implicit def opsDouble(v:V[Double]) = new OpsDouble(v) | |
def _flatMap[A, B](v:V[A])(f: A => V[B]):V[B] = { in => v(in) match { | |
case Vok(get) => f(get).apply(in) | |
case it @ Verr(error) => it | |
} | |
} | |
def unitOk[A](get:A):V[A] = { in => Vok(get) } | |
def unitErr[A](error:String):V[A] = { in => Verr(error) } | |
def _map[A, B](v:V[A])(f: A => B):V[B] = _flatMap(v)(f.andThen(unitOk)) | |
def regex(r:Regex, err:String = "regex failed"):V[String] = { in => | |
r.findFirstIn(in.getText).map(Vok.apply).getOrElse(Verr(err)) | |
} | |
def digit():V[Int] = regex("^[0-9]+$".r, "Ganzzahl erwartet z.B. \"98\"").map(_.toInt) | |
def none():V[String] = { in => unitOk(in.getText.toString)(in) } | |
def float():V[Float] = regex("^[0-9]+[.,][0-9]+$".r, "Kommazahl erwartet z.B. \"98,43\"").map(_.replace(',', '.').toFloat) | |
def double():V[Double] = float().map(_.toDouble) | |
def _or[A](a:V[A], b:V[A]):V[A] = { in => | |
a(in) match { | |
case it @ Validation.Vok(get) => it | |
case error1:Verr => b(in) match { | |
case it @ Validation.Vok(get) => it | |
case error2:Verr => Verr(error1.err + " oder " + error2.err) | |
} | |
} | |
} | |
def digitOrFloat():V[Float] = digit.map(_.toFloat) || float() | |
def digitOrDouble():V[Double] = digit.map(_.toDouble) || double() | |
private def _between(v:V[Double])(min:Double, max:Double, error:String):V[Double] = v.flatMap { | |
case it if it >= min && it <= max => unitOk(it) | |
case _ => unitErr(error) | |
} | |
private def _between(v:V[Int])(min:Int, max:Int, error:String):V[Int] = _between(v.map(_.toDouble))(min.toDouble, max.toDouble, error).map(_.toInt) | |
def year():V[Int] = digit between (0, Calendar.getInstance().get(Calendar.YEAR)) | |
def treeheight():V[Double] = digitOrDouble between(0.0, 150.0) | |
def crown():V[Double] = digitOrDouble between (0.0, 50.0) | |
def trunk():V[Int] = digit() between(0, 1000) | |
def treenumber(originalTreenumber: => String):V[String] = regex("^.+$".r, "Please us a valid name").flatMap { it => | |
val existsInDb = DbHelper.block(new Callable[Boolean] { | |
override def call(): Boolean = DaoTree.getInstance().get(it) != null | |
}) | |
if(existsInDb && originalTreenumber != it) { | |
unitErr("Ist bereits vergeben") | |
} else { | |
unitOk(it) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment