irregular
irregular

Reputation: 1587

Issue with elegantly accumulating errors using Either[String, B] in Scala

I am trying to parse a csv row here and each field can be a different type. To handle the error accumulation I am using Either[String, B] where the String is an error message and B is the value. The issue here is that B can be different types, Option[Int], String, Array[String], resulting in my Map being type (String, Either[String,java.io.Serializable]) effectively making the Map unreusable. Is there a way (I'm definitely sure there is) to more elegantly accumulate errors while also reusing those values to populate properties on an object?

override def parseCsv(implicit fields: Map[String, String]): Either[String, User] = {

  val parsedValues = Map(Headers.Id -> getFieldAsString(Headers.Id),
                    Headers.FirstName -> getFieldAsString(Headers.FirstName),
                    Headers.LastName -> getFieldAsString(Headers.LastName),
                    Headers.Email -> getFieldAsString(Headers.Email),
                    Headers.TimeZone -> getFieldAsString(Headers.TimeZone),
                    Headers.Region -> getOption(Headers.Region),
                    Headers.Phone -> getOption(Headers.Phone),
                    Headers.ProfileImage -> getFieldAsString(Headers.ProfileImage),
                    Headers.Roles -> getFieldAsArray(Headers.Roles))
  val errors = parsedValues.collect { case (key, Left(errors)) => errors }
  if (!errors.isEmpty) Left(errors.mkString(", "))
  else {
    val user = new User
    user.id = getFieldAsString(Headers.Id).right.get
    user.firstName = getFieldAsString(Headers.FirstName).right.get
    user.lastName = getFieldAsString(Headers.LastName).right.get
    user.email = getFieldAsString(Headers.Email).right.get
    user.timeZone = getFieldAsString(Headers.TimeZone).right.get
    user.phoneNumber = (for {
                          region <- getOption(Headers.Region).right.get
                          phone <- getOption(Headers.Phone).right.get
                          _ = validatePhoneNumber(phone, region)
                        } yield {
                          new PhoneNumber(region, phone)
                        }).orNull
    user.profileImageUrl = getFieldAsString(Headers.ProfileImage).right.get
    user.roles = getFieldAsArray(Headers.Roles).right.get
    Right(user)
  }
}

Upvotes: 1

Views: 263

Answers (1)

Nagarjuna Pamu
Nagarjuna Pamu

Reputation: 14825

Create case classes for all types of Bs. These case classes must extend some common trait. While populating the user object just pattern match and retrieve values.

sealed trait Result {
  val paramName: String
}

case class OptionInt(override val paramName: String, value: Option[Int]) extends Result

case class ArrayString(override val paramName: String, value: Array[String]) extends Result

case class StringValue(override val paramName: String, value: String) extends Result

now the final type would be like Either[String, Result]

after parsing the whole file create a List[Result]

If you are expecting age as Option[Int] and firstName as String then do this

list.foreach { result =>
    result match {
      case Option("age", value) => userId.age = value.getOrElse(defaultAge)
      case StringValue("firstName", value) => userId.firstName = value
      case StringValue("email", value) => userId.email = value
      case _ => //do nothing
    }
}

Upvotes: 1

Related Questions