Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// swift-tools-version:5.3
// swift-tools-version:6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "MauriNet",
platforms: [
.iOS(.v13),
.macOS(SupportedPlatform.MacOSVersion.v10_15)
.iOS(.v17),
.macOS(.v14),
.tvOS(.v17),
.watchOS(.v10)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
Expand Down
154 changes: 150 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,157 @@

# [MauriNet](https://github.com/GeekingwithMauri/MauriNet)

Thin layer for standard network handling
Thin layer for standard network handling, with **async/await as a first class citizen**.

- Wrapper around Foundation's `URLSession` and all of its settings
- WIP: Combine wrapper for chained requests
- ✅ Vanilla async/await wrappers for CRUD network operations (`create` / `read` / `update` / `delete`)
- ✅ async/await download and upload APIs
- ✅ Type-safe `Decodable` requests out of the box
- ✅ Cooperative cancellation: cancelling the surrounding `Task` cancels the network request
- ✅ Swift 6 language mode with audited `Sendable` conformances
- ✅ 100% `URLSession` under the hood. Zero third-party dependencies
- ✅ Fully unit tested

## Requirements

- Swift 6.0+ (Xcode 16+)
- iOS 17+ / macOS 14+ / tvOS 17+ / watchOS 10+

## Installation

### Swift Package Manager

In Xcode: _File ▸ Add Package Dependencies..._ and paste

```
https://github.com/GeekingwithMauri/MauriNet
```

Or declare it directly in your `Package.swift`:

```swift
dependencies: [
.package(url: "https://github.com/GeekingwithMauri/MauriNet", branch: "main")
],
targets: [
.target(name: "YourTarget", dependencies: ["MauriNet"])
]
```

## Usage

### CRUD operations with async/await

`AsyncRequestManager` is the modern entry point of the library. Its CRUD conveniences take any `URLRequest` and stamp the appropriate HTTP verb on it for you:

```swift
import MauriNet

let manager = AsyncRequestManager()
let postsRequest = URLRequest(url: URL(validURL: "https://example.com/api/posts"))

// Create (POST)
let creationReply = try await manager.create(postsRequest, payload: encodedNewPost)

// Read (GET)
let retrievedData = try await manager.read(postsRequest)

// Update (PUT by default, PATCH on demand)
let updateReply = try await manager.update(postsRequest, payload: encodedAmendedPost)
let patchReply = try await manager.update(postsRequest, payload: encodedDelta, method: .patch)

// Delete (DELETE)
try await manager.delete(postsRequest)
```

Any failure throws a strongly typed `NetworkError`, mapped from the reply's HTTP status code:

```swift
do {
let retrievedData = try await manager.read(postsRequest)
} catch let error as NetworkError {
switch error {
case .notFound:
// 404: handle the missing resource
case .unauthorized:
// 401: refresh credentials
default:
print(error.localizedDescription)
}
}
```

### Decodable requests

Skip the manual decoding dance. Declare your model as the return type and let the manager handle the rest:

```swift
struct Post: Decodable {
let id: Int
let title: String
}

let posts: [Post] = try await manager.request(postsRequest)
```

A custom-configured `JSONDecoder` can be passed as a second argument when needed:

```swift
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

let posts: [Post] = try await manager.request(postsRequest, decoder: decoder)
```

### Downloads

```swift
let resourceRequest = URLRequest(url: URL(validURL: "https://example.com/resource.zip"))
let localFile = try await manager.download(resourceRequest)
// localFile points to the app's temporary directory: move it to a
// permanent location (or consume it) at your earliest convenience
```

The destination can also be made explicit by providing an existing directory:

```swift
let downloadsFolder = URL.documentsDirectory.appending(path: "Downloads")
let localFile = try await manager.download(resourceRequest, into: downloadsFolder)
```

### Uploads

```swift
var uploadRequest = URLRequest(url: URL(validURL: "https://example.com/avatar"))
uploadRequest.httpMethod = HTTPMethod.post.value

// the data to be uploaded
let avatarData = try Data(contentsOf: avatarFileURL)

let serverReply = try await manager.upload(uploadRequest, from: avatarData)
```

### Testability

`AsyncRequestManager` is backed by the `URLSessionable` protocol (which `URLSession` already conforms to). Inject your own conforming double to unit test your networking layer without hitting the actual network:

```swift
struct StubbedSession: URLSessionable {
func retrieveData(for request: URLRequest) async throws -> (Data, URLResponse) {
// return canned data + response
}

func downloadFile(for request: URLRequest) async throws -> (URL, URLResponse) {
// return canned file location + response
}

func upload(_ payload: Data, for request: URLRequest) async throws -> (Data, URLResponse) {
// return canned server reply + response
}
}

let testableManager = AsyncRequestManager(session: StubbedSession())
```

# Documentation 🔎📜

All the documentation was generated with [Jazzy](https://github.com/realm/jazzy) as explained in [this article](https://geekingwithmauri.com/swift/documentation-in-practice.html) and can be found [here](https://geekingwithmauri.github.io/MauriNet/)
All public signatures are fully documented. Reference documentation was generated with [Jazzy](https://github.com/realm/jazzy) as explained in [this article](https://geekingwithmauri.com/swift/documentation-in-practice.html) and can be found [here](https://geekingwithmauri.github.io/MauriNet/)
65 changes: 0 additions & 65 deletions Sources/MauriNet/Combine/Agent.swift

This file was deleted.

Loading
Loading