scala提取器模式用于复杂验证,但具有良好的错误输出

scala提取器模式用于复杂验证,但具有良好的错误输出,scala,scala-2.10,Scala,Scala 2.10,我正在努力在某个用例中使用提取器模式,在这个用例中它似乎非常强大 我首先输入来自web请求的Map[String,String]。这是对api的searchRequest或countRequest searchRequest有密钥 查询(必选) fromDate(可选默认值) toDate(可选默认值) nextToken(可选) maxResults(可选默认值) countRequest有密钥 查询(必选) fromDate(可选默认值) toDate(可选默认值) bucket(可选

我正在努力在某个用例中使用提取器模式,在这个用例中它似乎非常强大

我首先输入来自web请求的Map[String,String]。这是对api的searchRequest或countRequest

searchRequest有密钥

  • 查询(必选)
  • fromDate(可选默认值)
  • toDate(可选默认值)
  • nextToken(可选)
  • maxResults(可选默认值)
countRequest有密钥

  • 查询(必选)
  • fromDate(可选默认值)
  • toDate(可选默认值)
  • bucket(可选默认值)
然后,我想将这两种结构都转换为一种组合类型结构,如下所示

protected case class CommonQueryRequest(
  originalQuery: String,
  fromDate: DateTime,
  toDate: DateTime
)

case class SearchQueryRequest(
  commonRequest: CommonQueryRequest,
  maxResults: Int,
  nextToken: Option[Long])

case class CountRequest(commonRequest: CommonQueryRequest, bucket: String)
如您所见,我正在将字符串转换为DateTimes和Int、Long等。我的问题是,我确实需要针对无效fromDate与无效toDate格式、无效maxResults与无效下一个标记(如果可用)的错误

同时,我需要保留默认值(这取决于是搜索还是计数请求)

当然,随着地图的传入,您可以告诉我搜索与计数,所以在我第一次尝试时,我添加了一个key=“type”,其值为search或count,这样我至少可以在这方面进行匹配

我是否走上了正确的道路?我认为使用匹配可能比我们现有的实现更干净,但我越走这条路,它似乎变得有点丑陋

谢谢,
迪安

我建议你看看scalaz.Validation and ValidationNel。这是收集验证错误的极好方法,非常适合输入请求验证

您可以在此处了解有关验证的更多信息:。然而,在我的示例中,我使用scalaz 7.1,它可能与本文中描述的略有不同。然而,主要思想仍然是一样的

以下是您的用例的小示例:

  import java.util.NoSuchElementException

  import org.joda.time.DateTime
  import org.joda.time.format.DateTimeFormat

  import scala.util.Try

  import scalaz.ValidationNel
  import scalaz.syntax.applicative._
  import scalaz.syntax.validation._

  type Input = Map[String, String]
  type Error = String

  case class CommonQueryRequest(originalQuery: String,
                                fromDate: DateTime,
                                toDate: DateTime)

  case class SearchQueryRequest(commonRequest: CommonQueryRequest,
                                maxResults: Int,
                                nextToken: Option[Long])

  case class CountRequest(commonRequest: CommonQueryRequest, bucket: String)

  def stringField(field: String)(input: Input): ValidationNel[Error, String] =
    input.get(field) match {
      case None => s"Field $field is not defined".failureNel
      case Some(value) => value.successNel
    }


  val dateTimeFormat = DateTimeFormat.fullTime()

  def dateTimeField(field: String)(input: Input): ValidationNel[Error, DateTime] =
    Try(dateTimeFormat.parseDateTime(input(field))) recover {
      case _: NoSuchElementException => DateTime.now()
    }  match {
      case scala.util.Success(dt) => dt.successNel
      case scala.util.Failure(err) => err.toString.failureNel
    }

  def intField(field: String)(input: Input): ValidationNel[Error, Int] =
    Try(input(field).toInt) match {
      case scala.util.Success(i) => i.successNel
      case scala.util.Failure(err) => err.toString.failureNel
    }

  def countRequest(input: Input): ValidationNel[Error, CountRequest] =
    (
      stringField  ("query")   (input) |@|
      dateTimeField("fromDate")(input) |@|
      dateTimeField("toDate")  (input) |@|
      stringField  ("bucket")  (input)
    ) { (query, from, to, bucket) =>
        CountRequest(CommonQueryRequest(query, from, to), bucket)
    }


  val validCountReq = Map("query" -> "a", "bucket" -> "c")
  val badCountReq = Map("fromDate" -> "invalid format", "bucket" -> "c")

  println(countRequest(validCountReq))
  println(countRequest(badCountReq))

scalactic看起来也很酷,我可能会走这条路(虽然不确定我们是否可以使用该库,但我想我会继续前进,直到有人说不)。

+1回答得很好,但出于某种原因,我们的libs中不允许使用scalaz,尽管这至少给了我一些好主意。您可以将验证代码从scalaz复制到您的项目中,并假装它根本不是scalaz。当您的项目中有50%的scalaz时,您可以安全地将其作为依赖项引入;)