From 23fca6f4e182c5040bfbabe23d5dfe6ed303a9f2 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 15 Mar 2024 16:22:47 +0100 Subject: [PATCH 01/14] Bump swift tools version --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 19654c66..2bd81d6c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.8 +// swift-tools-version:5.9 import PackageDescription From 26365f9dd4f0e77701bd1a1cc306b0cbae5d729b Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 24 May 2024 15:00:33 +0200 Subject: [PATCH 02/14] Enable complete concurrency checking --- Package.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 2bd81d6c..58c2614d 100644 --- a/Package.swift +++ b/Package.swift @@ -32,7 +32,10 @@ let package = Package( dependencies: [ .product(name: "Collections", package: "swift-collections") ], - exclude: ["Abstraction/README.md", "Framework/README.md"] + exclude: ["Abstraction/README.md", "Framework/README.md"], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency=complete") + ] ), .target( name: "HTMLKitConverter", @@ -54,6 +57,9 @@ let package = Package( dependencies: [ .target(name: "HTMLKit"), .product(name: "Vapor", package: "vapor"), + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency=complete") ] ), .target( From 2d964782ca578cd095af677cce8cb3b4e4dfe2f9 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 24 May 2024 15:42:12 +0200 Subject: [PATCH 03/14] Update test workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9527b938..22724929 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ on: jobs: test-pushpull: runs-on: ubuntu-latest - container: swift:5.8-focal + container: swift:5.9.2-jammy steps: - uses: actions/checkout@v3 - name: Run tests From 558f10200eafc449353d77b6f43aa42ddbdf2b1e Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Mon, 2 Sep 2024 20:03:51 +0200 Subject: [PATCH 04/14] Make the features optionset sendable --- Sources/HTMLKit/Framework/Rendering/Features.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Rendering/Features.swift b/Sources/HTMLKit/Framework/Rendering/Features.swift index 780ea9e2..697a2ae6 100644 --- a/Sources/HTMLKit/Framework/Rendering/Features.swift +++ b/Sources/HTMLKit/Framework/Rendering/Features.swift @@ -1,5 +1,5 @@ /// An option set of features. -public struct Features: OptionSet { +public struct Features: OptionSet, Sendable { public var rawValue: Int From 12760a293a20c0427c127003e543ef1a3d1d87c5 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Mon, 2 Sep 2024 20:06:33 +0200 Subject: [PATCH 05/14] Make the markdown structure sendable --- Sources/HTMLKit/Framework/Rendering/Markdown/Markdown.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Rendering/Markdown/Markdown.swift b/Sources/HTMLKit/Framework/Rendering/Markdown/Markdown.swift index a13a3de7..9a46b776 100644 --- a/Sources/HTMLKit/Framework/Rendering/Markdown/Markdown.swift +++ b/Sources/HTMLKit/Framework/Rendering/Markdown/Markdown.swift @@ -1,6 +1,6 @@ import Foundation -internal final class Markdown { +internal struct Markdown: Sendable { /// The markdowns characters internal static let characters = CharacterSet(charactersIn: "*_~[`") From 8277c8fd942015bd3d96fe6fc6fda9c62dd84c9a Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 28 Nov 2025 10:10:34 +0100 Subject: [PATCH 06/14] Bump swift tools version --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 9ac366b1..e81545f7 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version:5.10 import PackageDescription From b79b4cb17f6996e6ef19f6ad2fd6be4dde5a4b17 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 28 Nov 2025 15:07:05 +0100 Subject: [PATCH 07/14] Mark the interpolationargument enum as sendable --- .../HTMLKit/Framework/Localization/InterpolationArgument.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Localization/InterpolationArgument.swift b/Sources/HTMLKit/Framework/Localization/InterpolationArgument.swift index 415c7513..83c1e7f2 100644 --- a/Sources/HTMLKit/Framework/Localization/InterpolationArgument.swift +++ b/Sources/HTMLKit/Framework/Localization/InterpolationArgument.swift @@ -5,7 +5,7 @@ import Foundation /// Each case corresponds to a specific data type and provides a placeholder /// that can be used for replacing values in the localized string. @_documentation(visibility: internal) -public enum InterpolationArgument { +public enum InterpolationArgument: Sendable { /// Holds an integer value case int(Int) From 2509f182ed9565958b5bf94202f7b1fefc9f0f47 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 28 Nov 2025 15:08:56 +0100 Subject: [PATCH 08/14] Mark the localizedstringkey structure as sendable --- Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift b/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift index 0e138f5e..ce657c34 100644 --- a/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift +++ b/Sources/HTMLKit/Framework/Localization/LocalizedStringKey.swift @@ -2,7 +2,7 @@ import Foundation /// A string key for the localization @_documentation(visibility: internal) -public struct LocalizedStringKey { +public struct LocalizedStringKey: Sendable { /// The key value internal let value: String From f3d9fa30e0eac09034cefc6fb1e9ff19e0fd0c4d Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 28 Nov 2025 15:11:40 +0100 Subject: [PATCH 09/14] Mark the translationtable structure as sendable --- Sources/HTMLKit/Framework/Localization/TranslationTable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Localization/TranslationTable.swift b/Sources/HTMLKit/Framework/Localization/TranslationTable.swift index cd6a481b..b47f6caf 100644 --- a/Sources/HTMLKit/Framework/Localization/TranslationTable.swift +++ b/Sources/HTMLKit/Framework/Localization/TranslationTable.swift @@ -1,7 +1,7 @@ /// A type that represents a translation table /// /// A translation table stores multiple localized strings, mapping unique string keys to their corresponding translations -internal struct TranslationTable { +internal struct TranslationTable: Sendable { /// The name of the table internal let name: String From 76ba9124496e0a7a143673240b4934fe7de5c90f Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 28 Nov 2025 15:42:13 +0100 Subject: [PATCH 10/14] Mark the localizedstring structure as sendable --- Sources/HTMLKit/Framework/Localization/LocalizedString.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Localization/LocalizedString.swift b/Sources/HTMLKit/Framework/Localization/LocalizedString.swift index fa00f711..a88668b3 100644 --- a/Sources/HTMLKit/Framework/Localization/LocalizedString.swift +++ b/Sources/HTMLKit/Framework/Localization/LocalizedString.swift @@ -2,7 +2,7 @@ import Foundation /// A type thats holds the information for the localization @_documentation(visibility: internal) -public struct LocalizedString: Content { +public struct LocalizedString: Content, Sendable { /// The key of the translation value internal let key: LocalizedStringKey From 2c3bb7094de0402e2acff76b75f65e37f56de94a Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 28 Nov 2025 15:42:45 +0100 Subject: [PATCH 11/14] Update test workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 22724929..0296e0e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ on: jobs: test-pushpull: runs-on: ubuntu-latest - container: swift:5.9.2-jammy + container: swift:5.10.1-jammy steps: - uses: actions/checkout@v3 - name: Run tests From 52aea0e64a8416c4e29b7010a3624a54ad58ca05 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 9 Jan 2026 12:13:51 +0100 Subject: [PATCH 12/14] Mark the locale structure as sendable --- Sources/HTMLKit/Framework/Localization/Locale.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/HTMLKit/Framework/Localization/Locale.swift b/Sources/HTMLKit/Framework/Localization/Locale.swift index 2d0fd284..e3e18679 100644 --- a/Sources/HTMLKit/Framework/Localization/Locale.swift +++ b/Sources/HTMLKit/Framework/Localization/Locale.swift @@ -2,10 +2,10 @@ /// /// A locale holds information about language, region and cultural preferences. @_documentation(visibility: internal) -public struct Locale: Hashable { +public struct Locale: Hashable, Sendable { /// A enumeration of potential language tags - public enum Tag: String { + public enum Tag: String, Sendable { case arabic = "ar" case belarusian = "be" From 71f3e07ec043f96077603584923f846c72283b82 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 9 Jan 2026 14:35:56 +0100 Subject: [PATCH 13/14] Resolve the warnings of the provider tests --- Tests/HTMLKitVaporTests/ProviderTests.swift | 110 +++++++++----------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/Tests/HTMLKitVaporTests/ProviderTests.swift b/Tests/HTMLKitVaporTests/ProviderTests.swift index 5085aa01..20121cf6 100644 --- a/Tests/HTMLKitVaporTests/ProviderTests.swift +++ b/Tests/HTMLKitVaporTests/ProviderTests.swift @@ -93,11 +93,9 @@ final class ProviderTests: XCTestCase { } } - func testEventLoopIntegration() throws { + func testEventLoopIntegration() async throws { - let app = Application(.testing) - - defer { app.shutdown() } + let app = try await Application.make(.testing) app.get("test") { request -> EventLoopFuture in @@ -106,7 +104,7 @@ final class ProviderTests: XCTestCase { return request.htmlkit.render(TestPage.NephewView(context: context)) } - try app.test(.GET, "test") { response in + try await app.test(.GET, "test") { response async in XCTAssertEqual(response.status, .ok) XCTAssertEqual(response.body.string, """ @@ -122,13 +120,13 @@ final class ProviderTests: XCTestCase { """ ) } + + try await app.asyncShutdown() } - func testConcurrencyIntegration() throws { - - let app = Application(.testing) + func testConcurrencyIntegration() async throws { - defer { app.shutdown() } + let app = try await Application.make(.testing) app.get("test") { request async throws -> Vapor.View in @@ -137,7 +135,7 @@ final class ProviderTests: XCTestCase { return try await request.htmlkit.render(TestPage.NephewView(context: context)) } - try app.test(.GET, "test") { response in + try await app.test(.GET, "test") { response async in XCTAssertEqual(response.status, .ok) XCTAssertEqual(response.body.string, """ @@ -153,18 +151,18 @@ final class ProviderTests: XCTestCase { """ ) } + + try await app.asyncShutdown() } - /// Tests the setup of localization through Vapor - func testLocalizationIntegration() throws { + /// Tests the setup of localization through Vapor. + func testLocalizationIntegration() async throws { guard let source = Bundle.module.url(forResource: "Localization", withExtension: nil) else { return } - let app = Application(.testing) - - defer { app.shutdown() } + let app = try await Application.make(.testing) app.htmlkit.localization.set(source: source) app.htmlkit.localization.set(locale: "fr") @@ -174,7 +172,7 @@ final class ProviderTests: XCTestCase { return try await request.htmlkit.render(TestPage.ChildView()) } - try app.test(.GET, "test") { response in + try await app.test(.GET, "test") { response async in XCTAssertEqual(response.status, .ok) XCTAssertEqual(response.body.string, """ @@ -190,24 +188,24 @@ final class ProviderTests: XCTestCase { """ ) } + + try await app.asyncShutdown() } /// Tests the behavior when localization is not properly configured /// /// Localization is considered improperly configured when one or both of the essential factors are missing. /// In such case the renderer is expected to skip the localization and directly return the fallback string literal. - func testLocalizationFallback() throws { + func testLocalizationFallback() async throws { - let app = Application(.testing) - - defer { app.shutdown() } + let app = try await Application.make(.testing) app.get("test") { request async throws -> Vapor.View in return try await request.htmlkit.render(TestPage.ChildView()) } - try app.test(.GET, "test") { response in + try await app.test(.GET, "test") { response async in XCTAssertEqual(response.status, .ok) XCTAssertEqual(response.body.string, """ @@ -223,34 +221,31 @@ final class ProviderTests: XCTestCase { """ ) } + + try await app.asyncShutdown() } - /// Tests the localization behavior based on the accept language of the client + /// Tests the localization behavior based on the accept language of the client. /// /// The environment locale is expected to be changed according to the language given by the provider. /// The renderer is expected to localize correctly the content based on the updated environment locale. - func testLocalizationByAcceptingHeaders() throws { + func testLocalizationByAcceptingHeaders() async throws { guard let source = Bundle.module.url(forResource: "Localization", withExtension: nil) else { return } - let app = Application(.testing) - - defer { app.shutdown() } + let app = try await Application.make(.testing) app.htmlkit.localization.set(source: source) app.htmlkit.localization.set(locale: "en-GB") app.get("test") { request async throws -> Vapor.View in - // Overwrite the accept language header to simulate a different language - request.headers.replaceOrAdd(name: "accept-language", value: "fr") - return try await request.htmlkit.render(TestPage.ChildView()) } - try app.test(.GET, "test") { response in + try await app.test(.GET, "test", headers: ["accept-language": "fr"]) { response async in XCTAssertEqual(response.status, .ok) XCTAssertEqual(response.body.string, """ @@ -266,6 +261,8 @@ final class ProviderTests: XCTestCase { """ ) } + + try await app.asyncShutdown() } /// Tests the localization behavior when the preferred language is unknown. @@ -284,14 +281,11 @@ final class ProviderTests: XCTestCase { app.htmlkit.localization.set(locale: "en-GB") app.get("test") { request async throws -> Vapor.View in - - // Overwrite the accept language header to simulate a different language - request.headers.replaceOrAdd(name: "accept-language", value: "en-US") - return try await request.htmlkit.render(TestPage.ChildView()) } - try await app.test(.GET, "test") { response async in + try await app.test(.GET, "test", headers: ["accept-language": "en-US"]) { response async in + XCTAssertEqual(response.status, .ok) XCTAssertEqual(response.body.string, """ @@ -311,14 +305,12 @@ final class ProviderTests: XCTestCase { try await app.asyncShutdown() } - /// Tests the access to environment through provider + /// Tests the access to environment through provider. /// /// The provider is expected to recieve the environment object and resolve it based on the request. - func testEnvironmentIntegration() throws { + func testEnvironmentIntegration() async throws { - let app = Application(.testing) - - defer { app.shutdown() } + let app = try await Application.make(.testing) app.htmlkit.environment.upsert(TestObject(), for: \TestObject.self) @@ -326,7 +318,8 @@ final class ProviderTests: XCTestCase { return try await request.htmlkit.render(TestPage.SipplingView()) } - try app.test(.GET, "test") { response in + try await app.test(.GET, "test") { response async in + XCTAssertEqual(response.status, .ok) XCTAssertEqual(response.body.string, """ @@ -342,21 +335,22 @@ final class ProviderTests: XCTestCase { """ ) } + + try await app.asyncShutdown() } - func testMarkdownSupport() throws { + /// Tests the markdown support. + func testMarkdownSupport() async throws { - let app = Application(.testing) - - defer { app.shutdown() } + let app = try await Application.make(.testing) - app.htmlkit.features = [.markdown] + app.htmlkit.features = [.markdown, .escaping] app.get("test") { request async throws -> Vapor.View in return try await request.htmlkit.render(TestPage.FriendView()) } - try app.test(.GET, "test") { response in + try await app.test(.GET, "test") { response async in XCTAssertEqual(response.status, .ok) XCTAssertEqual(response.body.string, """ @@ -372,19 +366,21 @@ final class ProviderTests: XCTestCase { """ ) } + + try await app.asyncShutdown() } /// Tests the error reporting to Vapor for issues that may occur during environment access. /// /// The error is expected to be classified as an internal server error and includes a error message. - func testEnvironmentErrorReporting() throws { + func testEnvironmentErrorReporting() async throws { struct TestObject { let firstName = "Jane" } - struct UnkownObject: HTMLKit.View { + struct UnknownObject: HTMLKit.View { @EnvironmentObject(TestObject.self) var object @@ -413,21 +409,17 @@ final class ProviderTests: XCTestCase { } } - let app = Application(.testing) - - defer { app.shutdown() } + let app = try await Application.make(.testing) - app.get("unkownobject") { request async throws -> Vapor.View in - - return try await request.htmlkit.render(UnkownObject()) + app.get("unknownobject") { request async throws -> Vapor.View in + return try await request.htmlkit.render(UnknownObject()) } app.get("wrongcast") { request async throws -> Vapor.View in - return try await request.htmlkit.render(WrongCast()) } - try app.test(.GET, "unkownobject") { response in + try await app.test(.GET, "unknownobject") { response async throws in XCTAssertEqual(response.status, .internalServerError) @@ -436,7 +428,7 @@ final class ProviderTests: XCTestCase { XCTAssertEqual(abort.reason, "Unable to retrieve environment object.") } - try app.test(.GET, "wrongcast") { response in + try await app.test(.GET, "wrongcast") { response async throws in XCTAssertEqual(response.status, .internalServerError) @@ -444,5 +436,7 @@ final class ProviderTests: XCTestCase { XCTAssertEqual(abort.reason, "Unable to cast the environment value.") } + + try await app.asyncShutdown() } } From a8598de20a8a0fe1094d5056653e17ad07fa52e0 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 9 Jan 2026 15:13:14 +0100 Subject: [PATCH 14/14] Make the localization structure sendable --- Sources/HTMLKit/Framework/Localization/Localization.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/HTMLKit/Framework/Localization/Localization.swift b/Sources/HTMLKit/Framework/Localization/Localization.swift index 45e7082a..9583e6b8 100644 --- a/Sources/HTMLKit/Framework/Localization/Localization.swift +++ b/Sources/HTMLKit/Framework/Localization/Localization.swift @@ -2,7 +2,7 @@ import Foundation /// A type that represents the localization @_documentation(visibility: internal) -public class Localization { +public struct Localization: Sendable { /// A enumeration of errors regarding the localization rendering public enum Errors: Error, Equatable { @@ -81,14 +81,14 @@ public class Localization { /// Sets the source directory /// /// - Parameter source: The directory where the translations should be loaded from. - public func set(source: URL) { + public mutating func set(source: URL) { self.tables = load(source: source) } /// Sets the default locale /// /// - Parameter locale: A locale tag e.g. en-US - public func set(locale: String) { + public mutating func set(locale: String) { self.locale = Locale(tag: locale) }