Created
June 19, 2015 21:19
-
-
Save anonymous/594f7b8fc3425793a244 to your computer and use it in GitHub Desktop.
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
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