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

时间:2016-02-16 15:40:37

标签: scala akka query-parameters akka-http

我有一个简单的路由,其中​​参数应该被提取到案例类中:

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并且错误消息具有误导性

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

相同的酒吧,虽然我希望.../resource?bar=a&x=0生成400 x for bars must be between 11 and 19,但它会使用404Request is missing required query parameter 'foo'进行回复。

我在这里误解了什么以及如何解决这个问题?

akka-http 2.0.3

修改

4lex1v的解决方案适合我。令我困扰的是,我故意放弃框架为我提供的帮助:我必须处理foo和bar都“手动”丢失的情况。同样适用于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"))
  }
}

2 个答案:

答案 0 :(得分:3)

我不会使用两个不同的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)
  //...
}

答案 1 :(得分:3)

我认为您看到的问题的主要部分来自您的路线定义方式。通过在“资源”路径下定义这两个可能的参数集,然后当它错过foo参数时,最终会在拒绝列表的头部以MissingParemeterRejection结束。 ValidationRejection也会在那里结束,但默认拒绝处理程序在决定将状态代码和消息传递给调用者时必须更喜欢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来删除树中无关紧要的内容,但是没有一个干净的地方可以做到这一点,所以我使用{{1相反。