Skip to content

Instantly share code, notes, and snippets.

@TomTriple
Last active November 12, 2016 14:43
Show Gist options
  • Save TomTriple/4a7abea85fa2fb3720a1435fd6527534 to your computer and use it in GitHub Desktop.
Save TomTriple/4a7abea85fa2fb3720a1435fd6527534 to your computer and use it in GitHub Desktop.
combinator library android validation
// 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