Skip to content

ConnectableKit is a Swift package for the Vapor framework that simplifies the response DTOs and JSON structures for API projects.

License

Notifications You must be signed in to change notification settings

tugcanonbas/connectable-kit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ConnectableKit

ConnectableKit is a Swift package for the Vapor framework that simplifies the response DTOs and JSON structures for API projects.

Features

  • Generic JSON structure: The Connectable protocol allows you to define a wrapped Vapor Content structs.
  • Custom HTTPStatus for every responses.
  • ErrorMiddleware configurations for handling Vapor's error as ConnectableKit JSON output.
  • CORSMiddleware configurations for handling Vapor's CORSMiddleware with ease.

Structure

TypeDescriptionType
statusFive possible cases: information, success, redirection, failure, and error.ResponseStatus: String
messageOptional custom message from server.String? = nil
dataGeneric associated type that represents the data that is being sent as a response. It can be any type that conforms to Vapor's Content protocol, which includes types such as String, Int, and custom structs or classes.Connectable? = nil
{
  "status": "success",
  "message": "Profile fetched successfully.",
  "data": {
    "id": "EBAD7AA7-A0AF-45F7-9D40-439C62FB26DD",
    "name": "Tuğcan",
    "surname": "ÖNBAŞ",
    "profileImage": "http://localhost:8080/default_profile_image.png",
    "profileCoverImage": "http://localhost:8080/default_profile_cover_image.png"
  }
}

Installation

ConnectableKit can be installed using Swift Package Manager. Simply add the following line to your Package.swift file:

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

Usage

To use the ConnectableKit Framework,

- For Connectable

In Struct, simply conform that Profile is 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
}

In Response call .DTO for responding wrapped generic response.

return .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: "http://localhost:8080/default_profile_image.png",
        profileCoverImage: "http://localhost:8080/default_profile_cover_image.png"
    )
    try await profile.save(on: req.db)

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

- For Empty Response

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 protocol

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
    }
}

- For ErrorMiddleware

import ConnectableKit

Simply call ConnectableKit.configureErrorMiddleware(app) for default error handling.

ConnectableKit.configureErrorMiddleware(app)

Or, you can use custom error middleware error handling with 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)

Error Response Example

Database Error:

{
  "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")
}

Response

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

If you use ConnectableKitErrorMiddleware, don't forget to use before all middleware configureations.

See in Vapor's Documentation Error Middleware

- For CORSMiddleware

import ConnectableKit
ConnectableKit.configureCors(app)

func configureCORS

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

Inspiration

The JSend specification, developed by Omniti Labs, outlines a consistent JSON response format for API endpoints. I found the specification to be very helpful in ensuring that API consumers can easily understand and parse the responses returned by the API.

As a result, I have heavily borrowed from the JSend specification for the response format used in this project. Specifically, I have adopted the status field to indicate the overall success or failure of the request, as well as the use of a message field to provide additional context for the response.

While I have made some modifications to the response format to suit the specific needs of this project, I am grateful for the clear and thoughtful guidance provided by the JSend specification.


License

ConnectableKit is available under the MIT license. See the LICENSE file for more info.


About

ConnectableKit is a Swift package for the Vapor framework that simplifies the response DTOs and JSON structures for API projects.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Languages