Skip to content

Commit aa4b9ba

Browse files
authored
Update README.md
Signed-off-by: Joe Maghzal <[email protected]>
1 parent f3b4cbf commit aa4b9ba

File tree

1 file changed

+202
-85
lines changed

1 file changed

+202
-85
lines changed

README.md

Lines changed: 202 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
[![GitHub Release](https://img.shields.io/github/release/SwiftyJoeyy/swift-networking.svg?include_prereleases)](https://github.com/SwiftyJoeyy/swift-networking/releases)
12
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSwiftyJoeyy%2Fswift-networking%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/SwiftyJoeyy/swift-networking)
23
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSwiftyJoeyy%2Fswift-networking%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/SwiftyJoeyy/swift-networking)
4+
[![License](https://img.shields.io/github/license/SwiftyJoeyy/swift-networking)](https://github.com/SwiftyJoeyy/swift-networking/blob/main/LICENSE)
35
[![Swift](https://github.com/SwiftyJoeyy/swift-networking/actions/workflows/swift.yml/badge.svg)](https://github.com/SwiftyJoeyy/swift-networking/actions/workflows/swift.yml)
46

57
# Swift Networking
@@ -20,40 +22,178 @@ Inspired by **Swift** & **SwiftUI**’s design philosophy, it allows you to defi
2022

2123

2224

23-
## 📦 Installation
25+
## 🚀 Getting Started
2426

25-
Add via **Swift Package Manager**:
27+
This guide walks you through the core building blocks:
28+
1. Defining a request.
29+
2. Adding headers, parameters, and body.
30+
3. Building a client.
31+
4. Sending a request.
32+
33+
34+
### 1. Defining a request
35+
To define a request, use the [`@Request`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/request(_:)) macro. This adds conformance to the [`Request`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/request) protocol, which requires a `request` property similar to SwiftUI’s body.
36+
Inside that property, you start with an [`HTTPRequest`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/httprequest), which represents the core of the request.
37+
38+
You can:
39+
40+
- Provide a url to override the client’s base URL
41+
- Add an optional path
42+
- Set the HTTP method using [`method(_:)`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/request/method(_:))
43+
- Append extra path components using [`appending(_:)`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/request/appending(_:))
44+
Example
2645

2746
```swift
28-
.package(url: "https://github.com/SwiftyJoeyy/swift-networking.git", from: "1.0.0")
47+
@Request struct TestingRequest {
48+
var request: some Request {
49+
HTTPRequest(url: "https://www.example.com", path: "gallery")
50+
.method(.get)
51+
.appending("cats", "images")
52+
}
53+
}
2954
```
55+
This creates a simple GET request to `https://www.google.com/test/cats/images`.
3056

31-
Then add `"Networking"` to your target dependencies.
57+
You can also compose and override requests:
58+
59+
```swift
60+
@Request
61+
struct TestRequest {
62+
@Header var test: String {
63+
return "Computed"
64+
}
65+
var request: some Request {
66+
TestingRequest() // TestingRequest instead of HTTPRequest
67+
.timeout(90)
68+
.method(.post)
69+
}
70+
}
71+
```
3272

3373

74+
### 2. Adding Headers, Parameters, and Body
3475

35-
## 🚀 Example
76+
`Networking` offers multiple ways to add headers, query parameters, and request bodies. You can use macros for top level values or chaining modifiers for inline customization.
3677

37-
Here's what a full request flow looks like — from client configuration to building a reusable, composable request:
78+
#### 🧩 Adding Parameters
79+
80+
Use [`Parameter`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/parameter) to create parameters from `String`, `Int`, `Double`, or `Bool` values, including arrays of those types.
81+
You can also use [`ParametersGroup`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/parametersgroup) to group multiple parameters into one modifier.
82+
83+
To add parameters to a request, you use the [`@Parameter`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/parameter(_:)) macro, or dynamically through modifier methods:
3884

3985
```swift
40-
let client = MyClient()
86+
@Request struct SearchRequest {
87+
@Parameter var query: String // Using the name of the property
88+
@Parameter("search_query") var query: String // Using a custom name.
89+
let includeFilter: Bool
90+
var request: some Request {
91+
HTTPRequest(path: "search")
92+
.appendingParameters {
93+
Parameter("query", value: "cats")
94+
if includeFilter {
95+
Parameter("filter", value: "popular")
96+
}
97+
}.appendingParameter("sorting", value: "ascending")
98+
.appendingParameter("filters", values: ["new", "free"])
99+
}
100+
}
101+
```
41102

42-
func fetch() async throws {
43-
let data = try await client.dataTask(TestingRequest())
44-
.decode(with: JSONDecoder())
45-
.retryPolicy(.doNotRetry)
46-
.decode(as: String.self)
47-
48-
print(data)
103+
#### 📬 Adding Headers
104+
105+
Use [`Header`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/header) to define headers from `String`, `Int`, `Double`, or `Bool` values.
106+
You can also use [`HeadersGroup`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/headersgroup) if you want to group multiple headers in a single modifier, or inject a raw dictionary `[String: String]`.
107+
You also get convenience types like: [`AcceptLanguage`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/acceptlanguage), [`ContentDisposition`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/contentdisposition) & [`ContentType`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/contenttype).
108+
Similar to parameters, headers can also be declared statically using the [`@Header`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/header(_:)) macro or applied dynamically using modifiers:
109+
110+
```swift
111+
@Request struct AuthenticatedRequest {
112+
@Header var token: String // Using the name of the property.
113+
@Header("Authorization") var token: String // Using a custom name.
114+
let includeLang: Bool
115+
var request: some Request {
116+
HTTPRequest(path: "me")
117+
.additionalHeaders {
118+
Header("Custom-Header", value: "value")
119+
if includeLang {
120+
AcceptLanguage("en")
121+
}
122+
}.additionalHeader("version", value: "1.0")
123+
}
124+
}
125+
```
126+
127+
#### 📦 Adding a Request Body
128+
129+
Currently, `Networking` supports [`JSON`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/json) and [`FormData`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/formdata) request bodies.
130+
To apply a body to a request, you can use the [`body(_:)`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/request/body(_:)) modifier, or convenience modifiers for `json`:
131+
132+
##### ✅ Using .body(...) Modifier
133+
134+
```swift
135+
HTTPRequest(path: "upload")
136+
.body {
137+
// JSON
138+
JSON(["dict": "data"]) // Dictionary
139+
JSON(Data()) // Raw data.
140+
JSON(EncodableUser()) // Encodable types.
141+
142+
// FormData
143+
FormData {
144+
FormDataBody( /// Raw data
145+
"Image",
146+
data: Data(),
147+
fileName: "image.png",
148+
mimeType: .png
149+
)
150+
FormDataFile( // Data from a file.
151+
"File",
152+
fileURL: URL(filePath: "filePath"),
153+
fileName: "file",
154+
mimeType: .fileURL
155+
)
156+
}
157+
}
158+
```
159+
160+
##### ✅ Using JSON Convenience Modifiers
161+
```swift
162+
HTTPRequest(path: "create")
163+
.json(["name": "John", "age": 30]) // Dictionary.
164+
.json(Data()) // Raw data.
165+
.json(EncodableUser()) // Encodable types.
166+
```
167+
> [!CAUTION]
168+
> If multiple `.body()` or `.json()` modifiers are used, the last one overrides the previous. This applies to all modifiers, modifier order matters.
169+
170+
#### 🧱 Inline Modifiers with HTTPRequest
171+
172+
You can also include modifiers directly in the `HTTPRequest` initializer. This gives you a clean, SwiftUI style declaration for most common request scenarios.
173+
174+
```swift
175+
HTTPRequest(path: "submit") {
176+
Header("X-Token", value: "123")
177+
Parameter("query", value: "swift")
178+
JSON(["key": "value"])
49179
}
50180
```
51181

52-
### 🧩 Declarative Client Configuration
182+
183+
### 3. Building a client
184+
185+
To define a networking client, use the [`@Client`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/client()) macro. This macro adds conformance to the [`NetworkClient`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/networkclient) protocol, which requires a `session` property. This property returns a [`Session`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/session) instance that describes how your requests should be configured and executed.
186+
187+
> [!IMPORTANT]
188+
> Do not call the session property directly. It’s a computed property that creates a new session each time it’s accessed.
189+
> Always send requests using the client instance itself (e.g. try await client.dataTask(...)).
190+
> You should also hold on to the client instance created either using a singleton or by dependency injection to avoid creating multiple instances.
191+
192+
Inside the session property, you can create and customize a `Session` using a closure that returns a configured `URLSessionConfiguration`.
193+
From there, you can apply additional behaviors such as Base URL, Logging, Retry policy, Authorization, Response validation, Encoding/decoding strategies.
53194

54195
```swift
55-
@Client
56-
struct MyClient {
196+
@Client struct MyClient {
57197
var session: Session {
58198
Session {
59199
URLSessionConfiguration.default
@@ -66,34 +206,32 @@ struct MyClient {
66206
.headers {
67207
Header("Key", value: "Value")
68208
}
69-
}.onRequest { request, task, session in
70-
// handle request creation
71-
return request
72-
}.enableLogs(true)
73-
.validate(for: [.accepted, .ok])
74-
.retry(limit: 2, for: [.conflict, .badRequest])
75-
.baseURL(URL(string: "example.com"))
76-
.encode(with: JSONEncoder())
209+
}
210+
.authorization(BearerAuthProvider())
211+
.baseURL("https://example.com")
77212
.decode(with: JSONDecoder())
213+
.encode(with: JSONEncoder())
214+
.retry(limit: 2, delay: 2)
215+
.enableLogs(true)
216+
.validate(for: [.badGateway, .created])
78217
}
79218
}
80219
```
81220

82-
### ⚙️ Custom Configuration Values
221+
#### ⚙️ Custom Configuration Values
83222

84-
You can define custom configuration keys using the `ConfigurationValues` extension:
223+
You can define custom configuration keys using the [`@Config`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/config(forceunwrapped:)) macro & extending [`ConfigurationValues`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/configurationvalues):
85224

86225
```swift
87226
extension ConfigurationValues {
88227
@Config var customConfig = CustomValue()
89228
}
90229
```
91230

92-
Then, you can set it on a task or on the session using:
231+
Then, you can set it on a task or on the session using the modifier [`configuration(_:_:)`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/configurable/configuration(_:_:)):
93232

94233
```swift
95-
@Client
96-
struct MyClient {
234+
@Client struct MyClient {
97235
var session: Session {
98236
Session()
99237
.configuration(\.customConfig, CustomValue())
@@ -107,74 +245,53 @@ let data = try await client.dataTask(TestingRequest())
107245

108246
This allows you to define project-specific config values and inject them anywhere in your request or session pipeline.
109247

110-
### 🧾 Declarative Request Definitions
111248

112-
```swift
113-
@Request("test-request-id")
114-
struct TestingRequest {
115-
@Header("customKey") var test = "test"
116-
@Parameter var testing = 1
249+
### 4. Sending a request
117250

118-
var request: some Request {
119-
HTTPRequest(url: "https://www.google.com") {
120-
Header("test", value: "value")
121-
Parameter("some", values: ["1", "2"])
122-
JSON("value")
123-
}.body {
124-
FormData {
125-
FormDataBody(
126-
"Image",
127-
data: Data(),
128-
fileName: "image.png",
129-
mimeType: .png
130-
)
131-
FormDataFile(
132-
"File",
133-
fileURL: URL(filePath: "filePath"),
134-
fileName: "file",
135-
mimeType: .fileURL
136-
)
137-
}
138-
}.method(.get)
139-
.timeout(90)
140-
.cachePolicy(.reloadIgnoringLocalCacheData)
141-
.appending(path: "v1")
142-
.additionalHeaders {
143-
Header("Header", value: "10")
144-
AcceptLanguage("en")
145-
}.additionalParameters {
146-
Parameter("Item", value: "value")
147-
}
148-
}
149-
}
251+
To send a request, you start by creating a task from a client. The framework provides two main types of tasks:
252+
- [`dataTask`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/session/datatask(_:)) — for requests that return data.
253+
- [`downloadTask`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/session/downloadtask(_:)) — for file downloads.
254+
255+
Each task can be configured individually using the same modifiers available on `Session` (e.g. retry, decoding, etc.), giving you full control per request.
256+
257+
```swift
258+
let task = MyClient()
259+
.dataTask(MyRequest())
260+
.retry(limit: 2)
261+
.validate(for: [.ok, .notModified])
150262
```
151263

152-
### 🔁 Composing and Overriding Requests
264+
To start a task you either call [`resume()`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/networkingtask/resume()) manually, or access the response directly (recommended) using [`response()`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/networktask/response()) or decode it to a specific type using [`decode(as:)`](https://swiftpackageindex.com/swiftyjoeyy/swift-networking/main/documentation/networking/datatask/decode(as:))
153265

154266
```swift
155-
@Request
156-
struct TestRequest {
157-
@Header var test: String {
158-
return "Computed"
159-
}
160-
var timeout: TimeInterval = 90
267+
let task = MyClient().dataTask(MyRequest())
161268

162-
var request: some Request {
163-
TestingRequest()
164-
.timeout(timeout)
165-
.method(.post)
166-
.additionalHeaders {
167-
Header("Additional", value: "value")
168-
}.appending(paths: "v3")
169-
}
170-
}
269+
task.resume()
270+
let result = try await task.response()
271+
let user = try await task.decode(as: User.self)
171272
```
172273

274+
> [!NOTE]
275+
> A task will only send the request once.
276+
> If `response()` or `decode(as:)` is called multiple times, the framework will await the result of the first call instead of resending the request.
277+
278+
279+
280+
## 📦 Installation
281+
282+
Add via **Swift Package Manager**:
283+
284+
```swift
285+
.package(url: "https://github.com/SwiftyJoeyy/swift-networking.git", from: "1.0.0")
286+
```
287+
288+
Then add `"Networking"` to your target dependencies.
289+
173290

174291

175292
## 📖 Documentation
176293

177-
WIP: Full documentation and guides will be available soon.
294+
The [documentation](https://swiftpackageindex.com/SwiftyJoeyy/swift-networking/main/documentation/networking) is provided by [swiftpackageindex](https://swiftpackageindex.com).
178295

179296

180297

0 commit comments

Comments
 (0)