Thin layer for standard network handling, with async/await as a first class citizen.
- ✅ Vanilla async/await wrappers for CRUD network operations (
create/read/update/delete) - ✅ async/await download and upload APIs
- ✅ Type-safe
Decodablerequests out of the box - ✅ Cooperative cancellation: cancelling the surrounding
Taskcancels the network request - ✅ Swift 6 language mode with audited
Sendableconformances - ✅ 100%
URLSessionunder the hood. Zero third-party dependencies - ✅ Fully unit tested
- Swift 6.0+ (Xcode 16+)
- iOS 17+ / macOS 14+ / tvOS 17+ / watchOS 10+
In Xcode: File ▸ Add Package Dependencies... and paste
https://github.com/GeekingwithMauri/MauriNet
Or declare it directly in your Package.swift:
dependencies: [
.package(url: "https://github.com/GeekingwithMauri/MauriNet", branch: "main")
],
targets: [
.target(name: "YourTarget", dependencies: ["MauriNet"])
]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:
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:
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)
}
}Skip the manual decoding dance. Declare your model as the return type and let the manager handle the rest:
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:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let posts: [Post] = try await manager.request(postsRequest, decoder: decoder)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 convenienceThe destination can also be made explicit by providing an existing directory:
let downloadsFolder = URL.documentsDirectory.appending(path: "Downloads")
let localFile = try await manager.download(resourceRequest, into: downloadsFolder)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)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:
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())All public signatures are fully documented. Reference documentation was generated with Jazzy as explained in this article and can be found here