A simple, scalable, and testable networking library for Swift.
- 🏗 Modular Architecture: Easily customize and extend the networking layer.
- 💪 Type-Safe Requests: Strong typing for API requests and responses.
- 📦 Lightweight: Built on top of URLSession for minimal overhead.
- 🚀 Modern Concurrency: Supports async/await in Swift.
- 🔄 Interceptor Support: Modify requests and responses globally.
- 🔐 Automatic Token Refresh: Built-in support for token refreshing.
- 📝 Logging: Customizable logging for debugging and monitoring.
- 🔄 Retry Mechanism: Implement retries with customizable policies.
- 🔧 Unit Tested: Comprehensive tests for reliability.
The HTTPClient protocol is responsible for making raw HTTP requests and returning the data and response. It abstracts the underlying URLSession and can be easily mocked for testing.
URLSessionHTTPClient is a concrete implementation of HTTPClient that uses URLSession to make network requests. It can be configured with custom URLSession instances.
RequestExecutor is a protocol for executing API requests and decoding responses into specified Swift model types.
APIRequestExecutor is a concrete implementation of RequestExecutor. It uses the provided HTTPClient to fetch data and decodes it into the specified model types.
Interceptors allow you to modify requests and responses globally. This is useful for adding headers, logging, error handling, token refreshing, and more.
- RequestInterceptor: A protocol for intercepting and modifying requests and responses.
- AuthorizationInterceptor: Adds authentication credentials to requests.
- TokenRefreshInterceptor: Handles automatic token refreshing upon receiving unauthorized responses.
- LoggingInterceptor: Provides customizable logging for requests and responses.
- RetryInterceptor: Implements retry logic for transient errors with customizable policies.
This networking library provides a convenient way to make API requests using APIRequestExecutor and RequestExecutor. Below, you will find examples of how to use these components to create and send API requests.
First, define a custom request type that conforms to the Request protocol. Include all necessary information for the API endpoint, such as the HTTP method, path, query parameters, and request body.
struct GetUserByIdRequest: Request {
typealias Response = User
let id: String
var method: HTTPMethod {
.get
}
var path: String {
"/users/\(id)"
}
}let baseURL = URL(string: "https://api.example.com")!
let requestBuilder = DefaultURLRequestBuilder(baseURL: baseURL)
let httpClient = URLSessionHTTPClient()
let apiExecutor = APIRequestExecutor(
requestBuilder: requestBuilder,
httpClient: httpClient
)To utilize interceptors like authentication, logging, and retries:
// Token provider and refresher
let tokenProvider = { () -> String? in
return TokenStorage.shared.accessToken
}
let tokenRefresher = {
try await AuthService.refreshToken()
}
// Initialize interceptors
let authorizationInterceptor = AuthorizationInterceptor(
tokenProvider: tokenProvider,
headerField: "Authorization",
tokenFormatter: { "Bearer \($0)" }
)
let tokenRefreshInterceptor = TokenRefreshInterceptor(
tokenProvider: tokenProvider,
tokenRefresher: tokenRefresher,
headerField: "Authorization",
tokenFormatter: { "Bearer \($0)" },
statusCodesToRefresh: [401]
)
let loggingInterceptor = LoggingInterceptor()
let retryInterceptor = RetryInterceptor(maxRetryCount: 3)
let interceptors: [RequestInterceptor] = [
tokenRefreshInterceptor,
authorizationInterceptor,
loggingInterceptor,
retryInterceptor
]
// Create an InterceptableHTTPClient
let httpClient = InterceptableHTTPClient(
httpClient: URLSessionHTTPClient(),
interceptors: interceptors,
maxRetryCount: 3
)
let apiExecutor = APIRequestExecutor(
requestBuilder: requestBuilder,
httpClient: httpClient
)Use the send(_:) function to send your request.
let getUserRequest = GetUserByIdRequest(id: "123")
do {
let user = try await apiExecutor.send(getUserRequest)
print("User: \(user)")
} catch {
print("Error: \(error)")
}If your API requires a different authentication scheme, customize the AuthorizationInterceptor.
let authorizationInterceptor = AuthorizationInterceptor(
tokenProvider: tokenProvider,
headerField: "X-API-Key",
tokenFormatter: { $0 } // Use the token as is
)let authorizationInterceptor = AuthorizationInterceptor(
tokenProvider: tokenProvider,
headerField: nil, // Do not add to headers
addTokenToQuery: true,
queryParameterName: "api_key"
)Handle automatic token refreshing with TokenRefreshInterceptor:
let tokenRefreshInterceptor = TokenRefreshInterceptor(
tokenProvider: tokenProvider,
tokenRefresher: tokenRefresher,
headerField: "Authorization",
tokenFormatter: { "Bearer \($0)" },
statusCodesToRefresh: [401, 403]
)Use LoggingInterceptor to log requests and responses:
let loggingInterceptor = LoggingInterceptor()Implement retries for transient errors:
let retryInterceptor = RetryInterceptor(maxRetryCount: 3, retryDelay: 1.0)Combine multiple interceptors:
let interceptors: [RequestInterceptor] = [
tokenRefreshInterceptor,
authorizationInterceptor,
loggingInterceptor,
retryInterceptor
]
let httpClient = InterceptableHTTPClient(
httpClient: URLSessionHTTPClient(),
interceptors: interceptors,
maxRetryCount: 3
)- Interceptors: Create custom interceptors by conforming to the
RequestInterceptorprotocol. - Error Handling: Provide an
APIErrorHandlerto handle API-specific errors. - Custom HTTPClient: Create a class conforming to
HTTPClientfor specific configurations.
- Unit Tests: NetFlex includes comprehensive unit tests for all components.
- Mocking: Use
MockHTTPClientandMockURLProtocolfor testing. - Testing Interceptors: Write unit tests for interceptors to verify their behavior.
Add NetFlex to your project using Swift Package Manager:
dependencies: [
.package(url: "https://github.com/egzonarifi/NetFlex.git", from: "1.0.1")
]NetFlex is released under the MIT license. See LICENSE for details.