Skip to content

EgzonArifi/NetFlex

Repository files navigation

Lingua

NetFlex

A simple, scalable, and testable networking library for Swift.

Features

  • 🏗 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.

Components

HTTPClient

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

URLSessionHTTPClient is a concrete implementation of HTTPClient that uses URLSession to make network requests. It can be configured with custom URLSession instances.

RequestExecutor

RequestExecutor is a protocol for executing API requests and decoding responses into specified Swift model types.

APIRequestExecutor

APIRequestExecutor is a concrete implementation of RequestExecutor. It uses the provided HTTPClient to fetch data and decodes it into the specified model types.

Interceptors

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.

Usage

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.

1. Create a Request

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

2. Initialize the APIRequestExecutor

Without Interceptors

let baseURL = URL(string: "https://api.example.com")!
let requestBuilder = DefaultURLRequestBuilder(baseURL: baseURL)
let httpClient = URLSessionHTTPClient()
let apiExecutor = APIRequestExecutor(
    requestBuilder: requestBuilder,
    httpClient: httpClient
)

With Interceptors

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
)

3. Send the Request

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

Advanced Usage

Customizing Authentication

If your API requires a different authentication scheme, customize the AuthorizationInterceptor.

Custom Header and Token Format

let authorizationInterceptor = AuthorizationInterceptor(
    tokenProvider: tokenProvider,
    headerField: "X-API-Key",
    tokenFormatter: { $0 } // Use the token as is
)

Adding Token to Query Parameters

let authorizationInterceptor = AuthorizationInterceptor(
    tokenProvider: tokenProvider,
    headerField: nil, // Do not add to headers
    addTokenToQuery: true,
    queryParameterName: "api_key"
)

Token Refreshing

Handle automatic token refreshing with TokenRefreshInterceptor:

let tokenRefreshInterceptor = TokenRefreshInterceptor(
    tokenProvider: tokenProvider,
    tokenRefresher: tokenRefresher,
    headerField: "Authorization",
    tokenFormatter: { "Bearer \($0)" },
    statusCodesToRefresh: [401, 403]
)

Logging

Use LoggingInterceptor to log requests and responses:

let loggingInterceptor = LoggingInterceptor()

Retry Mechanism

Implement retries for transient errors:

let retryInterceptor = RetryInterceptor(maxRetryCount: 3, retryDelay: 1.0)

Combining Interceptors

Combine multiple interceptors:

let interceptors: [RequestInterceptor] = [
    tokenRefreshInterceptor,
    authorizationInterceptor,
    loggingInterceptor,
    retryInterceptor
]

let httpClient = InterceptableHTTPClient(
    httpClient: URLSessionHTTPClient(),
    interceptors: interceptors,
    maxRetryCount: 3
)

Customization

  • Interceptors: Create custom interceptors by conforming to the RequestInterceptor protocol.
  • Error Handling: Provide an APIErrorHandler to handle API-specific errors.
  • Custom HTTPClient: Create a class conforming to HTTPClient for specific configurations.

Testing

  • Unit Tests: NetFlex includes comprehensive unit tests for all components.
  • Mocking: Use MockHTTPClient and MockURLProtocol for testing.
  • Testing Interceptors: Write unit tests for interceptors to verify their behavior.

Installation

Add NetFlex to your project using Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/egzonarifi/NetFlex.git", from: "1.0.0")
]

License

NetFlex is released under the MIT license. See LICENSE for details.