Содержимое для декодирования Vapor 3: делать / ловить для нескольких форматов записей? - PullRequest
3 голосов
/ 03 февраля 2020

У меня есть контроллер, действие создания которого я хочу принять 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: $0.widget)
    }
  } catch DecodingError.keyNotFound {
    return try req.content.decode(WidgetCreateObject.self).flatMap(to: Widget.self) {
      return createWidget(from: $0)
    }
  }
}

, где WidgetCreateObject выглядит примерно так:

struct WidgetCreateObject { var foo: String? }

, а WidgetCreateHolder выглядит так:

struct WidgetCreateHolder { var widget: WidgetCreateObject }

То есть, мое действие create должно try создать держатель, но если это не удастся, оно должно отловить ошибку и попытаться просто создать внутренний объект (a WidgetCreateObject). Однако, когда я внедряю этот код в Heroku и делаю запрос только с внутренним объектом JSON, я получаю это в своих журналах:

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

, хотя я пытаюсь перехватить эту ошибку!

Как я могу заставить мое действие создания принимать два разных формата объекта JSON?

1 Ответ

2 голосов
/ 04 февраля 2020

Разобрался!

Метод decode возвращает Future, так что фактическое декодирование (и, следовательно, ошибка) происходит позже, а не во время операции do / catch. Это означает, что нет способа отловить ошибку с помощью этой команды do catch.

К счастью, Future s имеет ряд методов, которым предшествует catch; меня интересует catchFlatMap, который принимает закрытие от Error -> Future<Decodable>. Этот метод «улавливает» ошибки, вызванные в вызванном Future, и передает ошибку в замыкание, используя результат во всех последующих фьючерсах.

Итак, я смог изменить свой код на:

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: $0)
        }
    }).flatMap(to: Widget.self) {
        return createWidget(from: $0.widget)
    }
}
...