artemkalinovsky/Kite
A Swift 6 networking library with async/await, JSON/XML deserialization, and auth header support — running on iOS/macOS/tvOS/watchOS/visionOS/Linux/Windows/Android!
Features
async/await-first request execution- Small protocol-based request model
- Built-in JSON and XML response deserializers
- Raw-data and no-op deserializers for simple endpoints
- Query-string, JSON body, auth-header, and multipart upload support
- Zero external dependencies
- Explicit error behavior for auth failures, decode failures, and non-2xx responses
Requirements
- Swift 6
- Apple platforms: macOS 12+, iOS 15+, tvOS 15+, watchOS 8+, visionOS 1+
- Linux: any Swift 6-supported distribution
- Windows: any Swift 6-supported release
- Android: experimental via the nightly Swift SDK for Android
Installation 📦
Swift Package Manager
In Xcode, choose:
File -> Add Package Dependencies... -> Up to Next Major Version starting at 5.0.0
Or add Kite to Package.swift:
.package(url: "https://github.com/artemkalinovsky/Kite.git", from: "5.0.0")Example:
// swift-tools-version:6.0
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]
)
],
dependencies: [
.package(url: "https://github.com/artemkalinovsky/Kite.git", from: "5.0.0")
],
targets: [
.target(
name: "MyPackage",
dependencies: ["Kite"]
)
]
)Quick Start 🧑💻
Suppose you want to fetch users from this JSON payload:
{
"results": [
{
"name": {
"first": "brad",
"last": "gibson"
},
"email": "brad.gibson@example.com"
}
]
}Create the client:
import Kite
let apiClient = APIClient()Define the response model:
struct User: Decodable {
struct Name: Decodable {
let first: String
let last: String
}
let name: Name
let email: String
}Define the request:
import Foundation
import Kite
struct FetchRandomUsersRequest: DeserializeableRequestProtocol {
var baseURL: URL { URL(string: "https://randomuser.me")! }
var path: String { "api" }
var deserializer: any ResponseDataDeserializer<[User]> {
JSONDeserializer<User>.collectionDeserializer(keyPath: "results")
}
}Execute it:
let (users, _) = try await apiClient.execute(request: FetchRandomUsersRequest())Request Defaults
HTTPRequestProtocol keeps the required surface deliberately small:
baseURLis the only required property.pathdefaults to"".methoddefaults to.get.parametersdefaults tonil.headersdefaults to[:].multipartFormDatadefaults tonil.
Parameter encoding is determined by the request shape:
- For
.getrequests,parametersare encoded as URL query items. - For non-GET requests without
multipartFormData,parametersare encoded as a JSON body andContent-Typeis set toapplication/json. - When
multipartFormDatais present,parametersare included as text form fields alongside the files in the same multipart body.
Deserializers
Kite ships with three built-in deserializer styles:
VoidDeserializer()for endpoints where you only care whether the request succeededRawDataDeserializer()when you want the raw response bytesJSONDeserializerforDecodablemodelsXMLDeserializerfor types that conform toXMLObjectDeserialization— built into Kite, no extra import needed
Examples:
let users = JSONDeserializer<User>.collectionDeserializer(keyPath: "results")
let profile = JSONDeserializer<User>.singleObjectDeserializer()
let feed = XMLDeserializer<FeedItem>.collectionDeserializer(keyPath: "response", "items", "item")To use XMLDeserializer, conform your model to XMLObjectDeserialization:
import Kite
struct FeedItem: XMLObjectDeserialization {
let title: String
static func deserialize(_ node: XMLIndexer) throws -> Self {
FeedItem(title: try node["title"].value())
}
}Authenticated Requests
Conform to AuthRequestProtocol when the endpoint requires an Authorization header:
import Foundation
import Kite
struct FetchProfileRequest: AuthRequestProtocol, DeserializeableRequestProtocol {
let accessToken: String
var baseURL: URL { URL(string: "https://api.example.com")! }
var path: String { "profile" }
var deserializer: any ResponseDataDeserializer<User> {
JSONDeserializer<User>.singleObjectDeserializer()
}
}By default, Kite sends:
Authorization: Bearer <accessToken>If your backend uses a different prefix, override accessTokenPrefix. If your backend expects a different authorization header name or casing, override authorizationHeaders.
Raw Data Requests
Use RawDataDeserializer when the endpoint does not return JSON or XML:
import Foundation
import Kite
struct DownloadAvatarRequest: DeserializeableRequestProtocol {
var baseURL: URL { URL(string: "https://cdn.example.com")! }
var path: String { "avatar.png" }
var deserializer: any ResponseDataDeserializer<Data> {
RawDataDeserializer()
}
}Multipart Uploads
Provide a [String: URL] dictionary through multipartFormData to upload files:
import Foundation
import Kite
struct UploadAvatarRequest: AuthRequestProtocol, DeserializeableRequestProtocol {
let accessToken: String
let imageURL: URL
var baseURL: URL { URL(string: "https://api.example.com")! }
var path: String { "upload" }
var method: HTTPMethod { .post }
var multipartFormData: [String: URL]? { ["file": imageURL] }
var deserializer: any ResponseDataDeserializer<URL> {
JSONDeserializer<URL>.singleObjectDeserializer(keyPath: "avatar_url")
}
}Kite builds the multipart body and sets the correct Content-Type boundary automatically.
Error Handling
Kite keeps failure modes explicit:
URLError(.userAuthenticationRequired)when an authenticated request resolves to an emptyAuthorizationheaderAPIClientError.unacceptableStatusCodefor non-2xx HTTP responsesJSONDeserializerErrorandXMLDeserializerErrorfor decode failures
Example:
do {
let (users, _) = try await apiClient.execute(request: FetchRandomUsersRequest())
print(users)
} catch let error as APIClientError {
print(error.localizedDescription)
} catch {
print(error.localizedDescription)
}Project Status
Kite is production-ready. Pull requests, questions, and suggestions are welcome.
Apps Using Kite
License 📄
Kite is released under the MIT license. See LICENSE for details.
Package Metadata
Repository: artemkalinovsky/Kite
Stars: 44
Forks: 3
Open issues: 0
Default branch: main
Primary language: swift
License: MIT
Topics: alamofire, android, api, api-rest, apimanager, jsonparser, linux, multiplatform, networklayer, networklibrary, swift, swift-framework, swift6, xmlparser
README: README.md