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 diff --git a/Package.swift b/Package.swift index c5eba2d0..e81545f7 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version:5.10 import PackageDescription @@ -34,7 +34,10 @@ let package = Package( .product(name: "Collections", package: "swift-collections"), .product(name: "Logging", package: "swift-log"), ], - exclude: ["Abstraction/README.md", "Framework/README.md"] + exclude: ["Abstraction/README.md", "Framework/README.md"], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency=complete") + ] ), .target( name: "HTMLKitConverter", @@ -56,6 +59,9 @@ let package = Package( dependencies: [ .target(name: "HTMLKit"), .product(name: "Vapor", package: "vapor"), + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency=complete") ] ), .target( 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) 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" 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) } 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 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 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 diff --git a/Sources/HTMLKit/Framework/Rendering/Features.swift b/Sources/HTMLKit/Framework/Rendering/Features.swift index 550a6957..f8106a4f 100644 --- a/Sources/HTMLKit/Framework/Rendering/Features.swift +++ b/Sources/HTMLKit/Framework/Rendering/Features.swift @@ -1,7 +1,7 @@ /// An option set of different features /// /// The feature set provides the flexibility to enable experimental features if desired. -public struct Features: Swift.OptionSet { +public struct Features: Swift.OptionSet, Sendable { public var rawValue: Int diff --git a/Sources/HTMLKit/Framework/Rendering/Markdown/Markdown.swift b/Sources/HTMLKit/Framework/Rendering/Markdown/Markdown.swift index 49cf8cfc..073e430c 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: "*_~[`") 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() } }