如何获取正确的参数验证错误

How to get the correct parameter validation error

我有一个简单的路由,参数应该被提取到 case 类:

val myRoute: Route =
    get {
      path("resource") {
        parameters('foo, 'x.as[Int]).as(FooParams) { params =>
        ...
        } ~
        parameters('bar, 'x.as[Int]).as(BarParams) { params =>
          ...
        }
    }
}

case class FooParams(foo: String, x: Int) {
require(x > 1 && x < 10, "x for foos must be between 2 and 9")
}

case class BarParams(bar: String, x: Int) {
require(x > 10 && x < 20, "x for bars must be between 11 and 19")
}

案例 类 应该验证输入,因此无效输入将被 400 拒绝。

发生了拒绝,但是 404 并且错误消息具有误导性

我预计 .../resource?foo=a&x=0x for foos must be between 2 and 9 但它是 Request is missing required query parameter 'bar'

对于条形图也是如此,虽然我希望 .../resource?bar=a&x=0 会导致 400x for bars must be between 11 and 19,但它会以 404Request is missing required query parameter 'foo' 进行响应。

我在这里误解了什么以及如何解决它?

akka-http 2.0.3

编辑

4lex1v 的解决方案对我有用。让我有点困扰的是,我故意放弃了框架提供给我的帮助:我必须处理 foo 和 bar 都丢失的情况 'manually'。 x 范围内的拒绝也是如此。 OTOH,代码更加明确,包括对同时给出 foobar 的情况的处理,并且当两者都缺失时可以自定义 MissingQueryParamRejection

val myRoute2: Route =
(get & path("resource")) {
  parameters('foo ?, 'bar ?, 'x.as[Int]) {
    case (Some(foo), None, x) if x > 1 && x < 10 => {
      val params = FooParams(foo, x)
      ...
    }
    case (Some(foo), None, x) => reject(MalformedQueryParamRejection("x", s"x for foos must be between 2 and 10 but was $x"))

    case (None, Some(bar), x) if x > 10 && x < 20 => {
      val params = BarParams(bar, x)
      ...
    }
    case (None, Some(bar), x) => reject(MalformedQueryParamRejection("x", s"x for bars must be between 11 and 19 but was $x"))

    case (Some(foo), Some(bar), x) => reject(MalformedQueryParamRejection("bar", "expecting either foo or bar, received both"))
    case (None, None, x) => reject(MissingQueryParamRejection("foo or bar"))
  }
}

我不会使用两个不同的 parameters 指令,相反我会建议使用一个并将您的参数作为选项,即 parameters('foo?, 'bar?, x.as[Int])。该指令将提取您需要的数据,稍后您可以匹配并转换您需要的大小写,如下所示:

(get & path("...")) {
  parameters('foo?, 'bar?, x.as[Int]) {
    case (None, Some(bar), x) => BarParams(bar, x)
    case (Some(foo), None, x) => FooParams(foo, x)
    /**
     * Here comes you Rejection (you can make your own)
     */
    case _ => reject(MalfromedParametersRejection)
  }
}

我认为在构造函数中使用 require 的另一件事是不好的做法,给定的解决方案允许您使用守卫来处理您描述的情况:

parameters('foo?, 'bar?, x.as[Int]) {
  case (None, Some(bar), x) if x > 1 && x < 10 => BarParams(bar, x)
  //...
}

我认为您看到的问题的主要部分来自您的路线定义方式。通过在路径 "resource" 下定义这两个可能的参数集,然后当它错过 foo 参数时,您最终会在拒绝列表的开头得到一个 MissingParemeterRejectionValidationRejection 也在那里结束,但默认拒绝处理程序在决定将什么状态代码和消息传达给调用者时必须更喜欢 MissingParameterRejection。如果您只是像这样重新定义路线:

val myRoute: Route =
  get {
    path("resource") {
      parameters('foo, 'x.as[Int]).as(FooParams) { params =>
      ...
      } ~
    }
    path("resource2"){
      parameters('bar, 'x.as[Int]).as(BarParams) { params =>
        ...
      }
    }
  }

然后一切都按预期工作。在这种情况下,它甚至不会尝试评估参数,直到它接受了根路径。由于每个根路径都有不同的参数集,因此不可能在列表的头部出现不必要的丢失参数异常。

现在,如果这不是一个可接受的替代方案,那么您可以用 mapRejections 之类的东西包装该路由,以删除不必要的缺失参数拒绝(如果它包含验证拒绝)。像这样:

val validationWins = mapRejections{ rej =>
  val mapped = rej.filter(_.isInstanceOf[ValidationRejection])
  if (mapped.isEmpty) rej else mapped
}

val myRoute = 
  get {
    path("resource") {
      validationWins{
        parameters('foo, 'x.as[Int]).as(FooParams) { params =>
          complete(StatusCodes.OK)
        } ~ 
        parameters('bar, 'x.as[Int]).as(BarParams) { params =>
          complete(StatusCodes.OK)
        }        
    }
  }

理想情况下,我更喜欢在我的路线树中使用 cancelRejections 来删除树中无关紧要的东西,但没有一个干净的地方可以做到这一点,所以我使用 mapRejections 代替。