Skip to content

About

A tiny, type-safe, dependency-free HTTP request builder for Swift. Builds clean under Swift 6 strict concurrency.

Topics

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Latest commit

 

History

19 Commits

Folders and files

Repository files navigation

NetworkRequest

A tiny, type-safe, dependency-free HTTP request builder for Swift.

Swift Platforms SPM License CI

NetworkRequest describes an HTTP request and how to parse its response as a single, immutable value. It does not execute itself — URLSession, async/await, Combine, or any mock you like is in charge of dispatch. The library just gives you a strongly-typed, composable description of the work.

Quick start

import NetworkRequest

struct User: Decodable, Sendable {
    let id: Int
    let name: String
}

struct APIError: Decodable, Error, Sendable {
    let message: String
}

let me = NetworkRequest<User, APIError>(
    url: URL(string: "https://api.example.com/me"),
    additionalHeaderFields: ["Authorization": "Bearer \(token)"]
)

let (data, response) = try await URLSession.shared.data(for: me.urlRequest())
let user = try me.parse(data, response)

A non-2xx response decodes the body as APIError and throws it — your do/catch block sees a typed error.

Why NetworkRequest?

  • Type-safe. A NetworkRequest<User, APIError> carries both the success and the failure shape in its type.
  • Execution-agnostic. No baked-in transport. Use URLSession, mock it, swap it for Combine, plug in your own.
  • Sendable-clean. Builds cleanly under Swift 6 strict concurrency and conditionally conforms to Sendable when its generic parameters do.
  • Zero dependencies. Just Foundation.
  • Built-in cURL debugging. request.cURLCommand reproduces a request on the command line.

Installation

Add the package to your Package.swift:

dependencies: [
    .package(url: "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/MarvinNazari/NetworkRequest", from: "1.0.0"),
],
targets: [
    .target(name: "MyApp", dependencies: ["NetworkRequest"]),
]

In Xcode: File ▸ Add Package Dependencies… and paste the URL.

Testing

The package ships a second product, NetworkRequestTesting, so you can unit test request building and response parsing without touching the network. Add it to your test target only:

.testTarget(
    name: "MyAppTests",
    dependencies: ["MyApp", .product(name: "NetworkRequestTesting", package: "NetworkRequest")]
)

It adds exactly three types — StubURLProtocol, RequestRecorder and RecordedRequest — and no extensions on NetworkRequest, URLRequest, URLSession or any other type it does not own.

StubURLProtocol builds an isolated URLSession that answers every request through your handler. Sending a request is the same two lines as in production code, because the core already exposes both closures:

import Testing
import NetworkRequest
import NetworkRequestTesting

@Test func fetchesTheCurrentUser() async throws {
    let recorder = RequestRecorder()
    let session = StubURLProtocol.session { request in
        await recorder.record(request)
        return .response(.json(#"{"id":1,"name":"Ada"}"#))
    }

    let (data, response) = try await session.data(for: try me.urlRequest())
    let user = try me.parse(data, response)

    #expect(user.name == "Ada")
    let sent = try #require(await recorder.last)
    #expect(sent.path == "/me")
    #expect(sent.queryDictionary == ["expand": "profile"])
    #expect(sent.headers["Authorization"] == "Bearer \(token)")
}

Also included:

  • StubURLProtocol.session(returning:) — same response every time.
  • StubURLProtocol.session(sequence:) — scripted responses in order; an extra request fails with a descriptive SequenceExhausted error.
  • StubURLProtocol.session(recording:returning:) — record into a RequestRecorder and always reply the same way.
  • .failure(URLError(...)) outcomes to simulate transport errors.
  • RecordedRequest, a value wrapper around the request that was sent, with urlRequest, url, method, path, headers, queryItems, queryDictionary, body, bodyString, jsonBody(), jsonArrayBody() and formBody. The JSON accessors throw, so a malformed body fails the test instead of reading as nil. Wrap a request you built yourself with RecordedRequest(try request.urlRequest()) to assert on it.

Every stub session has its own handler, so suites run safely in parallel.

Documentation

Full API reference and articles (Getting Started, A Real-World Example, Recipes, Building Requests, Parsing Responses, Request Bodies) are published as Documentation.

The same documentation is bundled as a DocC catalog inside the package; in Xcode, choose Product ▸ Build Documentation to browse it locally.

Requirements

  • Swift 6.2 toolchain (Xcode 17+)
  • iOS 13+, macOS 10.15+, tvOS 13+, watchOS 6+

License

MIT — see LICENSE.

About

A tiny, type-safe, dependency-free HTTP request builder for Swift. Builds clean under Swift 6 strict concurrency.

Topics

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages