Skip to content

Snapp-Mobile/Fetcher

Repository files navigation

Fetcher Logo

Fetcher

A lightweight, modern, and robust REST API client written in Swift.

Swift Package Index iOS 13.0+ | macOS 11.0+ Latest Release Tests License: MIT

Overview

Fetcher is a Swift library designed to simplify interactions with RESTful APIs. It provides a clean, protocol-oriented, and testable solution for making network requests, handling responses, and managing common API workflows like authentication and error handling.

The core of the framework is the Fetcher struct, which acts as the main client for performing requests. It operates on objects conforming to the APIURL protocol, which provides a structured and type-safe way to define your API endpoints, avoiding scattered URL strings across your codebase.

Key features include:

  • Asynchronous API: Modern async/await syntax for clean and concurrent network calls.
  • Protocol-Driven Endpoints: Define your API endpoints semantically using the APIURL protocol.
  • Robust Error Handling: A comprehensive APIError enum to represent various network and server errors.
  • Token Management: Built-in logic to handle token-based authentication, including automatic retries and token refresh.
  • Environment-Based Configuration: Use the FetcherEnvironment protocol to inject dependencies like URLSession and custom logic, making your networking layer highly testable.
  • Concurrency Utilities: Helpers for running multiple requests in parallel.

Documentation

For more information visit Documentation.

Installation

Add Fetcher as a dependency to your Package.swift file:

.package(url: "https://github.com/Snapp-Mobile/Fetcher.git", from: "0.1.0")

Setup

To use Fetcher, you need to provide an implementation of FetcherEnvironment.

import Fetcher
import Foundation
import os.log

// A basic implementation of FetcherEnvironment for demonstration.
// In a real application, you would manage tokens, network reachability,
// and logging more robustly.
class MyProductionEnvironment: FetcherEnvironment {
    let urlSession: URLSession = .shared
    var isNetworkingAvailable: Bool = true // You might use Network.framework here

    var apiErrorsLogger: FetcherLogger? = nil // Implement a custom logger if needed

    // Placeholder implementations for token management
    func updateToken(to newToken: Fetcher.Token?, logger: FetcherLogger?) async throws -> Fetcher.Token? {
        // ... update stored token ...
        return newToken
    }

    func refreshToken(_ token: Fetcher.Token?, using fetcher: Fetcher) async throws -> Fetcher.Token? {
        // ... logic to refresh token, e.g., make an API call to your auth server ...
        return token // Return a new, valid token
    }

    func getToken(logger: FetcherLogger?) async throws -> Fetcher.Token? {
        // ... retrieve current token from storage ...
        return nil
    }

    func logout() async throws {
        // ... clear stored token and user session ...
    }
}

let myEnvironment = MyProductionEnvironment()
let fetcher = Fetcher(environment: myEnvironment)

Usage

Define your API endpoints by conforming to the APIURL protocol:

import Fetcher
import Foundation

// Example API endpoint definition
enum MyAPIEndpoint: APIURL {
    case getGreeting

    var path: String {
        switch self {
        case .getGreeting:
            return "/greeting"
        }
    }

    var requestMethod: String {
        switch self {
        case .getGreeting:
            return "GET"
        }
    }

    var url: URL? {
        return URL(string: "https://api.example.com")?.appendingPathComponent(path)
    }

    func bodyParams(token: Fetcher.Token?) -> [String: Any]? {
        return nil
    }

    func request(token: Fetcher.Token?) -> URLRequest? {
        guard let url = self.url else { return nil }
        var request = URLRequest(url: url)
        request.httpMethod = requestMethod
        // Add authorization header if token is available
        if let accessToken = token?.accessToken {
            request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
        }
        return request
    }
}

// Define a Decodable struct for your API response
struct GreetingResponse: Decodable {
    let message: String
}

// To fetch data from your API:
func fetchGreeting() async {
    do {
        let greeting: GreetingResponse = try await fetcher.fetch(MyAPIEndpoint.getGreeting)
        print("Received greeting: \(greeting.message)")
    } catch {
        print("Failed to fetch greeting: \(error.localizedDescription)")
    }
}

// Call the function (e.g., from an async context)
// Task {
//     await fetchGreeting()
// }

About

A lightweight REST API client written in Swift

Topics

Resources

License

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •  

Languages