Skip to content

Instantly share code, notes, and snippets.

@OleTraveler
Created July 20, 2014 20:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save OleTraveler/525ba037ae94625ba822 to your computer and use it in GitHub Desktop.
Save OleTraveler/525ba037ae94625ba822 to your computer and use it in GitHub Desktop.
Code Written for blog entry:
package com.czarism.blog
import scalaz._
import Scalaz._
/**
* Created by tstevens on 7/18/14.
*/
object FavorValidation {
type ErrorMessage = String
def asciiOnly(str: String) : Validation[NonEmptyList[ErrorMessage],String] =
if ("[\\x00-\\x7F]+".r.pattern.matcher(str).matches) str.success
else "only ascii characters are allowed".failNel
def maxStrLength(max: Int): (String) => Validation[NonEmptyList[ErrorMessage],String] = str =>
if (str.length <= max) str.success
else s"can not exceed ${max} characters".failNel
def digitsOnly(str: String) : Validation[NonEmptyList[ErrorMessage], String] =
if ("""^\d*$""".r.pattern.matcher(str).matches) str.success
else s"only numbers are allowed".failNel
def toInt(str: String) : Validation[NonEmptyList[ErrorMessage], Int] = try {
str.toInt.success
} catch {
case e: NumberFormatException => "must be a number".failNel
}
def modTen(str: String) : Validation[NonEmptyList[ErrorMessage], String] = {
def passesModTen(str: String): Boolean = ???
if (passesModTen(str)) str.success
else s"invalid number".failNel
}
def validMonth(m: Int) : Validation[NonEmptyList[ErrorMessage], Int] =
if (m >= 1 && m <= 12) m.success
else "invalid month".failNel
def positiveNu(m: Int) : Validation[NonEmptyList[ErrorMessage], Int] =
if (m > 0) m.success
else "must be positive".failNel
def validExpiration(currentMonth: Int, currentYear:Int) : (Int,Int) => Validation[NonEmptyList[ErrorMessage], (Int,Int)] = (month,year) =>
if (year > currentYear || (year === currentYear && month >= currentMonth)) (month, year).success
else "card has expired".failNel
case class CreditCard(cardholder: String, number: String, expMonth: Int, expYear:Int)
def validateCardHolder(cardholder: String): Validation[NonEmptyList[ErrorMessage], String] =
(asciiOnly(cardholder) |@| maxStrLength(10)(cardholder)){ (s ,_) => s }
def validate(cardholder: String, number: String, expMonth: String, expYear: String) : Validation[NonEmptyList[ErrorMessage], CreditCard] = {
/** Accumulate both */
val cardHolderV = validateCardHolder(cardholder)
/** Check digits, then modTen */
val numberV = digitsOnly(number).flatMap(modTen(_))
val validToday = validExpiration(7,2014)
val monthYear = for {
m <- toInt(expMonth).flatMap(validMonth(_))
y <- toInt(expYear).flatMap(positiveNu(_))
my <- validToday(m,y)
} yield my
/** If there were any errors, return them. Otherwise create a credit card */
(cardHolderV |@| numberV |@| monthYear) { (c,n,my) => CreditCard(c,n,my._1,my._2)}
}
}
object FavorDisjunction {
type ErrorMessage = String
def asciiOnly(str: String): ErrorMessage \/ String =
if ("[\\x00-\\x7F]+".r.pattern.matcher(str).matches) \/-(str)
else -\/("only ascii characters are allowed")
def maxStrLength(max: Int): (String) => ErrorMessage \/ String = str =>
if (str.length <= max) \/-(str)
else -\/(s"can not exceed ${max} characters")
def digitsOnly(str: String): ErrorMessage \/ String =
if ( """^\d*$""".r.pattern.matcher(str).matches) \/-(str)
else -\/(s"only numbers are allowed")
def toInt(str: String): ErrorMessage \/ Int = try {
\/-(str.toInt)
} catch {
case e: NumberFormatException => -\/("must be a number")
}
def modTen(str: String): ErrorMessage \/ String = {
def passesModTen(str: String): Boolean = ???
if (passesModTen(str)) \/-(str)
else -\/(s"invalid number")
}
def validMonth(m: Int): ErrorMessage \/ Int =
if (m >= 1 && m <= 12) \/-(m)
else -\/("invalid month")
def positiveNumber(m: Int): ErrorMessage \/ Int =
if (m > 0) \/-(m)
else -\/("must be positive")
def validExpiration(currentMonth: Int, currentYear: Int): (Int, Int) => ErrorMessage \/ (Int, Int) = (month, year) =>
if (year > currentYear || (year === currentYear && month >= currentMonth)) \/-((month, year))
else -\/("card has expired")
case class CreditCard(cardholder: String, number: String, expMonth: Int, expYear: Int)
def validateCardHolder(cardholder: String): NonEmptyList[ErrorMessage] \/ String =
(asciiOnly(cardholder).validation.toValidationNel |@| maxStrLength(10)(cardholder).validation.toValidationNel) { (s, _) => s}
.disjunction
def validate(cardholder: String, number: String, expMonth: String, expYear: String): NonEmptyList[ErrorMessage] \/ CreditCard = {
/** Accumulate both */
val cardHolderV = validateCardHolder(cardholder)
/** Check digits, then modTen */
val numberV = digitsOnly(number).flatMap(modTen(_))
val validToday = validExpiration(7, 2014)
val monthYear = for {
my <- (toInt(expMonth).flatMap(validMonth(_)).validation.toValidationNel |@|
toInt(expYear).flatMap(positiveNumber(_)).validation.toValidationNel) {
(_, _)
}
.disjunction
validMY <- validToday(my._1, my._2).leftMap(NonEmptyList(_)) //error return type of my is NonEmptyList[ErrorMessage]
} yield validMY
/** If there were any errors, return them. Otherwise create a credit card */
(cardHolderV.validation |@|
numberV.validation.toValidationNel |@|
monthYear.validation) { (c, n, my) => CreditCard(c, n, my._1, my._2)}
.disjunction
}
}
object Disjunction {
implicit class DisjunctionI[A, B](d: \/[A, B]) {
// /** Takes a function from A to some type B that has a typeclass Semigroup[B] */
// def validationS[N[_], S: Semigroup[N[A]]](f: A => N) : Validation[N[A],B] =
// d match {
// case -\/(a) => Failure(f(a))
// case \/-(b) => Success(b)
// }
def validationF[F[_]](f: A => F[A]): Validation[F[A],B] =
d match {
case -\/(a) => Failure(f(a))
case \/-(b) => Success(b)
}
def validationNel: Validation[NonEmptyList[A], B] = validationF(NonEmptyList(_))
def leftNel: \/[NonEmptyList[A],B] = d.leftMap(NonEmptyList(_))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment