CornucopiaHTTP is a Swift Package Manager library that wraps URLSession in a modern async/await API, providing typed requests, automatic JSON handling, optional compression, progress callbacks, and background transfers.
Add CornucopiaHTTP to your package manifest:
dependencies: [
.package(url: "https://github.com/Cornucopia-Swift/CornucopiaHTTP", branch: "master")
]Import the module in the targets that need networking:
.target(
name: "MyApp",
dependencies: [
.product(name: "CornucopiaHTTP", package: "CornucopiaHTTP")
]
)import CornucopiaHTTP
struct User: Codable {
let id: Int
let name: String
}
var request = URLRequest(url: URL(string: "https://api.example.com/users/1")!)
request.setValue("application/json", forHTTPHeaderField: "Accept")
let user: User = try await HTTP.GET(from: request)let networking = Networking()
struct CreateUser: Encodable { var name: String }
struct CreatedUser: Decodable { var id: Int; var name: String }
let url = URL(string: "https://api.example.com/users")!
let create = CreateUser(name: "Ada")
let created: CreatedUser = try await networking.POST(item: create, to: URLRequest(url: url))
print("New user id", created.id)let downloadURL = FileManager.default.temporaryDirectory.appendingPathComponent("archive.zip")
let request = URLRequest(url: URL(string: "https://downloads.example.com/archive.zip")!)
let headers = try await networking.GET(from: request, to: downloadURL, progressObserver: { progress in
print("Progress", progress.fractionCompleted)
})
print("Saved to", downloadURL.path)
print("Content-Length:", headers["Content-Length"] ?? "unknown")#if canImport(ObjectiveC)
let backgroundNetworking = OOPNetworking.shared
let task = try backgroundNetworking.GET(from: URLRequest(url: url), to: downloadURL)
print("Task identifier:", task.taskIdentifier)
#endif- Reuse an existing
URLSession: setNetworking.customURLSessionbefore creating instances. - Observe UI state: provide a
CornucopiaCore.BusynessObserverviaNetworking.busynessObserverto toggle loading indicators. - Enable upload compression: allow gzip on selected endpoints.
try Networking.enableCompressedUploads(
for: Regex("https://api.example.com/v1/.*"),
key: "api-v1"
)Disable it again with Networking.disableCompressedUploads(for: "api-v1").
Networking.Error captures common failure cases:
.unsuitableRequestfor malformed requests.unsuccessful(HTTP.Status)for non-success status codes.unsuccessfulWithDetailswhen the server returns a JSON error payload.decodingErrorwhen decoding the response fails
Use Swift’s do/catch to differentiate between them:
do {
let profile: User = try await HTTP.GET(from: request)
} catch Networking.Error.unsuccessful(let status) {
logger.error("Server returned \(status)")
} catch Networking.Error.decodingError(let error) {
logger.error("JSON decoding failed: \(error)")
} catch {
logger.error("Unexpected networking error: \(error)")
}Networking.registerMockData(_:httpStatus:contentType:for:) lets you stub responses without hitting the network.
let url = URL(string: "https://api.example.com/users/preview")!
let payload = try JSONEncoder().encode(User(id: 42, name: "Preview"))
Networking.registerMockData(
payload,
httpStatus: .OK,
contentType: .applicationJSON,
for: url
)
let preview: User = try await HTTP.GET(from: URLRequest(url: url)) // served from the mockMocks are stored in memory. Register the data you expect before invoking your API under test.
- FaviconFetcher – locate a site’s favicon (or fall back to
/favicon.ico) using the existing networking stack. - Progress helpers –
Networking.ProgressObserverclosures work for both real and mocked downloads.
Tests rely on a JSON server running at http://localhost:3000. Start it in a separate terminal:
json-server json-server/db.jsonThen run the Swift tests:
swift test
swift test --filter HTTPTests # run a subset- iOS 16.0+
- macOS 13.0+
- tvOS 16.0+
- watchOS 9.0+
- Linux with Swift 5.10+
- CornucopiaCore – JSON codecs, logging, utility types
- SWCompression – gzip compression
- FoundationBandAid – Linux compatibility layer
CornucopiaHTTP is released under the MIT license. Contributions and bug reports are welcome.