Обеспечение соответствия API-интерфейса Vapor требованиям JSON API Spec - PullRequest
0 голосов
/ 08 сентября 2018

У меня есть API, написанный на Vapor.Я хотел бы следовать спецификации JSON API.

Мне трудно понять, как я могу создать свой объект ответа в правильном формате.

Например, я хотел бы, чтобы мои ответы были структурированы следующим образом ...

{
  "links": {
    "self": "http://example.com/dish",
    "next": "http://example.com/dish?page=2",
    "last": "http://example.com/dish?page=10"
  },
  "data": [{
    "title": "Spag Bol",
    "course": "main",
    "description": "BasGetti",
    "price": 3.9900000000000002
  },
  {
    "title": "Ice Cream",
    "course": "desert",
    "description": "Vanilla",
    "price": 0.98999999999999999
  }]
}

Я могу вернуть содержимое данных довольно просто, если POST к этой конечной точке (псевдокод))

router.post(Dish.self, at: "api/dish") { req, data -> Future<Dish> in
    return Future.map(on: req, { () -> Dish in
        data.id = 001
        return data
    })
}

Я попытался создать класс ApiResponse и передать данные, чтобы я мог структурировать ответ, но это не сработало с ошибкой Cannot convert return expression of type 'ApiResonse' to return type 'Dish'

   router.post(Dish.self, at: "api/dish") { req, data -> Future<Dish> in
        return Future.map(on: req, { () -> Dish in
            data.id = 001
            return ApiResonse(links: Links(self: "http://google.com", next: "http://google.com", last: "http://google.com"), data: data)
        })
    }

IЯ не уверен, как я могу это сделать.Это классы попыток

final class Dish: Content {
    var id: Int?
    var title: String
    var description: String
    var course: String
    var price: Double

    init(title: String, description: String, course: String, price: Double) {
        self.title = title
        self.description = description
        self.course = course
        self.price = price
    }
}

struct Links {
    var `self`: String?
    var next: String?
    var last: String?
}

class ApiResonse {
    var links: Links?
    var data: Any

    init(links: Links, data: Any) {
        self.links = links
        self.data = data
    }
}

Нужно ли использовать Generics для настройки класса ответа?Кто-нибудь может привести пример?

Ответы [ 2 ]

0 голосов
/ 09 сентября 2018
  1. Каждый class или struct в составном объекте ApiResponse должен соответствовать протоколу Content. Протокол Content включает протокол Codable для декодирования и кодирования JSON.

  2. Обратите внимание, что Any не не соответствует протоколу Codable, и поэтому Any не может использоваться в качестве какой-либо составной части ответа. См. Vapor Docs: «Использование содержимого» * ​​1021 * для получения более подробной информации.

    В Vapor 3 все типы контента (JSON, protobuf, URLEncodedForm, Multipart и т. Д.) Обрабатываются одинаково. Все, что вам нужно для анализа и сериализации контента, это класс или структура Codable.

  3. Объект или составной объект, который полностью соответствует Content, можно использовать в качестве ответа ResponseEncodable.

  4. Модель ApiResponse может быть универсальной , когда каждая конечная точка маршрута разрешается в определенный Content совместимый с протоколом тип .

Пример проекта с кодом ниже находится на GitHub: VaporExamplesLab / Example-SO-VaporJsonResponse .

Примеры моделей

struct Dish: Content {
    var id: Int?
    var title: String
    var description: String
    var course: String
    var price: Double

    init(id: Int? = nil, title: String, description: String, course: String, price: Double) {
        self.id = id
        self.title = title
        self.description = description
        self.course = course
        self.price = price
    }
}

struct Links: Content {
    var current: String?
    var next: String?
    var last: String?
}

struct ApiResponse: Content {
    var links: Links?
    var dishes: [Dish]

    init(links: Links, dishes: [Dish]) {
        self.links = links
        self.dishes = dishes
    }
}

Пример POST: Возвращает ApiResponse

router.post(Dish.self, at: "api/dish") { 
    (request: Request, dish: Dish) -> ApiResponse in
    var dishMutable = dish
    dishMutable.id = 001

    var links = Links()
    links.current = "http://example.com"
    links.next = "http://example.com"
    links.last = "http://example.com"

    return ApiResponse(links: links, dishes: [dishMutable])
}

Пример POST: Возвращает Future<ApiResponse>

router.post(Dish.self, at: "api/dish-future") { 
    (request: Request, dish: Dish) -> Future<ApiResponse> in
    var dishMutable = dish
    dishMutable.id = 002

    var links = Links()
    links.current = "http://example.com"
    links.next = "http://example.com"
    links.last = "http://example.com"

    return Future.map(on: request, { 
        () -> ApiResponse in
        return ApiResponse(links: links, dishes: [dishMutable])
    }) 
}

Полученный ответ JSON

Тело ответа для приведенного выше кода выдает следующее:

{
  "links": {
    "current": "http://example.com",
    "next": "http://example.com",
    "last": "http://example.com"
  },
  "dishes": [
    {
      "id": 1,
      "title": "Aztec Salad",
      "description": "Flavorful Southwestern ethos with sweet potatos and black beans.",
      "course": "salad",
      "price": 1.82
    }
  ]
}

Универсальная модель

struct ApiResponseGeneric<T> : Content where T: Content { 
    var links: Links?
    var data: T

    init(links: Links, data: T) {
        self.links = links
        self.data = data
    }
}

Конечная точка бетонного маршрута

router.post(Dish.self, at: "api/dish-generic-future") { 
    (request: Request, dish: Dish) -> Future<ApiResponseGeneric<[Dish]>> in
    var dishMutable = dish
    dishMutable.id = 004

    var links = Links()
    links.current = "http://example.com"
    links.next = "http://example.com"
    links.last = "http://example.com"

    return Future.map(on: request, { 
        () -> ApiResponseGeneric<[Dish]> in
        return ApiResponseGeneric<[Dish]>(links: links, data: [dishMutable])
    }) 
}
0 голосов
/ 09 сентября 2018

Вам нужно иметь data быть [Dish]

class ApiResonse {
    var links: Links?
    var data: [Dish]

    init(links: Links, data: [Dish]) {
        self.links = links
        self.data = [Dish]
    }
}
...