Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
fumoboy007 committed Jan 3, 2024
0 parents commit 8f9d89b
Show file tree
Hide file tree
Showing 32 changed files with 1,263 additions and 0 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Documentation

on:
push:
branches:
- main

permissions:
contents: read
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
publish:
name: Publish Documentation
# TODO: Use `macos-latest` after the macOS 13 image graduates to GA.
# https://github.com/actions/runner-images/issues/7508#issuecomment-1718206371
runs-on: macos-13

environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

steps:
- name: Set up GitHub Pages
uses: actions/configure-pages@v3
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Print Swift compiler version
run: "swift --version"
- uses: actions/checkout@v3
- name: Generate documentation
run: "swift package generate-documentation --target HTTPErrorHandling --disable-indexing --include-extended-types --transform-for-static-hosting --hosting-base-path swift-http-error-handling"
- name: Upload documentation
uses: actions/upload-pages-artifact@v2
with:
path: ".build/plugins/Swift-DocC/outputs/HTTPErrorHandling.doccarchive"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
35 changes: 35 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Tests

on: [push]

# TODO: Add Windows job after Swift is added to the Windows images [1] or after
# `swift-actions/setup-swift` supports Swift 5.9+ on Windows [2].
# 1. https://github.com/actions/runner-images/issues/8281
# 2. https://github.com/swift-actions/setup-swift/pull/470#issuecomment-1718406382
jobs:
test-macos:
name: Run Tests on macOS
# TODO: Use `macos-latest` after the macOS 13 image graduates to GA.
# https://github.com/actions/runner-images/issues/7508#issuecomment-1718206371
runs-on: macos-13

steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Print Swift compiler version
run: "swift --version"
- uses: actions/checkout@v3
- name: Run tests
run: "swift test --parallel"

test-linux:
name: Run Tests on Linux
runs-on: ubuntu-latest

steps:
- name: Print Swift compiler version
run: "swift --version"
- uses: actions/checkout@v3
- name: Run tests
run: "swift test --parallel"
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
9 changes: 9 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# MIT License

Copyright © 2024 Darren Mo.

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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

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.
50 changes: 50 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"pins" : [
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin.git",
"state" : {
"revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
"version" : "1.3.0"
}
},
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
},
{
"identity" : "swift-http-types",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-types.git",
"state" : {
"revision" : "1827dc94bdab2eb5f2fc804e9b0cb43574282566",
"version" : "1.0.2"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed",
"version" : "1.5.3"
}
},
{
"identity" : "swift-retry",
"kind" : "remoteSourceControl",
"location" : "https://github.com/fumoboy007/swift-retry.git",
"state" : {
"revision" : "bd3f43ed9deaa0d296928e8bf0f02dcbd935fe14",
"version" : "0.1.3"
}
}
],
"version" : 2
}
61 changes: 61 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// swift-tools-version: 5.9

import PackageDescription

