Skip to content

Commit 1e78ff6

Browse files
committed
Initial commit.
0 parents  commit 1e78ff6

32 files changed

+1273
-0
lines changed

.github/workflows/documentation.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Documentation
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
14+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
15+
concurrency:
16+
group: "pages"
17+
cancel-in-progress: false
18+
19+
jobs:
20+
publish:
21+
name: Publish Documentation
22+
# TODO: Use `macos-latest` after the macOS 13 image graduates to GA.
23+
# https://github.com/actions/runner-images/issues/7508#issuecomment-1718206371
24+
runs-on: macos-13
25+
26+
environment:
27+
name: github-pages
28+
url: ${{ steps.deployment.outputs.page_url }}
29+
30+
steps:
31+
- name: Set up GitHub Pages
32+
uses: actions/configure-pages@v3
33+
- uses: maxim-lobanov/setup-xcode@v1
34+
with:
35+
xcode-version: latest-stable
36+
- name: Print Swift compiler version
37+
run: "swift --version"
38+
- uses: actions/checkout@v3
39+
- name: Generate documentation
40+
run: "swift package generate-documentation --target HTTPErrorHandling --disable-indexing --include-extended-types --transform-for-static-hosting --hosting-base-path swift-http-error-handling"
41+
- name: Upload documentation
42+
uses: actions/upload-pages-artifact@v2
43+
with:
44+
path: ".build/plugins/Swift-DocC/outputs/HTTPErrorHandling.doccarchive"
45+
- name: Deploy to GitHub Pages
46+
id: deployment
47+
uses: actions/deploy-pages@v2

.github/workflows/tests.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Tests
2+
3+
on: [push]
4+
5+
# TODO: Add Windows job after Swift is added to the Windows images [1] or after
6+
# `swift-actions/setup-swift` supports Swift 5.9+ on Windows [2].
7+
# 1. https://github.com/actions/runner-images/issues/8281
8+
# 2. https://github.com/swift-actions/setup-swift/pull/470#issuecomment-1718406382
9+
jobs:
10+
test-macos:
11+
name: Run Tests on macOS
12+
# TODO: Use `macos-latest` after the macOS 13 image graduates to GA.
13+
# https://github.com/actions/runner-images/issues/7508#issuecomment-1718206371
14+
runs-on: macos-13
15+
16+
steps:
17+
- uses: maxim-lobanov/setup-xcode@v1
18+
with:
19+
xcode-version: latest-stable
20+
- name: Print Swift compiler version
21+
run: "swift --version"
22+
- uses: actions/checkout@v3
23+
- name: Run tests
24+
run: "swift test --parallel"
25+
26+
test-linux:
27+
name: Run Tests on Linux
28+
runs-on: ubuntu-latest
29+
30+
steps:
31+
- name: Print Swift compiler version
32+
run: "swift --version"
33+
- uses: actions/checkout@v3
34+
- name: Run tests
35+
run: "swift test --parallel"

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/config/registries.json
8+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9+
.netrc

