https://github.com/sbertix/composablerequest
A Swift library to abstract API clients.
https://github.com/sbertix/composablerequest
swift url urlrequest urlsession
Last synced: 3 months ago
JSON representation
A Swift library to abstract API clients.
- Host: GitHub
- URL: https://github.com/sbertix/composablerequest
- Owner: sbertix
- License: apache-2.0
- Created: 2020-03-16T18:16:22.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2024-10-17T22:02:46.000Z (12 months ago)
- Last Synced: 2025-07-03T19:44:31.301Z (3 months ago)
- Topics: swift, url, urlrequest, urlsession
- Language: Swift
- Homepage:
- Size: 2.01 MB
- Stars: 10
- Watchers: 1
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: docs/README.md
- Contributing: docs/CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: docs/CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
![]()
[](https://swift.org)




**ComposableRequest** is a networking layer based on a declarative interface, written in (modern) **Swift**.
It abstracts away `URLSession` implementation, in order to provide concise and powerful endpoint representations (both for their requests and responses), supporting, out-of-the-box, **Combine** `Publisher`s and _structured concurrency_ (`async`/`await`) with a single definition.
It comes with `Storage` (inside of **Storage**), a way of caching `Storable` items, and related concrete implementations (e.g. `UserDefaultsStorage`, `KeychainStorage` – for which you're gonna need to add **StorageCrypto**, depending on [**KeychainAccess**](https://github.com/kishikawakatsumi/KeychainAccess), together with the ability to provide the final user of your API wrapper to inject code through `Provider`s.
## Status

You can find all changelogs directly under every [release](https://github.com/sbertix/ComposableRequesst/releases).
> What's next?
**ComposableRequest** was initially [**Swiftagram**](https://github.com/sbertix/Swiftagram)'s networking layer and it still tends to follow roughly the same development cycle.
[Milestones](https://github.com/sbertix/ComposableRequest/milestones), [issues](https://github.com/sbertix/ComposableRequest/issues), are the best way to keep updated with active developement.
Feel free to contribute by sending a [pull request](https://github.com/sbertix/ComposableRequest/pulls).
Just remember to refer to our [guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md) beforehand.## Installation
### Swift Package Manager (Xcode 11 and above)
1. Select `File`/`Swift Packages`/`Add Package Dependency…` from the menu.
1. Paste `https://github.com/sbertix/ComposableRequest.git`.
1. Follow the steps.
1. Add **Storage** together with **Requests** for the full experience.> Why not CocoaPods, or Carthage, or ~blank~?
Supporting multiple _dependency managers_ makes maintaining a library exponentially more complicated and time consuming.\
Furthermore, with the integration of the **Swift Package Manager** in **Xcode 11** and greater, we expect the need for alternative solutions to fade quickly.Targets
- **Requests**, an HTTP client originally integrated in **Swiftagram**, the core library.
- **Storage**
- **StorageCrypto**, depending on [**KeychainAccess**](https://github.com/kishikawakatsumi/KeychainAccess), can be imported together with **Storage** to extend its functionality.
## Usage
Check out [**Swiftagram**](https://github.com/sbertix/Swiftagram) or visit the (_auto-generated_) documentation for [**Requests**](https://sbertix.github.io/ComposableRequest/Requests/), [**Storage**](https://sbertix.github.io/ComposableRequest/Storage/) and [**StorageCrypto**](https://sbertix.github.io/ComposableRequest/StorageCrypto/) to learn about use cases.
### Endpoint
As an implementation example, we can display some code related to the Instagram endpoint tasked with deleting a post.
```swift
public extension Request {
/// An enum listing an error.
enum DeleteError: Swift.Error { case invalid }
/// Delete one of your own posts, matching `identifier`.
/// Checkout https://github.com/sbertix/Swiftagram for more info.
///
/// - parameter identifier: A valid `String`.
/// - returns: A locked `AnySingleEndpoint`, waiting for authentication `HTTPCookie`s.
func delete(_ identifier: String) -> Providers.Lock<[HTTPCookie], AnySingleEndpoint> {
// Wait for user defined values.
.init { cookies in
// Fetch first info about the post to learn if it's a video or picture
// as they have slightly different endpoints for deletion.
Single {
Path("https://i.instagram.com/api/v1/media/\(identifier)/info")
// Wait for the user to `inject` an array of `HTTPCookie`s.
// You should implement your own `model` to abstract away
// authentication cookies, but as this is just an example
// we leave it to you.
Headers(HTTPCookie.requestHeaderFields(with: cookies))
// Decode it inside an `AnyDecodable`, allowing to interrogate JSON
// representations of object without knowing them in advance.
Response {
let output = try JSONDecoder().decode(AnyDecodable.self, from: $0)
guard let type = output.items[0].mediaType.int,
[1,2, 8].contains(type) else {
throw DeleteError.invalid
}
return type
}
}.switch {
Path("https://i.instagram.com/api/v1/media/\(identifier)/delete")
Query($0 == 2 ? "VIDEO" : "PHOTO", forKey: "media_type")
// This will be applied exactly as before, but you can add whaterver
// you need to it, as it will only affect this `Request`.
Headers(HTTPCookie.requestHeaderFields(with: cookies))
Response {
let output = try JSONDecoder().decode(AnyDecodable.self, from: $0)
return $0.status.bool ?? false
}
}
}
}
}
```
> How can the user then retreieve the information?
All the user has to do is…
```swift
/// A valid post identifier.
let identifier: String = /* a valid String */
/// A valid array of cookies.
let cookies: [HTTPCookie] = /* an array of HTTPCookies */
/// A *retained* collection of `AnyCancellable`s.
var bin: Set = []/// Delete it using **Combine**.
Request.delete(identifier)
.unlock(with: cookies)
.resolve(with: .shared) // The shared `URLSession`.
.sink { _ in } receiveValue: { print($0) }
.store(in: &bin)[…]
/// Delete it using _async/await_.
let result = try await Request.delete(identifier)
.unlock(with: cookies)
.resolve(with: .shared)
```### Resume and cancel requests
> What about cancelling the request, or starting it a later date?
Concrete implementation of `Receivable` might implement suspension and cancellation through their underlying types (like `URLSessionDataTask` or `Cancellable`).
### Caching
Caching of `Storable`s is provided through conformance to the `Storage` protocol, specifically by implementing either `ThrowingStorage` or `NonThrowingStorage`.The library comes with several concrete implementations.
- `TransientStorage` should be used when no caching is necessary, and it's what `Authenticator`s default to when no `Storage` is provided.
- `UserDefaultsStorage` allows for faster, out-of-the-box, testing, although it's not recommended for production as private cookies are not encrypted.
- `KeychainStorage`, requiring you to add **ComposableStorageCrypto**, (**preferred**) stores them safely in the user's keychain.
-->