ConnectableKit

ConnectableKit 是一个 Swift 包,用于 Vapor 框架,它简化了 API 项目的响应 DTO 和 JSON 结构。

特性


结构

类型 描述 类型
status 五种可能的案例:信息,成功,重定向,失败和错误。 ResponseStatus: String
message 来自服务器的可选自定义消息。 String? = nil
data 通用的关联类型,表示作为响应发送的数据。它可以是任何符合 Vapor 的 Content 协议的类型,包括 String、Int 和自定义结构体或类。 Connectable? = nil
{
  "status": "success",
  "message": "Profile fetched successfully.",
  "data": {
    "id": "EBAD7AA7-A0AF-45F7-9D40-439C62FB26DD",
    "name": "Tuğcan",
    "surname": "ÖNBAŞ",
    "profileImage": "https://:8080/default_profile_image.png",
    "profileCoverImage": "https://:8080/default_profile_cover_image.png"
  }
}

安装

ConnectableKit 可以使用 Swift Package Manager 安装。只需将以下行添加到你的 Package.swift 文件中

dependencies: [
    .package(url: "https://github.com/tugcanonbas/connectable-kit.git", from: "1.0.0")
]
dependencies: [
    .product(name: "ConnectableKit", package: "connectable-kit"),
],

用法

要使用 ConnectableKit 框架,

- 对于 Connectable

在结构体中,只需让 Profile 遵循 Connectable 协议即可

import ConnectableKit

struct Profile: Model, Connectable {
    @ID(key: .id)
    var id: UUID

    @Field(key: "name")
    var name: String

    @Field(key: "surname")
    var surname: String

    @Field(key: "profileImage")
    var profileImage: String

    @Field(key: "profileCoverImage")
    var profileCoverImage: String
}

在 Response 中调用 .DTO 以响应包装后的通用响应。

返回 .toDTO(_ httpStatus: Vapor.HTTPStatus = .ok, status: ResponserStatus = .success, message: String? = nil) -> Responser<Self>

app.get("/profiles", ":id") { req -> Profile.DTO in
    let id = try req.parameters.require("id", as: UUID.self)
    let profile = try await Profile.query(on: req.db).filter(\.$id == id).first()!

    return profile.toDTO(message: "Profile fetched successfully.")
}
app.post("/profiles") { req -> Profile.DTO in
    let profile = Profile(
        id: UUID(),
        name: "Tuğcan",
        surname: "ÖNBAŞ",
        profileImage: "https://:8080/default_profile_image.png",
        profileCoverImage: "https://:8080/default_profile_cover_image.png"
    )
    try await profile.save(on: req.db)

    return profile.toDTO(.created, message: "Profile fetched successfully.")
}

- 对于空响应

app.put("/profiles", ":id") { req -> Connector.DTO in
    let id = try req.parameters.require("id", as: UUID.self)
    let update = try req.content.decode(Profile.Update.self)

    let profile = try await Profile.query(on: req.db).filter(\.$id == id).first()!
    profile.name = update.name
    try await profile.save(on: req.db)

    return Connector.toDTO(.accepted, message: "Profile updated successfully.")
}
{
  "status": "success",
  "message": "Profile updated successfully."
}

Connectable 协议

public protocol Connectable: Content {
    associatedtype DTO = Responser<Self>
    func toDTO(_ httpStatus: Vapor.HTTPStatus, status: ResponserStatus, message: String?) -> Responser<Self>
}

public extension Connectable {
    func toDTO(_ httpStatus: Vapor.HTTPStatus = .ok, status: ResponserStatus = .success, message: String? = nil) -> Responser<Self> {
        let response = Responser(httpStatus, status: status, message: message, data: self)
        return response
    }
}

- 对于 ErrorMiddleware

import ConnectableKit

只需调用 ConnectableKit.configureErrorMiddleware(app) 即可进行默认的错误处理。

ConnectableKit.configureErrorMiddleware(app)

或者,你可以使用带有 ConnectableErrorMiddleware.ErrorClosure 的自定义错误中间件错误处理。

let errorClosure: ConnectableErrorMiddleware.ErrorClosure = { error in
            let status: Vapor.HTTPResponseStatus
            let reason: String
            let headers: Vapor.HTTPHeaders

            switch error {
            case let abort as Vapor.AbortError:
                reason = abort.reason
                status = abort.status
                headers = abort.headers
            case let customError as CustomError:
                reason = customError.localizedDescription
                status = customError.httpResponseStatus
                headers = [:]
            default:
                reason = app.environment.isRelease
                    ? "Something went wrong."
                    : String(describing: error)
                status = .internalServerError
                headers = [:]
            }

            return (status, reason, headers)
        }

ConnectableKit.configureErrorMiddleware(app, errorClosure: errorClosure)

错误响应示例

数据库错误

{
  "status": "error",
  "message": "server: duplicate key value violates unique constraint \"uq:users.username\" (_bt_check_unique)"
}

AbortError

guard let profile = try await Profile.query(on: req.db).filter(\.$id == id).first() else {
    throw Abort(.notFound, reason: "User not found in our Database")
}

响应

{
  "status": "failure",
  "message": "User not found in our Database"
}

如果你使用 ConnectableKitErrorMiddleware,请不要忘记在所有中间件配置之前使用它。

请参阅 Vapor 的文档 Error Middleware

- 对于 CORSMiddleware

import ConnectableKit
ConnectableKit.configureCors(app)

func configureCORS

public static func configureCORS(_ app: Vapor.Application, configuration: Vapor.CORSMiddleware.Configuration? = nil)

灵感来源

Omniti Labs 开发的 JSend 规范,概述了 API 端点的一致 JSON 响应格式。我发现该规范对于确保 API 使用者可以轻松理解和解析 API 返回的响应非常有帮助。

因此,我大量借鉴了 JSend 规范,用于本项目中使用的响应格式。具体来说,我采用了 status 字段来指示请求的总体成功或失败,以及使用 message 字段来为响应提供额外的上下文。

虽然我对响应格式进行了一些修改,以适应本项目的特定需求,但我仍然感谢 JSend 规范提供的清晰和周到的指导。


许可证

ConnectableKit 在 MIT 许可证下可用。有关更多信息,请参见 LICENSE 文件。