-
Notifications
You must be signed in to change notification settings - Fork 1
Defining Endpoints
Endpoint has one required member, associatedtype Response, and one commonly overridden member,
path. Everything else has a default.
struct GetUser: Endpoint {
typealias Response = User
let id: Int
var path: String { "/users/\(id)" }
}
struct SearchUsers: Endpoint {
typealias Response = [User]
let query: String
var path: String { "/users" }
var queryParameters: QueryParameters? { ["q": .string(query), "limit": .int(20)] }
}
struct UpdateProfile: Endpoint {
typealias Response = User
let draft: ProfileDraft
var method: HTTPMethod { .patch }
var path: String { "/me" }
var body: RequestBody? { .json(draft) }
var authentication: AuthRequirement { .required }
}Path parameters are templated with :name and filled from pathParameters:
struct GetPost: Endpoint {
typealias Response = Post
let userID: Int
let postID: Int
var path: String { "/users/:userID/posts/:postID" }
var pathParameters: [String: String] { ["userID": "\(userID)", "postID": "\(postID)"] }
}Grouping operations in an enum works too:
enum UserAPI {
struct Profile: Endpoint { typealias Response = User; var path: String { "/me" } }
struct Update: Endpoint {
typealias Response = User
let draft: ProfileDraft
var method: HTTPMethod { .patch }
var path: String { "/me" }
var body: RequestBody? { .json(draft) }
}
}| Member | Default | Purpose |
|---|---|---|
baseURL |
nil (use the environment's) |
Point one endpoint at a different host |
method |
.get |
HTTP method |
headers |
[:] |
Per-endpoint headers, merged over the environment's |
queryParameters |
nil |
Typed query string |
pathParameters |
[:] |
:name template substitutions |
body |
nil |
Request body |
authentication |
.none |
.none / .required / .custom(AuthStrategy)
|
timeout |
nil (use the environment's) |
Per-endpoint timeout |
priority |
.normal |
Queue ordering when concurrency-limited |
retryPolicy |
nil (use the config's) |
Per-endpoint retry override |
cachePolicy |
nil (use the config's) |
Per-endpoint cache override |
deduplicate |
nil (use the config's) |
Force dedup on/off for this endpoint |
skipRequestQueue |
false |
Bypass the concurrency queue (use for the refresh call) |
offlineBehavior |
.fail |
.fail or .queue(expiresAfter:)
|
decoder |
nil (use the config's) |
Per-endpoint JSONDecoder
|
decode(_:response:using:) |
generic dispatch | Fully custom decoding |
Configuration is layered: environment defaults → endpoint overrides → call site.
var config = NetworkConfiguration(
baseURL: "https://api.example.com",
headers: ["Accept": "application/json"],
timeout: 30
)
config.retry = .aggressive
config.maxConcurrentRequests = 4
config.enableDeduplication = true
config.cache = .memory(policy: .networkFirst)
config.metrics = InMemoryMetrics()
config.environment.logLevel = .basic
let client = NetworkClient(configuration: config)| Concern | Where |
|---|---|
| Base URL, default headers, timeout, log level |
NetworkEnvironment (via NetworkConfiguration) |
| Path / query / per-endpoint headers / body | Endpoint |
| Cache policy |
config.cache default, Endpoint.cachePolicy override |
| Retry policy |
config.retry default, Endpoint.retryPolicy override |
| Authentication |
config.authorization + Endpoint.authentication
|
| Pinning | config.sslPinning |
| Concurrency limit | config.maxConcurrentRequests |
RequestBody cases:
var body: RequestBody? { .json(draft) } // Encodable -> application/json
var body: RequestBody? { .data(rawBytes, contentType: "application/octet-stream") }
var body: RequestBody? { .string("hello", contentType: "text/plain") }
var body: RequestBody? { .formURLEncoded(["grant_type": "refresh_token", "token": t]) }
var body: RequestBody? { .multipart(form) } // see Uploads, Downloads and Progress.json encodes with the configuration's defaultEncoder (or the endpoint's), which by default
converts to snake_case and encodes dates as ISO 8601. Override NetworkConfiguration.defaultEncoder
for different conventions.
Response can be:
Response type |
Behavior |
|---|---|
Decodable / Codable
|
Decoded with the endpoint or configuration JSONDecoder
|
Data |
Raw bytes, no decoding |
String |
UTF-8 decoded body |
EmptyResponse |
For 204 / empty-body endpoints |
Status handling is centralized: 2xx succeeds; 401 → .unauthorized, 403 → .forbidden,
404 → .notFound, 422 → .validation, 429 → .rateLimited(retryAfter:),
5xx → .server, anything else unexpected → .unacceptableStatusCode(code, context). Every
error case carries a ResponseContext (status, headers, body, decoded server message) where one
exists. See Error Handling.
Custom decoding for a non-JSON API:
struct GetProtobufThing: Endpoint {
typealias Response = Thing
var path: String { "/thing" }
func decode(_ data: Data, response: HTTPURLResponse, using decoder: JSONDecoder) throws -> Thing {
try Thing(serializedBytes: data)
}
}SwiftNetworkKit is source-available proprietary software (not open source). See FAQ Licensing and Support. Repo: https://github.com/ihusnainalii/SwiftNetworkKit · Docs: https://swiftnetworkkit.vercel.app/
Getting started
Guides
- Defining Endpoints
- Authentication and Token Refresh
- Security and Certificate Pinning
- Retry Policy
- Interceptors
- Request Management
- Caching
- Offline Request Queue
- Uploads Downloads and Progress
- Pagination and Batch
- Combine and SwiftUI
- Error Handling
- Observability
- Testing and Mocking
- Best Practices and a Complete Example
- Platform Notes, Thread Safety and Performance
Reference