Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ concurrency:
jobs:
analyze:
name: Analyze
runs-on: macos-14
runs-on: macos-15
permissions:
security-events: write

Expand All @@ -35,6 +35,7 @@ jobs:

- name: Build
run: swift build

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4

2 changes: 1 addition & 1 deletion .github/workflows/docc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: macos-14
runs-on: macos-15

steps:
- name: Checkout 🛎️
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ concurrency:

jobs:
SwiftLint:
runs-on: macOS-14
runs-on: macOS-15
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.0.1"
xcode-version: "16.3.0"

- name: Install SwiftLint
run: brew install swiftlint

- name: Verify SwiftLint version
run: swiftlint version

- name: Run SwiftLint
run: swiftlint --strict
run: swiftlint --strict --config .swiftlint.yml
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ jobs:
# macOS
# =========================
macos:
runs-on: macos-14
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.0.1"
xcode-version: "16.3.0"
- name: Cache SwiftPM
uses: actions/cache@v4
with:
Expand All @@ -45,7 +45,7 @@ jobs:
# =========================
linux:
runs-on: ubuntu-latest
container: swift:5.9-jammy
container: swift:6.0-jammy
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ disabled_rules: # rule identifiers to exclude from running
- todo
- unused_capture_list
- trailing_whitespace
- void_return
opt_in_rules: # some rules are only opt-in
- array_init
# Prefer using Array(seq) over seq.map { $0 } to convert a sequence into an Array.
Expand Down
18 changes: 18 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 4 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -18,6 +18,7 @@ let package = Package(
dependencies: [
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "4.115.0"),
// 📄 Swift-DocC plugin for generating documentation.
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.0.0"),
],
targets: [
Expand All @@ -32,13 +33,8 @@ let package = Package(
.testTarget(
name: "ErrorMiddlewareTests",
dependencies: [
.target(
name: "ErrorMiddleware"
),
.product(
name: "XCTVapor",
package: "vapor"
),
.target(name: "ErrorMiddleware"),
.product(name: "VaporTesting", package: "vapor")
]
)
]
Expand Down
10 changes: 5 additions & 5 deletions Sources/ErrorMiddleware/ErrorMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public extension ErrorMiddleware {
httpStatus = appError.status
headers = appError.headers
identifier = appError.identifier
status = appError.status.code.description
status = "\(appError.status.code)"
code = "\(appError.status.code.description).\(number).\(appError.number)"
case let abort as AbortError:
/// This is an abort error, we should use its status, reason, and headers
Expand All @@ -73,17 +73,17 @@ public extension ErrorMiddleware {
httpStatus = .internalServerError
headers = [:]
identifier = error.errorDescription
status = "500"
code = "500.\(number).0000"
status = "\(HTTPStatus.internalServerError.code)"
code = "\(HTTPStatus.internalServerError.code).\(number).0000"
default:
/// Not an abort error, and not debuggable or in dev mode
/// Just deliver a generic 500 to avoid exposing any sensitive error info
reason = "Something went wrong."
httpStatus = .internalServerError
headers = [:]
identifier = "something_went_wrong"
status = "500"
code = "500.\(number).0000"
status = "\(HTTPStatus.internalServerError.code)"
code = "\(HTTPStatus.internalServerError.code).\(number).0000"
}
/// Report the error to logger.
req.logger.report(error: error)
Expand Down
75 changes: 39 additions & 36 deletions Tests/ErrorMiddlewareTests/ErrorMiddlewareTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,53 +15,56 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import XCTVapor
@testable import ErrorMiddleware
import VaporTesting
import Testing

final class ErrorMiddlewareTests: XCTestCase {
// swiftlint: disable implicitly_unwrapped_optional
var app: Application!
// swiftlint: enable implicitly_unwrapped_optional
override func setUp() async throws {
app = try await Application.make(.testing)
app.post("order") { _ -> String in
return "done"
@Suite("Error middleware tests")
struct ErrorMiddlewareTests {
private func withApp(_ test: (Application) async throws -> ()) async throws {
let app = try await Application.make(.testing)
do {
app.post("order") { _ -> String in
return "done"
}
try await test(app)
} catch {
throw error
}
}

override func tearDown() async throws {
try await app.asyncShutdown()
}

func testErrorMiddlewareSnakeCase() throws {
let app = Application(.testing)
defer { app.shutdown() }
app.middleware.use(ErrorMiddleware.custom(environment: app.environment, for: 1))
@Test("Error middleware snake case")
func errorMiddlewareSnakeCase() async throws {
try await withApp { app in
app.middleware.use(ErrorMiddleware.custom(environment: app.environment, for: 1))

try app.testable().test(.GET, "order") { res throws in
let error = try res.content.decode(ErrorResponse.self, using: app.errorDecode)
XCTAssertEqual(error.isError, true)
XCTAssertEqual(error.reason, "Not Found")
XCTAssertEqual(error.error, "404")
XCTAssertEqual(error.status, "404")
XCTAssertEqual(error.code, "404.1.")
XCTAssertEqual(error.errorUri, "https://example.com/doc/errors")
try await app.test(.GET, "order") { res throws in
let error = try res.content.decode(ErrorResponse.self, using: app.errorDecode)
#expect(error.isError == true)
#expect(error.reason == "Not Found")
#expect(error.error == "404")
#expect(error.status == "404")
#expect(error.code == "404.1.")
#expect(error.errorUri == "https://example.com/doc/errors")
}
}
}

func testErrorMiddlewareDefaultCase() throws {
let app = Application(.testing)
defer { app.shutdown() }
app.middleware.use(ErrorMiddleware.custom(environment: app.environment, for: 1, keyEncodingStrategy: .useDefaultKeys))
@Test("Error middleware default case")
func errorMiddlewareDefaultCase() async throws {
try await withApp { app in
app.middleware.use(ErrorMiddleware.custom(environment: app.environment, for: 1, keyEncodingStrategy: .useDefaultKeys))

try app.testable().test(.GET, "order") { res throws in
let error = try res.content.decode(ErrorResponse.self)
XCTAssertEqual(error.isError, true)
XCTAssertEqual(error.reason, "Not Found")
XCTAssertEqual(error.error, "404")
XCTAssertEqual(error.status, "404")
XCTAssertEqual(error.code, "404.1.")
XCTAssertEqual(error.errorUri, "https://example.com/doc/errors")
try await app.test(.GET, "order") { res throws in
let error = try res.content.decode(ErrorResponse.self)
#expect(error.isError == true)
#expect(error.reason == "Not Found")
#expect(error.error == "404")
#expect(error.status == "404")
#expect(error.code == "404.1.")
#expect(error.errorUri == "https://example.com/doc/errors")
}
}
}
}
26 changes: 8 additions & 18 deletions Tests/ErrorMiddlewareTests/ErrorResponseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,14 @@
// Created by Mykola Buhaiov on 18.06.2024.
//

import XCTest
import XCTVapor
@testable import ErrorMiddleware
import VaporTesting
import Testing

/// Unit tests for the `ErrorResponse` equatable conformance.
final class ErrorResponseTests: XCTestCase {
// swiftlint: disable implicitly_unwrapped_optional
var app: Application!
// swiftlint: enable implicitly_unwrapped_optional
override func setUp() async throws {
app = try await Application.make(.testing)
}

override func tearDown() async throws {
try await app.asyncShutdown()
}
/// Tests the equality of two `ErrorResponse` instances.
func testErrorResponseEquatable() {
@Suite("Error response tests")
struct ErrorResponseTests {
@Test("Error response equatable")
func errorResponseEquatable() {
let firstErrorResponse = ErrorResponse(
isError: true,
reason: "reason of error",
Expand All @@ -57,10 +47,10 @@ final class ErrorResponseTests: XCTestCase {
errorUri: "error uri"
)
// Assert that the two instances are equal.
XCTAssertEqual(firstErrorResponse, secondErrorResponse)
#expect(firstErrorResponse == secondErrorResponse)
// Modify a property in the second instance.
secondErrorResponse.code = "4"
// Assert that the two instances are not equal after the modification.
XCTAssertNotEqual(firstErrorResponse, secondErrorResponse)
#expect(firstErrorResponse != secondErrorResponse)
}
}
Loading