I have Map[String,String]
of configuration values. I want to extract a series of keys and provide meaningful error messages if any of them are missing. For example:
val a = Map("url"->"http://example.com", "user"->"bob", "password"->"12345")
Say I want to transform this into a case class:
case class HttpConnectionParams(url:String, user:String, password: String)
Now, I can simply use a for loop to extract the values:
for(url <- a.get("url");
user <- a.get("user");
password <- a.get("password")) yield {
HttpConnectionParams(url,user,password)
}
To get an Option[HttpConnectionParams]
. This is nice and clean, except if I get a None
then I don't know what was missing. I'd like to provide that information.
Enter scalaz. I'm using version 7.1.3.
From what I've been able to put together (a good reference is here) I can use disjunctions:
for(url <- a.get("url") \/> "Url must be supplied";
user <- a.get("user") \/> "Username must be supplied";
password <- a.get("password") \/> "Password must be supplied") yield {
HttpConnectionParams(url,user,password)
}
This is nice because now I get an error message, but this is railway oriented because it stops at the first failure. What if I want to get all of the errors? Let's use validation and the applicative builder (aka "|@|"):
val result = a.get("url").toSuccess("Url must be supplied") |@|
a.get("username").toSuccess("Username must be supplied") |@|
a.get("password").toSuccess("Password must be supplied")
result.tupled match {
case Success((url,user,password)) => HttpConnectionParams(url,user,password)
case Failure(m) => println("There was a failure"+m)
}
This does what I expect, but I have some questions about the usage:
import scalaz._
somehow didn't work for me.[1] How can I figure this out from the API docs?[1] After much consternation I arrived at this set of imports for the applicative use-case. Hopefully this helps somebody:
import scalaz.std.string._
import scalaz.syntax.std.option._
import scalaz.syntax.apply._
import scalaz.Success
import scalaz.Failure
You can do this a little more nicely by defining a helper method and skipping the .tupled
step by using .apply
:
import scalaz._, Scalaz._
def lookup[K, V](m: Map[K, V], k: K, message: String): ValidationNel[String, V] =
m.get(k).toSuccess(NonEmptyList(message))
val validated: ValidationNel[String, HttpConnectionParams] = (
lookup(a, "url", "Url must be supplied") |@|
lookup(a, "username", "Username must be supplied") |@|
lookup(a, "password", "Password must be supplied")
)(HttpConnectionParams.apply)
Also, please don't be ashamed to use import scalaz._, Scalaz._
. We all do it and it's just fine in the vast majority of cases. You can always go back and refine your imports later. I also still stand by this answer I wrote years ago—you shouldn't feel like you need to have a comprehensive understanding of Scalaz (or cats) in order to be able to use pieces of it effectively.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With