由于某种原因,我必须手动验证一些变量,并返回一个映射,其中包含每个变量的错误消息的序列。我决定使用可变集合,因为我认为没有其他选择了:
val errors = collection.mutable.Map[String, ListBuffer[String]]()
//field1
val fieldToValidate1 = getData1()
if (fieldToValidate1 = "")
errors("fieldToValidate1") += "it must not be empty!"
if (validate2(fieldToValidate1))
errors("fieldToValidate1") += "validation2!"
if (validate3(fieldToValidate1))
errors("fieldToValidate1") += "validation3!"
//field2
val fieldToValidate2 = getData1()
//approximately the same steps
if (fieldToValidate2 = "")
errors("fieldToValidate2") += "it must not be empty!"
//.....
在我看来,它看起来有点笨拙,应该有其他优雅的解决方案。如果可能的话,我也不想使用可变集合。你的想法?
答案 0 :(得分:2)
您可以使用errors
定义var
,而不是使用可变集合,并以这种方式更新它。
var errors = Map[String, List[String]]().withDefaultValue(Nil)
errors = errors updated ("fieldToValidate1", errors("fieldToValidate1") ++ List("it must not be empty!"))
errors = errors updated ("fieldToValidate1", errors("fieldToValidate1") ++ List("validation2"))
代码看起来更乏味,但它来自可变集合。
答案 1 :(得分:1)
那么什么是支票的好类型?我正在考虑A => Option[String]
如果A
是您所测试对象的类型。如果您的错误消息不依赖于测试对象的值,(A => Boolean, String)
可能会更方便。
//for constructing checks from boolean test and an error message
def checkMsg[A](check: A => Boolean, msg: => String): A => Option[String] =
x => if(check(x)) Some(msg) else None
val checks = Seq[String => Option[String]](
checkMsg((_ == ""), "it must not be empty"),
//example of using the object under test in the error message
x => Some(x).filterNot(_ startsWith "ab").map(x => x + " does not begin with ab")
)
val objectUnderTest = "acvw"
val errors = checks.flatMap(c => c(objectUnderTest))
正如我刚才所说,您要求每张支票都带有标签的地图。在这种情况下,您需要提供检查标签。然后,您的支票类型为(String, A => Option[String])
。
答案 2 :(得分:1)
虽然[相对]广泛使用scalaz的验证方法(正如@senia所示),但我认为这是一种有点压倒性的方法(如果你把scalaz带到你的项目中)你必须是一个经验丰富的scala开发人员,否则它可能会给你带来更多弊大于利。
很好的替代方法可能是ScalaUtils使用了Or
and Every
专门用于此目的,实际上如果您使用的是ScalaTest,您已经看到了它们的一个示例(它使用下面的scalautils) 。我羞耻地从他们的文档中复制粘贴的例子:
import org.scalautils._
def parseName(input: String): String Or One[ErrorMessage] = {
val trimmed = input.trim
if (!trimmed.isEmpty) Good(trimmed) else Bad(One(s""""${input}" is not a valid name"""))
}
def parseAge(input: String): Int Or One[ErrorMessage] = {
try {
val age = input.trim.toInt
if (age >= 0) Good(age) else Bad(One(s""""${age}" is not a valid age"""))
}
catch {
case _: NumberFormatException => Bad(One(s""""${input}" is not a valid integer"""))
}
}
import Accumulation._
def parsePerson(inputName: String, inputAge: String): Person Or Every[ErrorMessage] = {
val name = parseName(inputName)
val age = parseAge(inputAge)
withGood(name, age) { Person(_, _) }
}
parsePerson("Bridget Jones", "29")
// Result: Good(Person(Bridget Jones,29))
parsePerson("Bridget Jones", "")
// Result: Bad(One("" is not a valid integer))
parsePerson("Bridget Jones", "-29")
// Result: Bad(One("-29" is not a valid age))
parsePerson("", "")
// Result: Bad(Many("" is not a valid name, "" is not a valid integer))
话虽如此,如果你想坚持使用没有任何外部依赖性的核心scala,我认为你不能比你当前的方法做得更好。
答案 3 :(得分:0)
如果您可以使用scalaz
汇总错误的最佳解决方案是Validation
:
def validate1(value: String) =
if (value == "") "it must not be empty!".failNel else value.success
def validate2(value: String) =
if (value.length > 10) "it must not be longer than 10!".failNel else value.success
def validate3(value: String) =
if (value == "error") "it must not be equal to 'error'!".failNel else value.success
def validateField(name: String, value: String): ValidationNel[(String, String), String] =
(
validate1(value) |@|
validate2(value) |@|
validate3(value)
).tupled >| value leftMap { _.map{ name -> _ } }
val result = (
validateField("fieldToValidate1", getData1()) |@|
validateField("fieldToValidate2", getData2())
).tupled
然后你可以得到这样的可选错误Map
:
val errors =
result.swap.toOption.map{
_.toList.groupBy(_._1).map{ case (k, v) => k -> v.map(_._2) }
}
// Some(Map(fieldToValidate2 -> List(it must not be equal to 'error'!), fieldToValidate1 -> List(it must not be empty!)))