使 Vapor API 响应 JSON API 符合规范
Making Vapor API response JSON API Spec compliant
我有一个 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
到这个端点(伪代码)
,我可以非常简单地return数据的内容
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
class 并传入数据,这样我就可以构建响应,但这并没有解决错误 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)
})
}
我不确定我该怎么做。这些是尝试过的 classes
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
}
}
我是否需要使用泛型来设置响应 class?谁能举个例子?
您需要 data
成为 [Dish]
class ApiResonse {
var links: Links?
var data: [Dish]
init(links: Links, data: [Dish]) {
self.links = links
self.data = [Dish]
}
}
复合对象ApiResponse
中的每个class
或struct
都需要遵守Content
协议。 Content
协议包括用于 JSON 解码和编码的 Codable
协议。
请注意 Any
不 遵守 Codable
协议,因此 Any
不能用作响应的任何组成部分。有关详细信息,请参阅 Vapor 3 Docs: "Using Content" and Vapor 4 Docs: "Content"。
Vapor 3: all content types (JSON, protobuf, URLEncodedForm, Multipart, etc) are treated the same. All you need to parse and serialize content is a Codable
class or struct.
Vapor 4: Vapor's content API allows you to easily encode / decode Codable
structs to / from HTTP messages.
完全符合Content
的对象或复合对象可以用作ResponseEncodable
响应。
当每个 路由端点解析为特定的 Content
协议时,ApiResponse
模型可以是 通用的 输入.
带有以下代码的示例项目位于 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
:Returns 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
:Returns 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])
})
}
我有一个 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
class 并传入数据,这样我就可以构建响应,但这并没有解决错误 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)
})
}
我不确定我该怎么做。这些是尝试过的 classes
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
}
}
我是否需要使用泛型来设置响应 class?谁能举个例子?
您需要 data
成为 [Dish]
class ApiResonse {
var links: Links?
var data: [Dish]
init(links: Links, data: [Dish]) {
self.links = links
self.data = [Dish]
}
}
复合对象
ApiResponse
中的每个class
或struct
都需要遵守Content
协议。Content
协议包括用于 JSON 解码和编码的Codable
协议。请注意
Any
不 遵守Codable
协议,因此Any
不能用作响应的任何组成部分。有关详细信息,请参阅 Vapor 3 Docs: "Using Content" and Vapor 4 Docs: "Content"。Vapor 3: all content types (JSON, protobuf, URLEncodedForm, Multipart, etc) are treated the same. All you need to parse and serialize content is a
Codable
class or struct.Vapor 4: Vapor's content API allows you to easily encode / decode
Codable
structs to / from HTTP messages.完全符合
Content
的对象或复合对象可以用作ResponseEncodable
响应。当每个 路由端点解析为特定的
Content
协议时,ApiResponse
模型可以是 通用的 输入.
带有以下代码的示例项目位于 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
:Returns 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
:Returns 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])
})
}