Skip to content

Instantly share code, notes, and snippets.

Created June 19, 2015 21:19
Show Gist options
  • Save anonymous/594f7b8fc3425793a244 to your computer and use it in GitHub Desktop.
Save anonymous/594f7b8fc3425793a244 to your computer and use it in GitHub Desktop.
import scalaz._, Scalaz._
object ErrorHandlingExample {
type PartNumber = String
sealed trait CpuType
case object Intel extends CpuType
case object Qualcomm extends CpuType
case class PhoneSpec(
partNumber: PartNumber,
name: String, // Cannot be empty string
screenSize: Double, // Cannot be negative
cpu: CpuType,
ramAmount: Int // Must be a power of 2
)
case class CatalogEntry(fields: Map[String, String])
case class Catalog(entries: List[(PartNumber, CatalogEntry)])
case class PhoneSpecError(partNumber: PartNumber, errors: NonEmptyList[String])
type ValidatedPhoneSpecs[A] = ValidationNel[PhoneSpecError, A]
def parseCatalog(c: Catalog): ValidationNel[PhoneSpecError, List[PhoneSpec]] = {
c.entries.traverse[ValidatedPhoneSpecs, PhoneSpec]{case (pn, entry) => parsePhoneSpec(pn, entry.fields).leftMap(NonEmptyList(_))}
}
def parsePhoneSpec(pn: PartNumber, fields: Map[String, String]): Validation[PhoneSpecError, PhoneSpec] = {
def mkError(error: String) = NonEmptyList(error)
val name = for {
raw <- fields.get("name") \/> mkError("Missing name field")
_ <- validate(raw)(_ != "") \/> mkError("Empty name field")
} yield raw
val size = for {
raw <- fields.get("screenSize") \/> mkError("Missing screen size field")
parsed <- parseDouble(raw) \/> mkError("Screen size was not a valid decimal")
_ <- validate(parsed)(_ > 0) \/> mkError("Screen size was not a positive number")
} yield parsed
val cpu = for {
raw <- fields.get("cpuType") \/> mkError("Missing CPU type field")
parsed <- parseCpuType(raw) \/> mkError("Invalid CPU type")
} yield parsed
val ramAmount = for {
raw <- fields.get("ramAmount") \/> mkError("Missing RAM amount field")
parsed <- parseInt(raw) \/> mkError("RAM amount was not an integer")
_ <- validate(parsed)(_ > 0) \/> mkError("RAM amount was not a positive number")
_ <- validate(parsed)(isPowerOfTwo) \/> mkError("RAM amount was not a power of two")
} yield parsed
(name.validation |@|
size.validation |@|
cpu.validation |@|
ramAmount.validation)(PhoneSpec(pn, _, _, _, _)).leftMap(PhoneSpecError(pn, _))
}
def parseInt(s: String): Option[Int] = util.Try(s.toInt).toOption
def parseDouble(s: String): Option[Double] = util.Try(s.toDouble).toOption
def parseCpuType(s: String): Option[CpuType] = s.toUpperCase match {
case "INTEL" => Some(Intel)
case "QUALCOMM" => Some(Qualcomm)
case _ => None
}
def validate[A](a: A)(pred: A => Boolean): Option[Unit] = if (pred(a)) Some(()) else None
def isPowerOfTwo(x: Int): Boolean = (x & (x - 1)) == 0
}
/*
EXAMPLE USAGE:
scala> parsePhoneSpec("pn1", Map("name" -> "iphone", "screenSize" -> "4.5", "cpuType" -> "intel", "ramAmount" -> "8"))
ype" -> "intel", "ramAmount" -> "8"))
res4: scalaz.Validation[ErrorHandlingExample.PhoneSpecError,ErrorHandlingExample.PhoneSpec] = Success(PhoneSpec(pn1,iphone,4.5,Intel,8))
scala> parsePhoneSpec("pn2", Map("screenSize" -> "4.5 inches", "cpuType" -> "intel", "ramAmount" -> "7"))
l", "ramAmount" -> "7"))
res5: scalaz.Validation[ErrorHandlingExample.PhoneSpecError,ErrorHandlingExample.PhoneSpec] = Failure(PhoneSpecError(pn2,NonEmptyList(Missing name field, Screen size was not a valid decimal, RAM amount was not a power of two)))
parsePhoneSpec("pn3", Map("name" -> "3ds", "screenSize" -> "4.5", "cpuType" -> "nintendo", "ramAmount" -> "8"))
res5: scalaz.Validation[ErrorHandlingExample.PhoneSpecError,ErrorHandlingExample.PhoneSpec] = Failure(PhoneSpecError(pn3,NonEmptyList(Invalid CPU type)))
scala> val c = //elided... all 3 maps combined
c: ErrorHandlineExample.Catalog = //elided
scala> parseCatalog(c)
res7: scalaz.ValidationNel[ErrorHandlingExample.PhoneSpecError,List[ErrorHandlingExample.PhoneSpec]] = Failure(NonEmptyList(PhoneSpecError(pn2,NonEmptyList(Missing name field, Screen size was not a valid decimal, RAM amount was not a power of two)), PhoneSpecError(pn3,NonEmptyList(Invalid CPU type))))
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment