{"id":30719848,"url":"https://github.com/dankinsoid/swift-api-client","last_synced_at":"2025-12-11T22:58:21.501Z","repository":{"id":225815144,"uuid":"759425586","full_name":"dankinsoid/swift-api-client","owner":"dankinsoid","description":"Comprehensive and modular Swift library for API design.","archived":false,"fork":false,"pushed_at":"2025-08-09T11:02:22.000Z","size":1046,"stargazers_count":15,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-09T13:03:13.779Z","etag":null,"topics":["api","api-client","client","http","http-client","networking","swift","swift-api-client","urlrequest","web"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dankinsoid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"dankinsoid","open_collective":"voidilov-daniil","ko_fi":"dankinsoid","custom":["https://paypal.me/voidilovuae","https://www.buymeacoffee.com/dankinsoid"]}},"created_at":"2024-02-18T15:07:22.000Z","updated_at":"2025-08-09T11:02:25.000Z","dependencies_parsed_at":"2024-06-22T21:26:07.861Z","dependency_job_id":"5c7430f6-4074-49f9-be7a-3240c85d316e","html_url":"https://github.com/dankinsoid/swift-api-client","commit_stats":null,"previous_names":["dankinsoid/swift-networking-core"],"tags_count":185,"template":false,"template_full_name":"dankinsoid/iOSLibraryTemplate","purl":"pkg:github/dankinsoid/swift-api-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2Fswift-api-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2Fswift-api-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2Fswift-api-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2Fswift-api-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dankinsoid","download_url":"https://codeload.github.com/dankinsoid/swift-api-client/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2Fswift-api-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273431361,"owners_count":25104491,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-03T02:00:09.631Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-client","client","http","http-client","networking","swift","swift-api-client","urlrequest","web"],"created_at":"2025-09-03T10:42:25.477Z","updated_at":"2025-12-11T22:58:16.445Z","avatar_url":"https://github.com/dankinsoid.png","language":"Swift","readme":"`swift-api-client` is a comprehensive and modular Swift library for API design.\n\n## Table of Contents\n- [Table of Contents](#table-of-contents)\n- [Main Goals of the Library](#main-goals-of-the-library)\n- [Usage](#usage)\n  - [Macros](#macros)\n- [What is `APIClient`](#what-is-apiclient)\n- [Built-in `APIClient` Extensions](#built-in-apiclient-extensions)\n  - [Request building](#request-building)\n  - [Request execution](#request-execution)\n    - [`APIClientCaller`](#apiclientcaller)\n    - [Serializer](#serializer)\n    - [Some execution modifiers](#some-execution-modifiers)\n  - [Encoding and Decoding](#encoding-and-decoding)\n    - [ContentSerializer](#contentserializer)\n  - [Auth](#auth)\n    - [Token refresher](#token-refresher)\n  - [Mocking](#mocking)\n  - [Logging](#logging)\n  - [Metrics](#metrics)\n- [`APIClient.Configs`](#apiclientconfigs)\n  - [Configs Modifications Order](#configs-modifications-order)\n- [Macros](#macros-1)\n- [URL and URLComponents Extensions](#url-and-urlcomponents-extensions)\n- [Introducing `swift-api-client-addons`](#introducing-swift-api-client-addons)\n- [Installation](#installation)\n- [Author](#author)\n- [License](#license)\n- [Contributing](#contributing)\n \n## Main Goals of the Library\n- Minimalistic and intuitive syntax.\n- Reusability, allowing for the injection of configurations across all requests.\n- Extensibility and modularity.\n- A simple core offering a wide range of possibilities.\n- Facilitation of testing, mocking and debugging (logs, metrics).\n\n## Usage\nThe core of the library is the `APIClient` struct, serving both as a request builder and executor. It is a generic struct, enabling use for any task associated with URL request.\n\nThe branching and configuration injection/overriding capabilities of APIClient, extending to all its child instances, facilitate the effortless recycling of networking logic and tasks, thereby eliminating the need for copy-pasting.\n\nWhile a full example is available in the [Example folder](/Example/), here is a simple usage example:\n```swift\nlet client = APIClient(url: baseURL)\n  .bodyDecoder(.json(dateDecodingStrategy: .iso8601))\n  .bodyEncoder(.json(dateEncodingStrategy: .iso8601))\n  .errorDecoder(.decodable(APIError.self))\n  .tokenRefresher { refreshToken, client, _ in\n    guard let refreshToken else { throw APIError.noRefreshToken }\n    let tokens: AuthTokens = try await client(\"auth\", \"token\")\n        .body([\"refresh_token\": refreshToken])\n        .post()\n    return (tokens.accessToken, tokens.refreshToken, tokens.expiresIn)\n  } auth: {\n    .bearer(token: $0)\n  }\n\n// Create a `APIClient` instance for the /users path\nlet usersClient = client(\"users\")\n\n// GET /users?name=John\u0026limit=1\nlet john: User = try await usersClient\n  .query([\"name\": \"John\", \"limit\": 1])\n  .auth(enabled: false)\n  .get()\n\n// Create a `APIClient` instance for /users/{userID} path\nlet johnClient = usersClient(john.id)\n\n// GET /user/{userID}\nlet user: User = try await johnClient.get()\n\n// PUT /user/{userID}\ntry await johnClient.body(updatedUser).put()\n\n// DELETE /user/{userID}\ntry await johnClient.delete()\n```\n\n### Macros\n\nAlso, you can use macros for API declaration:\n```swift\n/// /pet\n@Path\nstruct Pet {\n\n  /// PUT /pet\n  @PUT(\"/\") public func update(_ body: PetModel) -\u003e PetModel {}\n\n  /// POST /pet\n  @POST(\"/\") public func add(_ body: PetModel) -\u003e PetModel {}\n\n  /// GET /pet/findByStatus\n  @GET public func findByStatus(@Query _ status: PetStatus) -\u003e [PetModel] {}\n\n  /// GET /pet/findByTags\n  @GET public func findByTags(@Query _ tags: [String]) -\u003e [PetModel] {}\n}\n```\n\n## What is `APIClient`\n\n`APIClient` is a struct combining a closure for creating a URL request and a typed dictionary of configurations `APIClient.Configs`. There are two primary ways to extend a `APIClient`:\n- `modifyRequest` modifiers.\n- `configs` modifiers.\n\nExecuting an operation on the client involves:\n- `withRequest` methods.\n\nAll built-in extensions utilize these modifiers.\n## Built-in `APIClient` Extensions\nThe full list is available [in docs](https://dankinsoid.github.io/swift-api-client/documentation/swiftapiclient/apiclient).\n### Request building\nNumerous methods exist for modifying a URL request such as `query`, `body`, `header`, `headers`, `method`, `path`, `body` and more.\n```swift\nlet client = APIClient(url: baseURL)\n  .method(.post)\n  .body(someEncodableBody)\n  .query(someEncodableQuery)\n  .header(.acceptEncoding, \"UTF-8\")\n```\nThe full list of modifiers is available in [RequestModifiers.swift](/Sources/SwiftAPIClient/Modifiers/RequestModifiers.swift), all based on the `modifyRequest` modifier.\n\nNotable non-obvious modifiers include:\n- `.callAsFunction(path...)` - as a shorthand for the `.path(path...)` modifier, allowing `client(\"path\")` instead of `client.path(\"path\")`.\n- HTTP method shorthands like `.get`, `.post`, `.put`, `.delete`, `.patch`.\n\n### Request execution\nThe method`call(_ caller: APIClientCaller\u003c...\u003e, as serializer: Serializer\u003c...\u003e)` is provided.\nExamples:\n```swift\ntry await client.call(.http, as: .decodable)\ntry await client.call(.http, as: .void)\ntry client.call(.httpPublisher, as: .decodable)\n```\nThere are also shorthands for built-in callers and serializers:\n- `call()` is equivalent to `call(.http, as: .decodable)` or `call(.http, as: .void)`\n- `callAsFunction()` acts as `call()`, simplifying `client.delete()` to `client.delete.call()` or  `client()` instead of `client.call()`, etc.\n\n#### `APIClientCaller`\nDefines request execution with several built-in callers for various request types, including:\n- `.http` for HTTP requests using `try await` syntax.\n- `.httpPublisher` for HTTP requests with Combine syntax.\n- `.httpDownload` for HTTP download requests using `try await` syntax.\n- `.mock` for mock requests using `try await` syntax.\n\nAll built-in HTTP callers use the `.httpClient` configuration, which can be customized with the `.httpClient()` modifier. The default `.httpClient` is `URLSession`. It's possible to customize the current `.httpClient` instance, for example, to use a custom `URLSession` configuration or [async-http-client](https://github.com/swift-server/async-http-client).\n\nCustom callers can be created for different types of requests, such as WebSocket, GraphQL, etc.\n\n#### Serializer\n`Serializer` is a struct that describes response serialization with several built-in serializers:\n- `.decodable` for decoding a response into a Decodable type.\n- `.data` for obtaining a raw Data response.\n- `.void` for ignoring the response.\n- `.instance` for receiving a response of the same type as `APIClientCaller` returns. For HTTP requests, it is `Data`.\n\nThe `.decodable` serializer uses the `.bodyDecoder` configuration, which can be customized with the `.bodyDecoder` modifier. The default `bodyDecoder` is `JSONDecoder()`.\n\n#### Some execution modifiers\n- `.retry(limit:)` for retrying a request a specified number of times.\n- `.throttle(interval:)` for throttling requests with a specified interval.\n- `.timeout(_:)` for setting an execution timeout.\n- `.waitForConnection()` for waiting for a connection before executing a request.\n- `.backgroundTask()` for executing a request in the background task.\n- `.retryIfFailedInBackground()` for retrying a request if it fails in the background.\n\n### Encoding and Decoding\nThere are several built-in configurations for encoding and decoding:\n- `.bodyEncoder` for encoding a request body. Built-in encoders include `.json`, `.formURL` and `.multipartFormData`.\n- `.bodyDecoder` for decoding a request body. The built-in decoder is `.json`.\n- `.queryEncoder` for encoding a query. The built-in encoder is `.query`.\n- `.errorDecoder` for decoding an error response. The built-in decoder is `.decodable(type)`.\n\nThese encoders and decoders can be customized with corresponding modifiers.\n\n#### ContentSerializer\n`ContentSerializer` is a struct that describes request body serialization, with one built-in content serializer: `.encodable` that utilizes the `.bodyEncoder` configuration.\nCustom content serializers can be specified by passing a `ContentSerializer` instance to the `.body(_:as:)` modifier.\n\n### Auth\n`.auth` and `.isAuthEnabled` configurations can be customized with `.auth(_:)` and `.auth(enabled:)` modifiers,\nallowing the injection of an authentication type for all requests and enabling/disabling it for specific requests.\n\nThe `.auth` configuration is an `AuthModifier` instance with several built-in `AuthModifier` types:\n- `.bearer(token:)` for Bearer token authentication.\n- `.basic(username:password:)` for Basic authentication.\n- `.apiKey(key:field:)` for API Key authentication.\n\n#### Token refresher\nThe `.tokenRefresher(...)` modifier can be used to specify a token refresher closure, which is called when a request returns a 401 status code. The refresher closure receives the cached refresh token, the client, and the response, and returns a new token, which is then used for the request. `.refreshToken` also sets the `.auth` configuration.\n\n### Mocking\nBuilt-in tools for mocking requests include:\n- `.mock(_:)` modifier to specify a mocked response for a request.\n- `Mockable` protocol allows any request returning a `Mockable` response to be mocked even without the `.mock(_:)` modifier.\n- `.usingMocksPolicy` configuration defines whether to use mocks, customizable with `.usingMocks(policy:)` modifier.\nBy default, mocks are ignored in the `live` environment and used as specified for tests and SwiftUI previews.\n\nAdditionally, `.mock(_:)` as a `APIClientCaller` offers an alternative way to mock requests, like `client.call(.mock(data), as: .decodable)`.\n\nCustom HTTPClient instances can also be created and injected for testing or previews.\n\n### Logging\n`swift-api-client` employs [`swift-log`](https://github.com/apple/swift-log) for logging, with `.logger` and `.logLevel` configurations customizable via `logger` and `.log(level:)` modifiers.\nThe default log level is `.info`. A built-in `.none` Logger is available to disable all logs.\n\nLog example:\n\n```\n[29CDD5AE-1A5D-4135-B76E-52A8973985E4] ModuleName/FileName.swift/72\n--\u003e 🌐 PUT /petstore (9-byte body)\nContent-Type: application/json\n--\u003e END PUT\n[29CDD5AE-1A5D-4135-B76E-52A8973985E4]\n\u003c-- ✅ 200 OK (100ms, 15-byte body)\n```\nLog message format can be customized with the `.loggingComponents(_:)` modifier. For example, cURL logs can be enabled with `.loggingComponents(.cURL)`.\n\n### Metrics\n`swift-api-client` employs [`swift-metrics`](https://github.com/apple/swift-metrics) for metrics, with `.reportMetrics` configuration customizable via `.reportMetrics(_:)` modifier.\n\n`swift-api-client` reports:\n- `api_client_requests_total`: total requests count.\n- `api_client_responses_total`: total responses count.\n- `api_client_errors_total`: total errors count.\n- `http_client_request_duration_seconds`: http requests duration.\n\n## `APIClient.Configs`\nA collection of config values is propagated through the modifier chain. These configs are accessible in all core methods: `modifyRequest`, `withRequest`, and `withConfigs`.\n\nTo create custom config values, extend the `APIClient.Configs` structure with a new property.\nUse subscript with your property key path to get and set the value, and provide a dedicated modifier for clients to use when setting this value:\n```swift\nextension APIClient.Configs {\n  var myCustomValue: MyConfig {\n    get {\n      self[\\.myCustomValue] ?? myDefaultConfig\n    }\n    set {\n      self[\\.myCustomValue] = newValue\n    }\n  }\n}\n\nextension APIClient {\n  func myCustomValue(_ myCustomValue: MyConfig) -\u003e APIClient {\n    configs(\\.myCustomValue, myCustomValue)\n  }\n}\n```\n\nThere is `valueFor` global method that allows you to define default values depending on the environment: live, test or preview.\n\n### Configs Modifications Order\nAll configs are collected in the final `withRequest` method and then passed to all modifiers, so the last defined value is used.\nNote that all execution methods, like `call`, are based on the `withRequest` method.\n\nFor instance, the following code will print `3` in all cases:\n```swift\nlet configs = try client\n  .configs(\\.intValue, 1)\n  .modifyRequest { _, configs in\n    print(configs.intValue) // 3\n  }\n  .configs(\\.intValue, 2)\n  .modifyRequest { _, configs in\n    print(configs.intValue) // 3\n  }\n  .configs(\\.intValue, 3)\n  .withRequest { _, configs in\n    print(configs.intValue)  // 3\n    return configs\n  }\nprint(configs.intValue) // 3\n```\n\n## Macros\n`swift-api-client` provides a set of macros for easier API declarations.\n- `API` macro that generates an API client struct.\n- `Path` macro that generates an API client scope for the path.\n- `Cal(_ method:)`, `GET`, `POST`, `PUT`, etc macros for declaring API methods.\nExample:\n```swift\n/// /pet\n@Path\nstruct Pet {\n\n  /// PUT /pet\n  @PUT(\"/\") public func update(_ body: PetModel) -\u003e PetModel {}\n\n  /// POST /pet\n  @POST(\"/\") public func add(_ body: PetModel) -\u003e PetModel {}\n\n  /// GET /pet/findByStatus\n  @GET public func findByStatus(@Query _ status: PetStatus) -\u003e [PetModel] {}\n\n  /// GET /pet/findByTags\n  @GET public func findByTags(@Query _ tags: [String]) -\u003e [PetModel] {}\n\n  /// /pet/{id}\n  @Path(\"{id}\")\n  public struct PetByID {\n\n    /// GET /pet/{id}\n    @GET(\"/\")\n    func get() -\u003e PetModel {}\n\n    /// DELETE /pet/{id}\n    @DELETE(\"/\")\n    func delete() {}\n\n    /// POST /pet/{id}\n    @POST(\"/\") public func update(@Query name: String?, @Query status: PetStatus?) -\u003e PetModel {}\n\n    /// POST /pet/{id}/uploadImage\n    @POST public func uploadImage(_ body: Data, @Query additionalMetadata: String? = nil) {}\n  }\n}\n```\nMacros are not necessary for using `swift-api-client`; they are just syntax sugar.\n\nSure, here is a concise section for your README that demonstrates the most convenient methods provided by your extensions:\n\n## URL and URLComponents Extensions\n\nThese extensions provide convenient methods for configuring URLs and URLComponents, offering a fluent interface for setting path components, query parameters, and other URL components.\n\n```swift\nlet url = URL(string: \"https://example.com\")!\n    .path(\"path1\", \"path2\")\n    .query(\"key1\", 1)\n```\n\nThese extensions simplify and streamline your URL building and modification processes in Swift.\n\n## Introducing `swift-api-client-addons`\n\nTo enhance your experience with `swift-api-client`, I'm excited to introduce [`swift-api-client-addons`](https://github.com/dankinsoid/swift-api-client-addons) \n— a complementary library designed to extend the core functionality of `swift-api-client` with additional features and utilities.\n\n## Installation\n\n1. [Swift Package Manager](https://github.com/apple/swift-package-manager)\n\nCreate a `Package.swift` file.\n```swift\n// swift-tools-version:5.9\nimport PackageDescription\n\nlet package = Package(\n  name: \"SomeProject\",\n  dependencies: [\n    .package(url: \"https://github.com/dankinsoid/swift-api-client.git\", from: \"1.44.0\")\n  ],\n  targets: [\n    .target(\n      name: \"SomeProject\",\n      dependencies: [\n        .product(name:  \"SwiftAPIClient\", package: \"swift-api-client\"),\n      ]\n    )\n  ]\n)\n```\n```ruby\n$ swift build\n```\n\n## Author\n\nDaniil Voidilov, voidilov@gmail.com\n\n## License\n\nswift-api-client is available under the MIT license. See the LICENSE file for more info.\n\n## Contributing\nWe welcome contributions to Swift-Networking! Please read our contributing guidelines to get started.\n","funding_links":["https://github.com/sponsors/dankinsoid","https://opencollective.com/voidilov-daniil","https://ko-fi.com/dankinsoid","https://paypal.me/voidilovuae","https://www.buymeacoffee.com/dankinsoid"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdankinsoid%2Fswift-api-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdankinsoid%2Fswift-api-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdankinsoid%2Fswift-api-client/lists"}