Vapor 3 解码内容:do/catch 用于多种 post 格式?

Vapor 3 decoding content: do/catch for multiple post formats?

我有一个控制器,我想接受其创建操作 JSON 要么像这样:

{ "foo": "bar" }

或者像这样:

{ "widget": { "foo": "bar" } }

也就是说,我想接受小部件或包裹在包含对象中的小部件。目前,我的控制器的创建操作看起来很像这样:

func createHandler(_ req: Request) throws -> Future<Widget> {
  do {
    return try req.content.decode(WidgetCreateHolder.self).flatMap(to: Widget.self) {
      return createWidget(from: [=14=].widget)
    }
  } catch DecodingError.keyNotFound {
    return try req.content.decode(WidgetCreateObject.self).flatMap(to: Widget.self) {
      return createWidget(from: [=14=])
    }
  }
}

其中 WidgetCreateObject 看起来像:

struct WidgetCreateObject { var foo: String? }

WidgetCreateHolder 看起来像:

struct WidgetCreateHolder { var widget: WidgetCreateObject }

也就是说,我的创建操作应该 try 来创建一个持有者,但如果失败,它应该捕获错误并尝试只创建内部对象 (a WidgetCreateObject)。但是,当我将此代码部署到 Heroku 并仅使用内部对象 JSON 发出请求时,我在日志中得到了这个:

[ ERROR ] DecodingError.keyNotFound: Value required for key 'widget'. (ErrorMiddleware.swift:26)

即使我正在尝试捕获该错误!

如何让我的创建操作接受两种不同格式的 JSON 对象?

想通了!

decode 方法 returns a Future,这样实际的解码(以及错误)发生在稍后,而不是在 do/catch 期间。这意味着无法使用此 do catch 捕获错误。

幸运的是,Futures 有一系列以catch 开头的方法;我感兴趣的是 catchFlatMap,它接受来自 Error -> Future<Decodable> 的闭包。此方法 'catches' 在被调用的 Future 中抛出的错误,并将错误传递给闭包,在任何下游 futures 中使用结果。

所以我能够将我的代码更改为:

func createHandler(_ req: Request) throws -> Future<Widget> {
    return try req.content.decode(WidgetCreateHolder.self).catchFlatMap({ _ in
        return try req.content.decode(WidgetCreateObject.self).map(to: WidgetCreateHolder.self) {
            return WidgetCreateHolder(widget: [=10=])
        }
    }).flatMap(to: Widget.self) {
        return createWidget(from: [=10=].widget)
    }
}