From 23fca6f4e182c5040bfbabe23d5dfe6ed303a9f2 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 15 Mar 2024 16:22:47 +0100 Subject: [PATCH 01/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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) } From ec004fcc9deb8ab518b5a7252d47806dbed2ce20 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 13 Mar 2026 19:44:53 +0100 Subject: [PATCH 15/30] Mark the mediaquery structure as sendable --- Sources/HTMLKit/Abstraction/Types/MediaQuery.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/HTMLKit/Abstraction/Types/MediaQuery.swift b/Sources/HTMLKit/Abstraction/Types/MediaQuery.swift index 6b7b5b64..3d6a7cf9 100644 --- a/Sources/HTMLKit/Abstraction/Types/MediaQuery.swift +++ b/Sources/HTMLKit/Abstraction/Types/MediaQuery.swift @@ -10,10 +10,10 @@ /// MediaQuery(.print, features: .resolution("300dpi")) /// ) /// ``` -public struct MediaQuery { +public struct MediaQuery: Sendable { /// An enumeration of potential interface orientations. - public enum InterfaceOrientation { + public enum InterfaceOrientation: Sendable { /// Indicates a landscape orientation. case landscape @@ -34,7 +34,7 @@ public struct MediaQuery { } /// An enumeration of potential media features. - public enum MediaFeature { + public enum MediaFeature: Sendable { /// Specifies the minimum target width. case minWidth(String) @@ -120,7 +120,7 @@ public struct MediaQuery { } /// An enumeration of potential media devices. - public enum MediaTarget: String { + public enum MediaTarget: String, Sendable { /// Matches all devices. case all From 45d586e54b2a050e5956966b24252eeda648cba0 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 13 Mar 2026 19:45:26 +0100 Subject: [PATCH 16/30] Mark the sizecandidate structure as sendable --- Sources/HTMLKit/Abstraction/Types/SizeCandidate.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/HTMLKit/Abstraction/Types/SizeCandidate.swift b/Sources/HTMLKit/Abstraction/Types/SizeCandidate.swift index 85e73242..71e1ead9 100644 --- a/Sources/HTMLKit/Abstraction/Types/SizeCandidate.swift +++ b/Sources/HTMLKit/Abstraction/Types/SizeCandidate.swift @@ -11,10 +11,10 @@ /// SizeCandidate("80vw") /// ) /// ``` -public struct SizeCandidate { +public struct SizeCandidate: Sendable { /// An enumeration of potential interface orientations. - public enum InterfaceOrientation { + public enum InterfaceOrientation: Sendable { /// Indicates a landscape orientation. case landscape @@ -36,7 +36,7 @@ public struct SizeCandidate { } /// An enumeration of potential width conditions. - public enum SizeCondition { + public enum SizeCondition: Sendable { /// Specifies the maximum width. case maxWidth(String) From 76d2740fcf962199dda59c17d4d139b1ecf45a2d Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 13 Mar 2026 19:46:00 +0100 Subject: [PATCH 17/30] Mark the sourcecandidate structure as sendable --- Sources/HTMLKit/Abstraction/Types/SourceCandidate.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/HTMLKit/Abstraction/Types/SourceCandidate.swift b/Sources/HTMLKit/Abstraction/Types/SourceCandidate.swift index 6a529959..68e861b5 100644 --- a/Sources/HTMLKit/Abstraction/Types/SourceCandidate.swift +++ b/Sources/HTMLKit/Abstraction/Types/SourceCandidate.swift @@ -12,10 +12,10 @@ import Foundation /// SourceCandiate("...png", width: 1680) /// ) /// ``` -public struct SourceCandidate { +public struct SourceCandidate: Sendable { /// An enumeration of potential pixel densities. - public enum PixelDensity { + public enum PixelDensity: Sendable { /// Specifies a 1:1 density. case regular From 3d44d03c17ed155fc99915aa322a413f45454f07 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 13 Mar 2026 19:46:22 +0100 Subject: [PATCH 18/30] Mark the unitpoint structure as sendable --- Sources/HTMLKit/Abstraction/Types/UnitPoint.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/HTMLKit/Abstraction/Types/UnitPoint.swift b/Sources/HTMLKit/Abstraction/Types/UnitPoint.swift index a62e5ccc..4d78b92b 100644 --- a/Sources/HTMLKit/Abstraction/Types/UnitPoint.swift +++ b/Sources/HTMLKit/Abstraction/Types/UnitPoint.swift @@ -9,10 +9,10 @@ /// .center(UnitPoint(x: 50, y: 50)) /// } /// ``` -public struct UnitPoint { +public struct UnitPoint: Sendable { /// An enumeration of potential point formats. - public enum PointFormat { + public enum PointFormat: Sendable { /// Indicates an absolute value. case absolute From 1b5f590fa81c1be02ab17e5fbc45b7a075334bfa Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Mon, 4 May 2026 21:38:16 +0200 Subject: [PATCH 19/30] Mark the keyboardshortcut structure as sendable --- Sources/HTMLKit/Abstraction/Types/KeyboardShortcut.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/HTMLKit/Abstraction/Types/KeyboardShortcut.swift b/Sources/HTMLKit/Abstraction/Types/KeyboardShortcut.swift index fc21d075..9d6a66b8 100644 --- a/Sources/HTMLKit/Abstraction/Types/KeyboardShortcut.swift +++ b/Sources/HTMLKit/Abstraction/Types/KeyboardShortcut.swift @@ -1,8 +1,8 @@ /// A type that represents a keyboard shortcut. -public struct KeyboardShortcut { +public struct KeyboardShortcut: Sendable { /// A type thats represents an action key. - public struct ActionKey: ExpressibleByUnicodeScalarLiteral { + public struct ActionKey: ExpressibleByUnicodeScalarLiteral, Sendable { /// Represents the enter key. public static let enter = ActionKey("Enter") @@ -34,7 +34,7 @@ public struct KeyboardShortcut { } /// An enumeration of modifier keys. - public enum ModifierKey: String { + public enum ModifierKey: String, Sendable { /// Represents the alt key. case alt = "Alt" From 2145a36d39bdf7386d263e4eb51a3c438118a635 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Mon, 4 May 2026 22:39:43 +0200 Subject: [PATCH 20/30] Mark the renderer configuration class as sendable --- Sources/HTMLKitVapor/Configuration.swift | 2 +- Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/HTMLKitVapor/Configuration.swift b/Sources/HTMLKitVapor/Configuration.swift index c2064de5..efbd9d64 100644 --- a/Sources/HTMLKitVapor/Configuration.swift +++ b/Sources/HTMLKitVapor/Configuration.swift @@ -1,7 +1,7 @@ import HTMLKit /// A type that holds configuration for the renderer -public final class Configuration { +public final class Configuration: @unchecked Sendable { /// Holds the localization configuration internal var localization: HTMLKit.Localization diff --git a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift index 105122d9..0145abf8 100644 --- a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift +++ b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift @@ -50,7 +50,7 @@ extension Application { /// The key used to store the configuration in Vapor's storage internal struct ConfigurationKey: StorageKey { - public typealias Value = Configuration + internal typealias Value = Configuration } /// The configuration for the view renderer From 75934db4566d738726b3fcc6fbd70913daa2060f Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Mon, 4 May 2026 23:12:10 +0200 Subject: [PATCH 21/30] Make the environment class sendable --- Package.swift | 4 +- .../Framework/Environment/Environment.swift | 37 +++++++++---------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Package.swift b/Package.swift index e81545f7..8c9ebe37 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,8 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections.git", from: "1.1.4"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.4.3"), .package(url: "https://github.com/apple/swift-log.git", from: "1.6.3"), - .package(url: "https://github.com/vapor/vapor.git", from: "4.114.1") + .package(url: "https://github.com/vapor/vapor.git", from: "4.114.1"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.86.2"), ], targets: [ .target( @@ -33,6 +34,7 @@ let package = Package( dependencies: [ .product(name: "Collections", package: "swift-collections"), .product(name: "Logging", package: "swift-log"), + .product(name: "NIO", package: "swift-nio"), ], exclude: ["Abstraction/README.md", "Framework/README.md"], swiftSettings: [ diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift index 0e35029d..6e4256ef 100644 --- a/Sources/HTMLKit/Framework/Environment/Environment.swift +++ b/Sources/HTMLKit/Framework/Environment/Environment.swift @@ -1,10 +1,11 @@ import Foundation +import NIOConcurrencyHelpers /// A class that represents the environment /// /// The environment provides storage for various settings used by the renderer @_documentation(visibility: internal) -public final class Environment { +public final class Environment: @unchecked Sendable { /// An enumeration of possible rendering errors. public enum Errors: Error { @@ -43,42 +44,34 @@ public final class Environment { /// The storage of the environment private var storage: [AnyKeyPath: Any] + /// A lock to get sure, we are thread safe here + private var lock: NIOLock + /// Initializes the environment public init() { self.storage = [:] + self.lock = .init() } /// The current time zone of the environment public var timeZone: TimeZone? { - - get { - retrieve(for: \EnvironmentKeys.timeZone) as? TimeZone - } + return retrieve(for: \EnvironmentKeys.timeZone) as? TimeZone } /// The current calendar of the environment public var calendar: Calendar? { - - get { - retrieve(for: \EnvironmentKeys.calendar) as? Calendar - } + return retrieve(for: \EnvironmentKeys.calendar) as? Calendar } /// The current locale of the environment public var locale: Locale? { - - get { - retrieve(for: \EnvironmentKeys.locale) as? Locale - } + return retrieve(for: \EnvironmentKeys.locale) as? Locale } /// The current color scheme of the environment public var colorScheme: String? { - - get { - retrieve(for: \EnvironmentKeys.colorScheme) as? String - } + return retrieve(for: \EnvironmentKeys.colorScheme) as? String } /// Retrieves a value from environment for a given key path @@ -87,7 +80,10 @@ public final class Environment { /// /// - Returns: The value public func retrieve(for path: AnyKeyPath) -> Any? { - return storage[path] + + return self.lock.withLock { + return storage[path] + } } /// Inserts or updates a value in the environment for the given key path @@ -96,7 +92,10 @@ public final class Environment { /// - value: The value to be stored or updated /// - path: The key path that identifies where the value is stored public func upsert(_ value: T, for path: AnyKeyPath) { - storage[path] = value + + self.lock.withLock { + storage[path] = value + } } /// Resolves an environment value From 27aabeaf094eeadfbcdbb1628570c04990f6aa4f Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Tue, 5 May 2026 21:00:53 +0200 Subject: [PATCH 22/30] Mark the encoder structure as sendable --- Sources/HTMLKit/Framework/Rendering/Encoding/Encoder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Rendering/Encoding/Encoder.swift b/Sources/HTMLKit/Framework/Rendering/Encoding/Encoder.swift index fe839ae4..bc8a8625 100644 --- a/Sources/HTMLKit/Framework/Rendering/Encoding/Encoder.swift +++ b/Sources/HTMLKit/Framework/Rendering/Encoding/Encoder.swift @@ -7,7 +7,7 @@ import Foundation /// > Note: The encoder does not utilize unicode encoding for some languages, such as JS and CSS. /// > It primarily aims to preserve the code integrity and to protect against code injection within an /// > HTML context. -internal struct Encoder { +internal struct Encoder: Sendable { /// An enumeration of potential encoding mechanism internal enum Mechanism { From 7c39bd7840fe866d9e4b4e30f8712b19de8b147d Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Tue, 5 May 2026 21:01:25 +0200 Subject: [PATCH 23/30] Mark the sanitizer structure as sendable --- Sources/HTMLKit/Framework/Rendering/Encoding/Sanitizer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Rendering/Encoding/Sanitizer.swift b/Sources/HTMLKit/Framework/Rendering/Encoding/Sanitizer.swift index 441ed75d..b2b7df07 100644 --- a/Sources/HTMLKit/Framework/Rendering/Encoding/Sanitizer.swift +++ b/Sources/HTMLKit/Framework/Rendering/Encoding/Sanitizer.swift @@ -1,5 +1,5 @@ /// A type responsible for sanitizing the renderer output -internal struct Sanitizer { +internal struct Sanitizer: Sendable { /// Strip a tag from a given string /// From 4ff2a5e5ca123448ecab6e3851a08201050e519b Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Tue, 5 May 2026 21:02:35 +0200 Subject: [PATCH 24/30] Mark the renderer structure as sendable --- Sources/HTMLKit/Framework/Rendering/Renderer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift index 9ffd86cc..9353c50f 100644 --- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift +++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift @@ -19,7 +19,7 @@ import Logging /// ``` /// @_documentation(visibility: internal) -public struct Renderer { +public struct Renderer: Sendable { /// A enumeration of potential errors during rendering public enum Error: Swift.Error { From 2474c5ab65e6549b86ba5faf0f7cee28661adbbe Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Tue, 5 May 2026 21:04:39 +0200 Subject: [PATCH 25/30] Make the viewrenderer structure sendable --- Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift | 2 +- Sources/HTMLKitVapor/ViewRenderer.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift index 0145abf8..bc482a00 100644 --- a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift +++ b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift @@ -9,7 +9,7 @@ extension Application { } /// Represents the provider for Vapor - public struct HtmlKit { + public struct HtmlKit: Sendable { /// Manages environment settings public var environment: HTMLKit.Environment { diff --git a/Sources/HTMLKitVapor/ViewRenderer.swift b/Sources/HTMLKitVapor/ViewRenderer.swift index 2f6411e2..bc8be3f0 100644 --- a/Sources/HTMLKitVapor/ViewRenderer.swift +++ b/Sources/HTMLKitVapor/ViewRenderer.swift @@ -2,7 +2,7 @@ import HTMLKit import Vapor /// A type responsible for rendering views in Vapor using HTMLKit -public class ViewRenderer { +public struct ViewRenderer: Sendable { /// The event loop that the renderer operates on internal var eventLoop: EventLoop From 3b0e193fb6c246335be62400b5397a806a689396 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 8 May 2026 09:06:08 +0200 Subject: [PATCH 26/30] Mark the enumeratedlist structure as sendable --- Sources/HTMLKit/Framework/Rendering/EnumeratedList.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Rendering/EnumeratedList.swift b/Sources/HTMLKit/Framework/Rendering/EnumeratedList.swift index 88e61a85..28aa4163 100644 --- a/Sources/HTMLKit/Framework/Rendering/EnumeratedList.swift +++ b/Sources/HTMLKit/Framework/Rendering/EnumeratedList.swift @@ -1,6 +1,6 @@ /// A value type that holds a set of values along with its separator. @_documentation(visibility: internal) -public struct EnumeratedList { +public struct EnumeratedList: Sendable { /// The values within the list. internal var values: [String] From be15b665f21049bab3ff83ad409aac049462a09d Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 8 May 2026 09:06:33 +0200 Subject: [PATCH 27/30] Mark the escapecontext enum as sendable --- Sources/HTMLKit/Framework/Rendering/EscapeContext.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/HTMLKit/Framework/Rendering/EscapeContext.swift b/Sources/HTMLKit/Framework/Rendering/EscapeContext.swift index 5709d43f..22cd0a63 100644 --- a/Sources/HTMLKit/Framework/Rendering/EscapeContext.swift +++ b/Sources/HTMLKit/Framework/Rendering/EscapeContext.swift @@ -1,9 +1,9 @@ /// A companion type that informs about trusted or tainted content. @_documentation(visibility: internal) -public enum EscapeContext { +public enum EscapeContext: Sendable { /// The subcontext that describes the content. - public enum Subcontext { + public enum Subcontext: Sendable { /// Consider an HTML context. case html From 378bbd58b736989a7c5ff4aaf232efce0222a88c Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 8 May 2026 09:07:03 +0200 Subject: [PATCH 28/30] Mark the attributedata structure as sendable --- .../Framework/Primitives/Attributes/AttributeData.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/HTMLKit/Framework/Primitives/Attributes/AttributeData.swift b/Sources/HTMLKit/Framework/Primitives/Attributes/AttributeData.swift index 07f7c313..3ac3db85 100644 --- a/Sources/HTMLKit/Framework/Primitives/Attributes/AttributeData.swift +++ b/Sources/HTMLKit/Framework/Primitives/Attributes/AttributeData.swift @@ -1,9 +1,9 @@ /// A type that represents the attribute data. @_documentation(visibility: internal) -public struct AttributeData { +public struct AttributeData: Sendable { /// An enumeration of potential values. - internal enum Value { + internal enum Value: Sendable { /// A string value. case string(String) From 7bd5a60a4f227c33de46cc0793ca31643c31d97c Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 8 May 2026 09:39:43 +0200 Subject: [PATCH 29/30] Store only sendable values in the environment --- Sources/HTMLKit/Framework/Environment/Environment.swift | 6 +++--- .../HTMLKit/Framework/Environment/EnvironmentModifier.swift | 4 ++-- Sources/HTMLKit/Framework/Primitives/Elements/Element.swift | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift index 6e4256ef..bc61e19a 100644 --- a/Sources/HTMLKit/Framework/Environment/Environment.swift +++ b/Sources/HTMLKit/Framework/Environment/Environment.swift @@ -42,7 +42,7 @@ public final class Environment: @unchecked Sendable { } /// The storage of the environment - private var storage: [AnyKeyPath: Any] + private var storage: [AnyKeyPath: Sendable] /// A lock to get sure, we are thread safe here private var lock: NIOLock @@ -79,7 +79,7 @@ public final class Environment: @unchecked Sendable { /// - Parameter path: The key path used to look up the value /// /// - Returns: The value - public func retrieve(for path: AnyKeyPath) -> Any? { + public func retrieve(for path: AnyKeyPath) -> Sendable? { return self.lock.withLock { return storage[path] @@ -91,7 +91,7 @@ public final class Environment: @unchecked Sendable { /// - Parameters: /// - value: The value to be stored or updated /// - path: The key path that identifies where the value is stored - public func upsert(_ value: T, for path: AnyKeyPath) { + public func upsert(_ value: T, for path: AnyKeyPath) { self.lock.withLock { storage[path] = value diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift index 46438175..42bb00d8 100644 --- a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift +++ b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift @@ -8,7 +8,7 @@ public struct EnvironmentModifier: Content { internal let key: AnyKeyPath /// The environment value - internal let value: Any? + internal let value: Sendable? /// The sub-content internal let content: [Content] @@ -19,7 +19,7 @@ public struct EnvironmentModifier: Content { /// - key: The key path of the environment value to be modified /// - value: The new value to update the environment value with /// - content: The sub-content to be rendered - public init(key: AnyKeyPath, value: Any? = nil, content: [Content]) { + public init(key: AnyKeyPath, value: Sendable? = nil, content: [Content]) { self.key = key self.value = value diff --git a/Sources/HTMLKit/Framework/Primitives/Elements/Element.swift b/Sources/HTMLKit/Framework/Primitives/Elements/Element.swift index e678ff83..2c91db6b 100644 --- a/Sources/HTMLKit/Framework/Primitives/Elements/Element.swift +++ b/Sources/HTMLKit/Framework/Primitives/Elements/Element.swift @@ -21,7 +21,7 @@ extension Element { /// - value: The value to be stored /// /// - Returns: The environment modifier - public func environment(key: KeyPath, value: V) -> EnvironmentModifier { + public func environment(key: KeyPath, value: V) -> EnvironmentModifier { return .init(key: key, value: value, content: [self]) } @@ -30,7 +30,7 @@ extension Element { /// - Parameter object: The object to be stored /// /// - Returns: The environment modifier - public func environment(object: T) -> EnvironmentModifier { + public func environment(object: T) -> EnvironmentModifier { return .init(key: \T.self, value: object, content: [self]) } } From cffd21f59cf7aef34a2ccb80369d8a698601ff30 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Fri, 8 May 2026 09:58:28 +0200 Subject: [PATCH 30/30] Mark the environmentvalue structure as sendable for now --- Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift index 06afbc0f..2da90d90 100644 --- a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift +++ b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift @@ -4,7 +4,7 @@ import Foundation /// /// The placeholder will be evaluated and resolved by the renderer when needed. @_documentation(visibility: internal) -public struct EnvironmentValue: Content { +public struct EnvironmentValue: Content, @unchecked Sendable { /// The path of the values parent internal let parentPath: AnyKeyPath