let package = Package(
name: "swift-http-error-handling",
platforms: [
.visionOS(.v1),
.macOS(.v13),
.macCatalyst(.v16),
.iOS(.v16),
.tvOS(.v16),
.watchOS(.v9),
],
products: [
.library(
name: "HTTPErrorHandling",
targets: [
"HTTPErrorHandling",
]
),
// According to SE-0356, Swift Package Manager does not yet officially support snippet-only dependencies.
// This library product and the corresponding target work around that limitation. The product name is
// prefixed with an underscore to convey that the product was not meant to be externally visible.
.library(
name: "_AdditionalSnippetDependencies",
targets: [
"_AdditionalSnippetDependencies",
]
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"),
.package(url: "https://github.com/apple/swift-http-types.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.3"),
.package(url: "https://github.com/fumoboy007/swift-retry.git", .upToNextMinor(from: "0.1.3")),
],
targets: [
.target(
name: "HTTPErrorHandling",
dependencies: [
.product(name: "DMRetry", package: "swift-retry"),
.product(name: "HTTPTypes", package: "swift-http-types"),
]
),
.testTarget(
name: "HTTPErrorHandlingTests",
dependencies: [
"HTTPErrorHandling",
.product(name: "HTTPTypes", package: "swift-http-types"),
]
),
.target(
name: "_AdditionalSnippetDependencies",
dependencies: [
.product(name: "HTTPTypesFoundation", package: "swift-http-types"),
.product(name: "Logging", package: "swift-log"),
]
),
]
)
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# swift-http-error-handling

Interpret HTTP responses and handle failures in Swift.

![Swift 5.9](https://img.shields.io/badge/swift-v5.9-%23F05138)
![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)
![MIT License](https://img.shields.io/github/license/fumoboy007/swift-http-error-handling)
![Automated Tests Workflow Status](https://img.shields.io/github/actions/workflow/status/fumoboy007/swift-http-error-handling/tests.yml?event=push&label=tests)

## Overview

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.

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.

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.

## Example Usage

```swift
import Foundation
import HTTPErrorHandling
import HTTPTypes
import HTTPTypesFoundation

let request = HTTPRequest(method: .get,
scheme: "https",
authority: "example.com",
path: "/")

try await request.retry { request in
let (_, response) = try await URLSession.shared.data(for: request)
try response.throwIfFailed()
}
```

See the [documentation](https://fumoboy007.github.io/swift-http-error-handling/documentation/httperrorhandling/) for more examples.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Retry an HTTP request using `URLSession`.

import Foundation
import HTTPErrorHandling
import HTTPTypes
import HTTPTypesFoundation

let request = HTTPRequest(method: .get,
scheme: "https",
authority: "example.com",
path: "/")

try await request.retry { request in
let (_, response) = try await URLSession.shared.data(for: request)
try response.throwIfFailed()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Customize the interpretation of the HTTP response status.

// snippet.hide

import HTTPErrorHandling
import HTTPTypes

let request = HTTPRequest(method: .post,
scheme: "https",
authority: "example.com",
path: "/")

// snippet.show

let response = try await perform(request)
try response.throwIfFailed(
successStatuses: [.created],
transientFailureStatuses: .commonTransientFailureStatuses.union([.conflict])
)

// snippet.hide

func perform(_ request: HTTPRequest) async throws -> HTTPResponse {
return HTTPResponse(status: .ok)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Throw an error if the HTTP response represents a failure.

// snippet.hide

import HTTPErrorHandling
import HTTPTypes

let request = HTTPRequest(method: .get,
scheme: "https",
authority: "example.com",
path: "/")

// snippet.show

let response = try await perform(request)
try response.throwIfFailed()

// snippet.hide

func perform(_ request: HTTPRequest) async throws -> HTTPResponse {
return HTTPResponse(status: .ok)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Attach the response body to ``HTTPApplicationError`` and access it later.

// snippet.hide

import Foundation
import HTTPErrorHandling
import HTTPTypes

let request = HTTPRequest(method: .get,
scheme: "https",
authority: "example.com",
path: "/")

// snippet.show

do {
let (responseBody, response) = try await perform(request)
try await response.throwIfFailed {
return try await deserializeFailureDetails(from: responseBody)
}
} catch let error as HTTPApplicationError<MyFailureDetails> {
let failureDetails = error.responseBody
doSomething(with: failureDetails)
}

// snippet.hide

func perform(_ request: HTTPRequest) async throws -> (Data, HTTPResponse) {
return (Data(), HTTPResponse(status: .ok))
}

struct MyFailureDetails {
}

func deserializeFailureDetails(from responseBody: Data) async throws -> MyFailureDetails {
return MyFailureDetails()
}

func doSomething(with failureDetails: MyFailureDetails) {
}
24 changes: 24 additions & 0 deletions Snippets/Retrying HTTP Requests/BasicRetry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Retry an HTTP request.

// snippet.hide

import HTTPErrorHandling
import HTTPTypes

let request = HTTPRequest(method: .get,
scheme: "https",
authority: "example.com",
path: "/")

// snippet.show

try await request.retry { request in
let response = try await perform(request)
try response.throwIfFailed()
}

// snippet.hide

func perform(_ request: HTTPRequest) async throws -> HTTPResponse {
return HTTPResponse(status: .ok)
}
Loading

0 comments on commit 8f9d89b

Please sign in to comment.