LICENSE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# MIT License
2+
3+
Copyright © 2024 Darren Mo.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Package.resolved

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// swift-tools-version: 5.9
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "swift-http-error-handling",
7+
platforms: [
8+
.visionOS(.v1),
9+
.macOS(.v13),
10+
.macCatalyst(.v16),
11+
.iOS(.v16),
12+
.tvOS(.v16),
13+
.watchOS(.v9),
14+
],
15+
products: [
16+
.library(
17+
name: "HTTPErrorHandling",
18+
targets: [
19+
"HTTPErrorHandling",
20+
]
21+
),
22+
// According to SE-0356, Swift Package Manager does not yet officially support snippet-only dependencies.
23+
// This library product and the corresponding target work around that limitation. The product name is
24+
// prefixed with an underscore to convey that the product was not meant to be externally visible.
25+
.library(
26+
name: "_AdditionalSnippetDependencies",
27+
targets: [
28+
"_AdditionalSnippetDependencies",
29+
]
30+
),
31+
],
32+
dependencies: [
33+
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"),
34+
.package(url: "https://github.com/apple/swift-http-types.git", from: "1.0.2"),
35+
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.3"),
36+
.package(url: "https://github.com/fumoboy007/swift-retry.git", .upToNextMinor(from: "0.1.3")),
37+
],
38+
targets: [
39+
.target(
40+
name: "HTTPErrorHandling",
41+
dependencies: [
42+
.product(name: "DMRetry", package: "swift-retry"),
43+
.product(name: "HTTPTypes", package: "swift-http-types"),
44+
]
45+
),
46+
.testTarget(
47+
name: "HTTPErrorHandlingTests",
48+
dependencies: [
49+
"HTTPErrorHandling",
50+
.product(name: "HTTPTypes", package: "swift-http-types"),
51+
]
52+
),
53+
.target(
54+
name: "_AdditionalSnippetDependencies",
55+
dependencies: [
56+
.product(name: "HTTPTypesFoundation", package: "swift-http-types"),
57+
.product(name: "Logging", package: "swift-log"),
58+
]
59+
),
60+
]
61+
)

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# swift-http-error-handling
2+
3+
Interpret HTTP responses and handle failures in Swift.
4+
5+
![Swift 5.9](https://img.shields.io/badge/swift-v5.9-%23F05138)
6+
![Linux, visionOS 1, macOS 13, iOS 16, tvOS 16, watchOS 9](https://img.shields.io/badge/platform-Linux%20%7C%20visionOS%201%20%7C%20macOS%2013%20%7C%20iOS%2016%20%7C%20tvOS%2016%20%7C%20watchOS%209-blue)
7+
![MIT License](https://img.shields.io/github/license/fumoboy007/swift-http-error-handling)
8+
![Automated Tests Workflow Status](https://img.shields.io/github/actions/workflow/status/fumoboy007/swift-http-error-handling/tests.yml?event=push&label=tests)
9+
10+
## Overview
11+
12+
In the HTTP protocol, a client sends a request to a server and the server sends a response back to the client. The response contains a [status code](https://httpwg.org/specs/rfc9110.html#overview.of.status.codes) to help the client interpret the response.
13+
14+
HTTP libraries like `Foundation` pass the response through to the caller without interpreting the response as a success or failure. `HTTPErrorHandling` can help the caller interpret HTTP responses and handle failures.
15+
16+
The module works with any HTTP library that is compatible with Swift’s [standard HTTP request and response types](https://github.com/apple/swift-http-types). The module can be used on its own in code that directly uses an HTTP library, or the module can be used as a building block by higher-level networking libraries.
17+
18+
## Example Usage
19+
20+
```swift
21+
import Foundation
22+
import HTTPErrorHandling
23+
import HTTPTypes
24+
import HTTPTypesFoundation
25+
26+
let request = HTTPRequest(method: .get,
27+
scheme: "https",
28+
authority: "example.com",
29+
path: "/")
30+
31+
try await request.retry { request in
32+
let (_, response) = try await URLSession.shared.data(for: request)
33+
try response.throwIfFailed()
34+
}
35+
```
36+
37+
See the [documentation](https://fumoboy007.github.io/swift-http-error-handling/documentation/httperrorhandling/) for more examples.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Retry an HTTP request using `URLSession`.
2+
3+
// snippet.hide
4+
5+
#if canImport(Darwin)
6+
7+
// snippet.show
8+
9+
import Foundation
10+
import HTTPErrorHandling
11+
import HTTPTypes
12+
import HTTPTypesFoundation
13+
14+
let request = HTTPRequest(method: .get,
15+
scheme: "https",
16+
authority: "example.com",
17+
path: "/")
18+
19+
try await request.retry { request in
20+
let (_, response) = try await URLSession.shared.data(for: request)
21+
try response.throwIfFailed()
22+
}
23+
24+
// snippet.hide
25+
26+
#endif
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Customize the interpretation of the HTTP response status.
2+
3+
// snippet.hide
4+
5+
import HTTPErrorHandling
6+
import HTTPTypes
7+
8+
let request = HTTPRequest(method: .post,
9+
scheme: "https",
10+
authority: "example.com",
11+
path: "/")
12+
13+
// snippet.show
14+
15+
let response = try await perform(request)
16+
try response.throwIfFailed(
17+
successStatuses: [.created],
18+
transientFailureStatuses: .commonTransientFailureStatuses.union([.conflict])
19+
)
20+
21+
// snippet.hide
22+
23+
func perform(_ request: HTTPRequest) async throws -> HTTPResponse {
24+
return HTTPResponse(status: .ok)
25+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Throw an error if the HTTP response represents a failure.
2+
3+
// snippet.hide
4+
5+
import HTTPErrorHandling
6+
import HTTPTypes
7+
8+
let request = HTTPRequest(method: .get,
9+
scheme: "https",
10+
authority: "example.com",
11+
path: "/")
12+
13+
// snippet.show
14+
15+
let response = try await perform(request)
16+
try response.throwIfFailed()
17+
18+
// snippet.hide
19+
20+
func perform(_ request: HTTPRequest) async throws -> HTTPResponse {
21+
return HTTPResponse(status: .ok)
22+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Attach the response body to ``HTTPApplicationError`` and access it later.
2+
3+
// snippet.hide
4+
5+
import Foundation
6+
import HTTPErrorHandling
7+
import HTTPTypes
8+
9+
let request = HTTPRequest(method: .get,
10+
scheme: "https",
11+
authority: "example.com",
12+
path: "/")
13+
14+
// snippet.show
15+
16+
do {
17+
let (responseBody, response) = try await perform(request)
18+
try await response.throwIfFailed {
19+
return try await deserializeFailureDetails(from: responseBody)
20+
}
21+
} catch let error as HTTPApplicationError<MyFailureDetails> {
22+
let failureDetails = error.responseBody
23+
doSomething(with: failureDetails)
24+
}
25+
26+
// snippet.hide
27+
28+
func perform(_ request: HTTPRequest) async throws -> (Data, HTTPResponse) {
29+
return (Data(), HTTPResponse(status: .ok))
30+
}
31+
32+
struct MyFailureDetails {
33+
}
34+
35+
func deserializeFailureDetails(from responseBody: Data) async throws -> MyFailureDetails {
36+
return MyFailureDetails()
37+
}
38+
39+
func doSomething(with failureDetails: MyFailureDetails) {
40+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Retry an HTTP request.
2+
3+
// snippet.hide
4+
5+
import HTTPErrorHandling
6+
import HTTPTypes
7+
8+
let request = HTTPRequest(method: .get,
9+
scheme: "https",
10+
authority: "example.com",
11+
path: "/")
12+
13+
// snippet.show
14+
15+
try await request.retry { request in
16+
let response = try await perform(request)
17+
try response.throwIfFailed()
18+
}
19+
20+
// snippet.hide
21+
22+
func perform(_ request: HTTPRequest) async throws -> HTTPResponse {
23+
return HTTPResponse(status: .ok)
24+
}

0 commit comments

Comments
 (0)