From 3f2a36b69d23d9d2ba1f2d02c17c778f464b622c Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Sun, 18 Oct 2026 16:11:13 +0200 Subject: [PATCH 1/2] Fix the concurrent localization --- .../Framework/Environment/Environment.swift | 9 ++ Sources/HTMLKitVapor/Configuration.swift | 8 ++ .../Extensions/Vapor+HTMLKit.swift | 86 ++++++++++++++++++- Sources/HTMLKitVapor/ViewRenderer.swift | 59 ------------- Tests/HTMLKitVaporTests/ProviderTests.swift | 65 +++++++++++++- 5 files changed, 161 insertions(+), 66 deletions(-) delete mode 100644 Sources/HTMLKitVapor/ViewRenderer.swift diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift index 0dad775d..391edd7c 100644 --- a/Sources/HTMLKit/Framework/Environment/Environment.swift +++ b/Sources/HTMLKit/Framework/Environment/Environment.swift @@ -54,6 +54,15 @@ public final class Environment: @unchecked Sendable { self.lock = .init() } + /// Creates an environment. + /// + /// - Parameter environment: The environment to derive from. + public init(from environment: Environment) { + + self.storage = environment.storage + self.lock = .init() + } + /// The current time zone of the environment public var timeZone: TimeZone? { diff --git a/Sources/HTMLKitVapor/Configuration.swift b/Sources/HTMLKitVapor/Configuration.swift index efbd9d64..5be2748c 100644 --- a/Sources/HTMLKitVapor/Configuration.swift +++ b/Sources/HTMLKitVapor/Configuration.swift @@ -19,4 +19,12 @@ public final class Configuration: @unchecked Sendable { self.environment = Environment() self.features = [.escaping] } + + /// Initializes a configuration + public init(localization: Localization, environment: Environment, features: Features) { + + self.localization = localization + self.environment = environment + self.features = features + } } diff --git a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift index 2e93ecec..8ad7764e 100644 --- a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift +++ b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift @@ -82,9 +82,89 @@ extension Application { extension Request { - /// Access to the view renderer - public var htmlkit: ViewRenderer { - return .init(eventLoop: eventLoop, configuration: application.htmlkit.configuration, logger: logger) + /// Access the provider on the request. + public var htmlkit: HtmlKit { + return .init(request: self) + } + + /// A type that represents the provider for Vapor. + public struct HtmlKit: Sendable { + + /// The key used to store the configuration in Vapor's storage. + private struct ConfigurationKey: StorageKey { + + public typealias Value = Configuration + } + + /// Manage the environment settings for the request. + public var environment: HTMLKit.Environment { + + get { + configuration.environment + } + + nonmutating set { + configuration.environment = newValue + } + } + + /// The configuration for renderer. + private var configuration: Configuration { + + if let configuration = request.storage[ConfigurationKey.self] { + return configuration + } + + let configuration = Configuration(localization: request.application.htmlkit.localization, environment: HTMLKit.Environment(from: request.application.htmlkit.environment), features: request.application.htmlkit.features) + + request.storage[ConfigurationKey.self] = configuration + + return configuration + } + + /// Holds a reference to the Vapor application + private let request: Request + + /// Initializes the HTMLKit provider for a given Vapor application + /// + /// - Parameter request: The request instance from Vapor + public init(request: Request) { + + self.request = request + } + + /// Renders the given view and transforms it into a view response + /// + /// - Parameter view: The view to render + /// + /// - Returns: The view response + public func render(_ view: some HTMLKit.View) -> EventLoopFuture { + + let renderer = Renderer(localization: configuration.localization, + environment: configuration.environment, + features: configuration.features, + logger: request.logger) + + do { + + var buffer = ByteBufferAllocator().buffer(capacity: 4096) + buffer.writeString(try renderer.render(view: view)) + + return request.eventLoop.makeSucceededFuture(View(data: buffer)) + + } catch(let error) { + return request.eventLoop.makeFailedFuture(error) + } + } + + /// Renders the given view and transforms it into a view response. + /// + /// - Parameter view: The view to render. + /// + /// - Returns: The view response. + public func render(_ view: some HTMLKit.View) async throws -> Vapor.View { + return try await render(view).get() + } } } diff --git a/Sources/HTMLKitVapor/ViewRenderer.swift b/Sources/HTMLKitVapor/ViewRenderer.swift deleted file mode 100644 index c6b63bf7..00000000 --- a/Sources/HTMLKitVapor/ViewRenderer.swift +++ /dev/null @@ -1,59 +0,0 @@ -import HTMLKit -import Vapor - -/// A type responsible for rendering views in Vapor using HTMLKit -public struct ViewRenderer: Sendable { - - /// The event loop that the renderer operates on - internal let eventLoop: EventLoop - - /// The actual renderer - internal let renderer: Renderer - - /// The logger used to log all operations - private let logger: Logger - - /// Initializes the view renderer - /// - /// - Parameters: - /// - eventLoop: The event loop used for asynchronous operations - /// - configuration: The configuration for the renderer - /// - logger: The logger used to log all operations - public init(eventLoop: EventLoop, configuration: Configuration, logger: Logger) { - - self.eventLoop = eventLoop - self.renderer = Renderer(localization: configuration.localization, - environment: configuration.environment, - features: configuration.features, - logger: logger) - self.logger = logger - } - - /// Renders the given view and transforms it into a view response - /// - /// - Parameter view: The view to render - /// - /// - Returns: The view response - public func render(_ view: some HTMLKit.View) -> EventLoopFuture { - - do { - - var buffer = ByteBufferAllocator().buffer(capacity: 4096) - buffer.writeString(try renderer.render(view: view)) - - return eventLoop.makeSucceededFuture(View(data: buffer)) - - } catch(let error) { - return eventLoop.makeFailedFuture(error) - } - } - - /// Renders the given view and transforms it into a view response - /// - /// - Parameter view: The view to render - /// - /// - Returns: The view response - public func render(_ view: some HTMLKit.View) async throws -> Vapor.View { - return try await render(view).get() - } -} diff --git a/Tests/HTMLKitVaporTests/ProviderTests.swift b/Tests/HTMLKitVaporTests/ProviderTests.swift index 228dfaff..50c66623 100644 --- a/Tests/HTMLKitVaporTests/ProviderTests.swift +++ b/Tests/HTMLKitVaporTests/ProviderTests.swift @@ -246,7 +246,7 @@ struct ProviderTests { if let languages = request.headers.first(name: .acceptLanguage) { if let language = languages.components(separatedBy: ",").first { - app.htmlkit.environment.upsert(HTMLKit.Locale(tag: language), for: \EnvironmentKeys.locale) + request.htmlkit.environment.upsert(HTMLKit.Locale(tag: language), for: \EnvironmentKeys.locale) } } @@ -294,7 +294,7 @@ struct ProviderTests { if let languages = request.headers.first(name: .acceptLanguage) { if let language = languages.components(separatedBy: ",").first { - request.application.htmlkit.environment.upsert(HTMLKit.Locale(tag: language), for: \EnvironmentKeys.locale) + request.htmlkit.environment.upsert(HTMLKit.Locale(tag: language), for: \EnvironmentKeys.locale) } } @@ -344,14 +344,14 @@ struct ProviderTests { app.get("test", "de") { request async throws -> Vapor.View in - request.application.htmlkit.environment.upsert(HTMLKit.Locale(tag: "de-DE"), for: \EnvironmentKeys.locale) + request.htmlkit.environment.upsert(HTMLKit.Locale(tag: "de-DE"), for: \EnvironmentKeys.locale) return try await request.htmlkit.render(TestPage.ChildView()) } app.get("test", "fr") { request async throws -> Vapor.View in - request.application.htmlkit.environment.upsert(HTMLKit.Locale(tag: "fr"), for: \EnvironmentKeys.locale) + request.htmlkit.environment.upsert(HTMLKit.Locale(tag: "fr"), for: \EnvironmentKeys.locale) return try await request.htmlkit.render(TestPage.ChildView()) } @@ -395,6 +395,63 @@ struct ProviderTests { try await app.asyncShutdown() } + /// Tests the concurrent localization behavior based on the accept languages of the client. + @Test + func testHeaderBasedConcurrentLocalization() async throws { + + guard let source = Bundle.module.url(forResource: "Localization", withExtension: nil) else { + return + } + + 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 + + if let languages = request.headers.first(name: .acceptLanguage) { + + if let language = languages.components(separatedBy: ",").first { + request.htmlkit.environment.upsert(HTMLKit.Locale(tag: language), for: \EnvironmentKeys.locale) + } + } + + return try await request.htmlkit.render(TestPage.ChildView()) + } + + let languages = ["fr": "Bonjour le monde", "en-GB": "Hiya World", "de-DE": "Hallo Welt"] + + await withThrowingTaskGroup(of: Void.self) { group in + + for language in languages { + + group.addTask { + + try await app.test(.GET, "test", headers: ["accept-language": language.key]) { response async in + + #expect(response.status == .ok) + #expect(response.body.string == + """ + \ + \ + \ + TestPage\ + \ + \ +

\(language.value)

\ + \ + + """ + ) + } + } + } + } + + try await app.asyncShutdown() + } + /// Tests the localization behavior when the preferred language is unknown. /// /// A language is considered unknown if the locale couldn't be found (e.g. typo) or isn't set up in the first place. From ef2376ebea3d4d5e73f381d0f5eebdeed70df452 Mon Sep 17 00:00:00 2001 From: Mattes Mohr Date: Sun, 18 Oct 2026 16:49:33 +0200 Subject: [PATCH 2/2] Tidy up a bit --- .../Framework/Environment/Condition.swift | 24 ++-- .../Framework/Environment/Conditional.swift | 10 +- .../Framework/Environment/Environment.swift | 122 +++++++++--------- .../Environment/EnvironmentKeys.swift | 10 +- .../Environment/EnvironmentModifier.swift | 16 +-- .../Environment/EnvironmentObject.swift | 30 ++--- .../Environment/EnvironmentString.swift | 38 +++--- .../Environment/EnvironmentValue.swift | 30 ++--- .../Framework/Environment/Negation.swift | 16 +-- .../Framework/Environment/Nullable.swift | 2 +- .../Framework/Environment/Relation.swift | 72 +++++------ .../Framework/Environment/Sequence.swift | 12 +- .../Framework/Environment/Statement.swift | 16 +-- .../Framework/Rendering/Renderer.swift | 12 +- Sources/HTMLKitVapor/Configuration.swift | 37 ++++-- .../Extensions/Vapor+HTMLKit.swift | 66 ++++------ Tests/HTMLKitTests/SecurityTests.swift | 2 +- 17 files changed, 261 insertions(+), 254 deletions(-) diff --git a/Sources/HTMLKit/Framework/Environment/Condition.swift b/Sources/HTMLKit/Framework/Environment/Condition.swift index 5348438e..50c969e9 100644 --- a/Sources/HTMLKit/Framework/Environment/Condition.swift +++ b/Sources/HTMLKit/Framework/Environment/Condition.swift @@ -1,38 +1,38 @@ -/// A type representing a conditional that compares an environment value against another value +/// A type representing a conditional that compares an environment value against another value. @_documentation(visibility: internal) public struct Condition { /// An enumeration of potential comparison. public enum Comparison { - /// Indicates an equal comparison + /// Indicates an equal comparison. case equal - /// Indicates a not-equal comparison + /// Indicates a not-equal comparison. case unequal - /// Indicates a greater-than comparison + /// Indicates a greater-than comparison. case greater - /// Indicates a less-than comparison + /// Indicates a less-than comparison. case less } - /// The left-hand side value + /// The left-hand side value. internal let lhs: EnvironmentValue - /// The right-hand side value to test against + /// The right-hand side value to test against. internal let rhs: any Comparable - /// The comparison to perfom + /// The comparison to perfom. internal let comparison: Comparison - /// Initializes a condition + /// Creates a condition. /// /// - Parameters: - /// - lhs: The origin value - /// - rhs: The value to atest against - /// - operation: The comparison to perfom + /// - lhs: The origin value. + /// - rhs: The value to atest against. + /// - operation: The comparison to perfom. public init(lhs: EnvironmentValue, rhs: any Comparable, comparison: Comparison) { self.lhs = lhs diff --git a/Sources/HTMLKit/Framework/Environment/Conditional.swift b/Sources/HTMLKit/Framework/Environment/Conditional.swift index d8904f7b..8162e5e9 100644 --- a/Sources/HTMLKit/Framework/Environment/Conditional.swift +++ b/Sources/HTMLKit/Framework/Environment/Conditional.swift @@ -2,18 +2,18 @@ @_documentation(visibility: internal) public indirect enum Conditional { - /// Holds an optional + /// Holds an optional. case optional(EnvironmentValue) - /// Holds an relation + /// Holds a relation. case relation(Relation) - /// Holds a condition + /// Holds a condition. case condition(Condition) - /// Holds a negation + /// Holds a negation. case negation(Negation) - /// Holds a value + /// Holds a value. case value(EnvironmentValue) } diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift index 391edd7c..b5eb73c4 100644 --- a/Sources/HTMLKit/Framework/Environment/Environment.swift +++ b/Sources/HTMLKit/Framework/Environment/Environment.swift @@ -1,9 +1,9 @@ import Foundation import NIOConcurrencyHelpers -/// A class that represents the environment +/// A type that represents the environment. /// -/// The environment provides storage for various settings used by the renderer +/// The environment provides storage for various settings used by the renderer. @_documentation(visibility: internal) public final class Environment: @unchecked Sendable { @@ -63,7 +63,7 @@ public final class Environment: @unchecked Sendable { self.lock = .init() } - /// The current time zone of the environment + /// The current time zone of the environment. public var timeZone: TimeZone? { get { @@ -71,7 +71,7 @@ public final class Environment: @unchecked Sendable { } } - /// The current calendar of the environment + /// The current calendar of the environment. public var calendar: Calendar? { get { @@ -79,7 +79,7 @@ public final class Environment: @unchecked Sendable { } } - /// The current locale of the environment + /// The current locale of the environment. public var locale: Locale? { get { @@ -87,7 +87,7 @@ public final class Environment: @unchecked Sendable { } } - /// The current color scheme of the environment + /// The current color scheme of the environment. public var colorScheme: String? { get { @@ -95,11 +95,11 @@ public final class Environment: @unchecked Sendable { } } - /// Retrieves a value from environment for a given key path + /// Retrieves a value from environment for a given key path. /// - /// - Parameter path: The key path used to look up the value + /// - Parameter path: The key path used to look up the value. /// - /// - Returns: The value + /// - Returns: The value. public func retrieve(for path: AnyKeyPath) -> Any? { return lock.withLock { @@ -107,11 +107,11 @@ public final class Environment: @unchecked Sendable { } } - /// Inserts or updates a value in the environment for the given key path + /// Inserts or updates a value in the environment for the given key path. /// /// - Parameters: - /// - value: The value to be stored or updated - /// - path: The key path that identifies where the value is stored + /// - 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) { lock.withLock { @@ -119,11 +119,11 @@ public final class Environment: @unchecked Sendable { } } - /// Resolves an environment value + /// Resolves an environment value. /// - /// - Parameter value: The environment value to resolve + /// - Parameter value: The environment value to resolve. /// - /// - Returns: The resolved environment value + /// - Returns: The resolved environment value. internal func resolve(value: EnvironmentValue) throws -> Any { guard let parent = retrieve(for: value.parentPath) else { @@ -137,11 +137,11 @@ public final class Environment: @unchecked Sendable { return value } - /// Evaluates an environment value + /// Evaluates an environment value. /// - /// - Parameter value: The value to evaluate + /// - Parameter value: The value to evaluate. /// - /// - Returns: The result of evaluation + /// - Returns: The result of evaluation. internal func evaluate(value: EnvironmentValue) throws -> Bool { guard let boolValue = try resolve(value: value) as? Bool else { @@ -151,11 +151,11 @@ public final class Environment: @unchecked Sendable { return boolValue } - /// Evaluates an environment relation + /// Evaluates an environment relation. /// - /// - Parameter relation: The relation to evaluate + /// - Parameter relation: The relation to evaluate. /// - /// - Returns: The result of the evaluation + /// - Returns: The result of the evaluation. internal func evaluate(relation: Relation) throws -> Bool { switch relation.term { @@ -251,11 +251,11 @@ public final class Environment: @unchecked Sendable { } } - /// Evaluates an environment condition + /// Evaluates an environment condition. /// - /// - Parameter condition: The condition to evaluate + /// - Parameter condition: The condition to evaluate. /// - /// - Returns: The result of the evaluation + /// - Returns: The result of the evaluation. internal func evaluate(condition: Condition) throws -> Bool { let value = try resolve(value: condition.lhs) @@ -275,20 +275,20 @@ public final class Environment: @unchecked Sendable { } } - /// Evaluates an environment negation + /// Evaluates an environment negation. /// - /// - Parameter negation: The negation to evaluate + /// - Parameter negation: The negation to evaluate. /// - /// - Returns: The result of the evaluation + /// - Returns: The result of the evaluation. internal func evaluate(negation: Negation) throws -> Bool { return try !evaluate(value: negation.value) } - /// Evaluates an environment optional + /// Evaluates an environment optional. /// - /// - Parameter optional: The optional to evaluate + /// - Parameter optional: The optional to evaluate. /// - /// - Returns: The result of the evaluation + /// - Returns: The result of the evaluation. internal func evaluate(optional: EnvironmentValue) throws -> Bool { guard let optionalValue = try resolve(value: optional) as? Nullable else { @@ -305,82 +305,82 @@ public final class Environment: @unchecked Sendable { extension Environment { - /// Evaluates one condition + /// Evaluates one condition. /// /// - Parameters: - /// - condition: The condition to evaluate - /// - content: The content for the true statement + /// - condition: The condition to evaluate. + /// - content: The content for the true statement. /// - /// - Returns: A environment statement + /// - Returns: An environment statement. public static func check(_ condition: EnvironmentValue, @ContentBuilder content: () -> [Content]) -> Statement { return Statement(compound: .value(condition), first: content(), second: []) } - /// Evaluates one condition + /// Evaluates one condition. /// /// - Parameters: - /// - condition: The condition to evaluate - /// - content: The content for the true statement - /// - then: The content for the false statement + /// - condition: The condition to evaluate. + /// - content: The content for the true statement. + /// - then: The content for the false statement. /// - /// - Returns: A environment statement + /// - Returns: An environment statement. public static func check(_ condition: EnvironmentValue, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement { return Statement(compound: .value(condition), first: content(), second: then()) } - /// Evaluates one condition + /// Evaluates one condition. /// /// - Parameters: - /// - condition: The condition to evaluate - /// - content: The content for the true statement + /// - condition: The condition to evaluate. + /// - content: The content for the true statement. /// - /// - Returns: A environment statement + /// - Returns: An environment statement. public static func check(_ condition: Conditional, @ContentBuilder content: () -> [Content]) -> Statement { return Statement(compound: condition, first: content(), second: []) } - /// Evaluates one condition + /// Evaluates one condition. /// /// - Parameters: - /// - condition: The condition to evaluate - /// - content: The content for the true statement - /// - then: The content for the false statement + /// - condition: The condition to evaluate. + /// - content: The content for the true statement. + /// - then: The content for the false statement. /// - /// - Returns: A environment statement + /// - Returns: An environment statement. public static func check(_ condition: Conditional, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement { return Statement(compound: condition, first: content(), second: then()) } - /// Unwraps a optional environment value + /// Unwraps an optional environment value. /// /// - Parameters: - /// - value: The optional value to unwrap - /// - content: The content for some statement + /// - value: The optional value to unwrap. + /// - content: The content for some statement. /// - /// - Returns: A environment statement + /// - Returns: An environment statement. public static func unwrap(_ value: EnvironmentValue, @ContentBuilder content: (EnvironmentValue) -> [Content]) -> Statement { return Statement(compound: .optional(value), first: content(value), second: []) } - /// Unwraps a optional environment value + /// Unwraps a noptional environment value. /// /// - Parameters: - /// - value: The optional value to unwrap - /// - content: The content for some statement - /// - then: The content for none statement + /// - value: The optional value to unwrap. + /// - content: The content for some statement. + /// - then: The content for none statement. /// - /// - Returns: A environment statement + /// - Returns: An environment statement. public static func unwrap(_ value: EnvironmentValue, @ContentBuilder content: (EnvironmentValue) -> [Content], @ContentBuilder then: () -> [Content]) -> Statement { return Statement(compound: .optional(value), first: content(value), second: then()) } - /// Iterates through a sequence of values + /// Iterates through a sequence of values. /// /// - Parameters: - /// - sequence: The sequence to iterate over - /// - content: The content for the iteration + /// - sequence: The sequence to iterate over. + /// - content: The content for the iteration. /// - /// - Returns: A environment sequence + /// - Returns: An environment sequence. public static func loop(_ sequence: EnvironmentValue, @ContentBuilder content: (EnvironmentValue) -> [Content]) -> Sequence { return Sequence(value: sequence, content: content(sequence)) } diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift index 39c2154a..6a708485 100644 --- a/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift +++ b/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift @@ -1,19 +1,19 @@ import Foundation -/// A set of predefined environment keys +/// A set of predefined environment keys. /// /// The keys are used on the environment modifiers to configure various settings for the environment. public struct EnvironmentKeys: Hashable { - /// A key used to configure the environment's calendar + /// A key used to configure the environment's calendar. public var calendar: Calendar - /// A key used to configure the environment's time zone + /// A key used to configure the environment's time zone. public var timeZone: TimeZone - /// A key used to configure the environment's locale + /// A key used to configure the environment's locale. public var locale: Locale - /// A key used to configure the environment's color scheme + /// A key used to configure the environment's color scheme. public var colorScheme: String } diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift index 46438175..e8fabec7 100644 --- a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift +++ b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift @@ -1,24 +1,24 @@ -/// A type that holds the modification details for the environment +/// A type that holds the modification for the environment. /// /// The modifier is received by the renderer and applied to the environment. @_documentation(visibility: internal) public struct EnvironmentModifier: Content { - /// The environment key + /// The environment key. internal let key: AnyKeyPath - /// The environment value + /// The environment value. internal let value: Any? - /// The sub-content + /// The subcontent. internal let content: [Content] - /// Initializes an environment modifier + /// Creates an environment modifier. /// /// - Parameters: - /// - 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 + /// - key: The key path of the environment value to be modified. + /// - value: The new value to update the environment value with. + /// - content: The subcontent to be rendered. public init(key: AnyKeyPath, value: Any? = nil, content: [Content]) { self.key = key diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentObject.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentObject.swift index f47aa845..0946bf76 100644 --- a/Sources/HTMLKit/Framework/Environment/EnvironmentObject.swift +++ b/Sources/HTMLKit/Framework/Environment/EnvironmentObject.swift @@ -1,48 +1,48 @@ import Foundation -/// A property wrapper type to provide access to an environment object +/// A property wrapper type to provide access to an environment object. /// /// An environment object allows to read shared environment data. @frozen @propertyWrapper public struct EnvironmentObject { - /// The wrapped object + /// The wrapped object. public var wrappedValue: Wrapper - /// Initialiizes the environment object + /// Creates an environment object. public init(_ type: ObjectType.Type) { self.wrappedValue = .init() } - /// A wrapper, that holds the object informationen + /// A wrapper, that holds the object informationen. @dynamicMemberLookup public struct Wrapper { - /// The path of the parent + /// The path of the parent. internal var parent: AnyKeyPath? - /// The path of the value + /// The path of the value. internal var path: AnyKeyPath - /// Initializes the wrapper + /// Initializes the wrapper. public init() { self.path = \WrapperType.self } - /// Initializes the wrapper + /// Creates the wrapper. /// /// - Parameters: - /// - parent: The path of the parent - /// - path: The path of the value + /// - parent: The path of the parent. + /// - path: The path of the value. internal init(parent: AnyKeyPath, path: AnyKeyPath) { self.parent = parent self.path = path } - /// Accesses a wrapped value for a given key path dynamically + /// Accesses a wrapped value for a given key path dynamically. /// - /// - Returns: An environment value + /// - Returns: An environment value. public subscript(dynamicMember member: KeyPath) -> EnvironmentValue { guard let newPath = self.path.appending(path: member) else { @@ -56,9 +56,9 @@ import Foundation return .init(parentPath: self.path, valuePath: newPath) } - /// Accesses a wrapped model object for a given key path dynamically + /// Accesses a wrapped model object for a given key path dynamically. /// - /// - Returns: An environment value + /// - Returns: An environment value. public subscript(dynamicMember member: KeyPath) -> Wrapper where T: ViewModel { guard let newPath = self.path.appending(path: member) else { @@ -70,5 +70,5 @@ import Foundation } } -/// A type, that defines a view model +/// A type, that defines a view model. public protocol ViewModel: Encodable {} diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentString.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentString.swift index c80b5840..d97a5739 100644 --- a/Sources/HTMLKit/Framework/Environment/EnvironmentString.swift +++ b/Sources/HTMLKit/Framework/Environment/EnvironmentString.swift @@ -1,31 +1,35 @@ -/// A type that represents an environment string +/// A type that represents an environment string. @_documentation(visibility: internal) -public struct EnvironmentString: ExpressibleByStringLiteral, ExpressibleByStringInterpolation, Content { +public struct EnvironmentString: Content { internal var values: [Content] = [] +} + +extension EnvironmentString: ExpressibleByStringLiteral { public init(stringLiteral: String) { self.values.append(stringLiteral) } +} + +extension EnvironmentString: ExpressibleByStringInterpolation { - public init(stringInterpolation: StringInterpolation) { + public init(stringInterpolation: EnvironmentString) { self.values.append(contentsOf: stringInterpolation.values) } +} + +extension EnvironmentString: StringInterpolationProtocol { + + public init(literalCapacity: Int, interpolationCount: Int) { + values.reserveCapacity(interpolationCount) + } - public struct StringInterpolation: StringInterpolationProtocol { - - internal var values: [Content] = [] + public mutating func appendLiteral(_ literal: String) { + values.append(literal) + } - public init(literalCapacity: Int, interpolationCount: Int) { - values.reserveCapacity(interpolationCount) - } - - public mutating func appendLiteral(_ literal: String) { - values.append(literal) - } - - public mutating func appendInterpolation(_ env: EnvironmentValue) { - values.append(env) - } + public mutating func appendInterpolation(_ value: EnvironmentValue) { + values.append(value) } } diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift index 06afbc0f..ffe5840c 100644 --- a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift +++ b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift @@ -1,22 +1,22 @@ import Foundation -/// A type that serves as a placeholder for an environment value +/// A type that serves as a placeholder for an environment value. /// /// The placeholder will be evaluated and resolved by the renderer when needed. @_documentation(visibility: internal) public struct EnvironmentValue: Content { - /// The path of the values parent + /// The path of the values parent. internal let parentPath: AnyKeyPath - /// The path of the value + /// The path of the value. internal let valuePath: AnyKeyPath - /// Initializes a environment value + /// Creates an environment value. /// /// - Parameters: - /// - parentPath: The key path of the parent - /// - valuePath: The key path of the value + /// - parentPath: The key path of the parent. + /// - valuePath: The key path of the value. internal init(parentPath: AnyKeyPath, valuePath: AnyKeyPath) { self.parentPath = parentPath @@ -26,35 +26,35 @@ public struct EnvironmentValue: Content { extension EnvironmentValue { - /// Concat environment value with environment value + /// Concat environment value with environment value. public static func + (lhs: Content, rhs: Self) -> Content { return [lhs, rhs] } - /// Compare an environment value with another comparable value + /// Compare an environment value with another comparable value. /// - /// Makes an unequal evaluation + /// Makes an unequal evaluation. public static func != (lhs: Self, rhs: some Comparable) -> Conditional { return .condition(Condition(lhs: lhs, rhs: rhs, comparison: .unequal)) } - /// Compare an environment value with another comparable value + /// Compare an environment value with another comparable value. /// - /// Makes an equal evaluation + /// Makes an equal evaluation. public static func == (lhs: Self, rhs: some Comparable) -> Conditional { return .condition(Condition(lhs: lhs, rhs: rhs, comparison: .equal)) } - /// Compare an environment value with another comparable value + /// Compare an environment value with another comparable value. /// - /// Makes an less than evaluation + /// Makes an less than evaluation. public static func < (lhs: Self, rhs: some Comparable) -> Conditional { return .condition(Condition(lhs: lhs, rhs: rhs, comparison: .less)) } - /// Compare an environment value with another comparable value + /// Compare an environment value with another comparable value. /// - /// Makes an greater than evaluation + /// Makes an greater than evaluation. public static func > (lhs: Self, rhs: some Comparable) -> Conditional { return .condition(Condition(lhs: lhs, rhs: rhs, comparison: .greater)) } diff --git a/Sources/HTMLKit/Framework/Environment/Negation.swift b/Sources/HTMLKit/Framework/Environment/Negation.swift index 042cad6f..fd2a5f9f 100644 --- a/Sources/HTMLKit/Framework/Environment/Negation.swift +++ b/Sources/HTMLKit/Framework/Environment/Negation.swift @@ -1,20 +1,20 @@ -/// A type thats represents an invert conditional +/// A type that represents an invert conditional. @_documentation(visibility: internal) public struct Negation { - /// The left-hand side conditional + /// The left-hand side conditional. internal let value: EnvironmentValue - /// Initializes the negation + /// Creates a negation. /// - /// - Parameter lhs: The conditional to evaluate - public init(value: EnvironmentValue) { + /// - Parameter value: The value to evaluate. + internal init(value: EnvironmentValue) { self.value = value } } -/// Creates a invert conditional +/// Creates an invert conditional. /// /// ```swift /// Environment.when(!value) { @@ -22,9 +22,9 @@ public struct Negation { /// ``` /// /// - Parameters: -/// - lhs: The left-hand side conditional +/// - lhs: The left-hand side conditional. /// -/// - Returns: A invert conditional +/// - Returns: An invert conditional. public prefix func ! (value: EnvironmentValue) -> Conditional { return .negation(Negation(value: value)) } diff --git a/Sources/HTMLKit/Framework/Environment/Nullable.swift b/Sources/HTMLKit/Framework/Environment/Nullable.swift index 4ef47815..7f6e4c60 100644 --- a/Sources/HTMLKit/Framework/Environment/Nullable.swift +++ b/Sources/HTMLKit/Framework/Environment/Nullable.swift @@ -1,4 +1,4 @@ -/// A type that represent a nullable value. +/// A type that represents a nullable value. /// /// > Note: This protocol is intended as a temporary workaround. @_documentation(visibility: internal) diff --git a/Sources/HTMLKit/Framework/Environment/Relation.swift b/Sources/HTMLKit/Framework/Environment/Relation.swift index e67b0554..b6394e7b 100644 --- a/Sources/HTMLKit/Framework/Environment/Relation.swift +++ b/Sources/HTMLKit/Framework/Environment/Relation.swift @@ -1,36 +1,36 @@ -/// A type representing the logical relation between two conditionals +/// A type representing the logical relation between two conditionals. @_documentation(visibility: internal) public struct Relation { /// An enumeration of potential logical terms. public enum Term { - /// Indicates a conjunction + /// Indicates a conjunction. /// - /// All conditions must be true for the relation to be true + /// All conditions must be true for the relation to be true. case conjunction - /// Indicates a disjunction + /// Indicates a disjunction. /// - /// One condition must be at least true for the relation to be true + /// One condition must be at least true for the relation to be true. case disjunction } - /// The logical term specifying the relation + /// The logical term specifying the relation. internal let term: Term - /// The left-hand side conditional + /// The left-hand side conditional. internal let lhs: Conditional - /// The right-hand side conditional + /// The right-hand side conditional. internal let rhs: Conditional - /// Initializes a relation + /// Creates a relation. /// /// - Parameters: - /// - term: The term on which the relation acts on - /// - lhs: The left-hand side conditional - /// - rhs: The right-hand side conditional to test against + /// - term: The term on which the relation acts on. + /// - lhs: The left-hand side conditional. + /// - rhs: The right-hand side conditional to test against. public init(term: Term, lhs: Conditional, rhs: Conditional) { self.term = term @@ -39,7 +39,7 @@ public struct Relation { } } -/// Creates a conjunctional relation between two conditionals +/// Creates a conjunctional relation between two conditionals. /// /// ```swift /// Environment.when(value > 0 && value) { @@ -47,15 +47,15 @@ public struct Relation { /// ``` /// /// - Parameters: -/// - lhs: The left-hand side conditional -/// - rhs: An environment value +/// - lhs: The left-hand side conditional. +/// - rhs: An environment value. /// -/// - Returns: A conjunctional relation +/// - Returns: A conjunctional relation. public func && (lhs: Conditional, rhs: EnvironmentValue) -> Conditional { return .relation(Relation(term: .conjunction, lhs: lhs, rhs: .value(rhs))) } -/// Creates a conjunctional relation between two conditionals +/// Creates a conjunctional relation between two conditionals. /// /// ```swift /// Environment.when(value && value > 0) { @@ -63,16 +63,16 @@ public func && (lhs: Conditional, rhs: EnvironmentValue) -> Conditional { /// ``` /// /// - Parameters: -/// - lhs: The left-hand side conditional -/// - rhs: An environment value +/// - lhs: The left-hand side conditional. +/// - rhs: An environment value. /// -/// - Returns: A conjunctional relation +/// - Returns: A conjunctional relation. public func && (lhs: EnvironmentValue, rhs: Conditional) -> Conditional { return .relation(Relation(term: .conjunction, lhs: .value(lhs), rhs: rhs)) } -/// Creates a conjunctional relation between two conditionals +/// Creates a conjunctional relation between two conditionals. /// /// ```swift /// Environment.when(value > 0 && value < 2) { @@ -80,15 +80,15 @@ public func && (lhs: EnvironmentValue, rhs: Conditional) -> Conditional { /// ``` /// /// - Parameters: -/// - lhs: The left-hand side conditional -/// - rhs: The right-hand side conditional +/// - lhs: The left-hand side conditional. +/// - rhs: The right-hand side conditional. /// -/// - Returns: A conjunctional relation +/// - Returns: A conjunctional relation. public func && (lhs: Conditional, rhs: Conditional) -> Conditional { return .relation(Relation(term: .conjunction, lhs: lhs, rhs: rhs)) } -/// Creates a disjunctional relation between two conditionals +/// Creates a disjunctional relation between two conditionals. /// /// ```swift /// Environment.when(value > 0 || value) { @@ -96,15 +96,15 @@ public func && (lhs: Conditional, rhs: Conditional) -> Conditional { /// ``` /// /// - Parameters: -/// - lhs: The left-hand side conditional -/// - rhs: The right-hand side conditional +/// - lhs: The left-hand side conditional. +/// - rhs: The right-hand side conditional. /// -/// - Returns: A disjunctional relation +/// - Returns: A disjunctional relation. public func || (lhs: Conditional, rhs: EnvironmentValue) -> Conditional { return .relation(Relation(term: .disjunction, lhs: lhs, rhs: .value(rhs))) } -/// Creates a disjunctional relation between two conditionals +/// Creates a disjunctional relation between two conditionals. /// /// ```swift /// Environment.when(value || value > 0) { @@ -112,15 +112,15 @@ public func || (lhs: Conditional, rhs: EnvironmentValue) -> Conditional { /// ``` /// /// - Parameters: -/// - lhs: The left-hand side conditional -/// - rhs: The right-hand side conditional +/// - lhs: The left-hand side conditional. +/// - rhs: The right-hand side conditional. /// -/// - Returns: A disjunctional relation +/// - Returns: A disjunctional relation. public func || (lhs: EnvironmentValue, rhs: Conditional) -> Conditional { return .relation(Relation(term: .disjunction, lhs: .value(lhs), rhs: rhs)) } -/// Creates a disjunctional relation between two conditionals +/// Creates a disjunctional relation between two conditionals. /// /// ```swift /// Environment.when(value > 0 || value < 2) { @@ -128,10 +128,10 @@ public func || (lhs: EnvironmentValue, rhs: Conditional) -> Conditional { /// ``` /// /// - Parameters: -/// - lhs: The left-hand side conditional -/// - rhs: The right-hand side conditional +/// - lhs: The left-hand side conditional. +/// - rhs: The right-hand side conditional. /// -/// - Returns: A disjunctional relation +/// - Returns: A disjunctional relation. public func || (lhs: Conditional, rhs: Conditional) -> Conditional { return .relation(Relation(term: .disjunction, lhs: lhs, rhs: rhs)) } diff --git a/Sources/HTMLKit/Framework/Environment/Sequence.swift b/Sources/HTMLKit/Framework/Environment/Sequence.swift index f591ec81..3c66bb94 100644 --- a/Sources/HTMLKit/Framework/Environment/Sequence.swift +++ b/Sources/HTMLKit/Framework/Environment/Sequence.swift @@ -1,18 +1,18 @@ -/// A type representing a loop rendering content within the environment +/// A type representing a loop rendering content within the environment. @_documentation(visibility: internal) public struct Sequence: GlobalElement { - /// The environment value of the sequence + /// The environment value of the sequence. internal let value: EnvironmentValue - /// The accumulated content + /// The accumulated content. internal let content: [Content] - /// Initializes a loop + /// Creates a sequence. /// /// - Parameters: - /// - value: The environment value to retrieve the sequence from - /// - content: The content to render for each item in the sequence + /// - value: The environment value to retrieve the sequence from. + /// - content: The content to render for each item in the sequence. public init(value: EnvironmentValue, content: [Content]) { self.value = value diff --git a/Sources/HTMLKit/Framework/Environment/Statement.swift b/Sources/HTMLKit/Framework/Environment/Statement.swift index 2db7286e..1c1f9dc6 100644 --- a/Sources/HTMLKit/Framework/Environment/Statement.swift +++ b/Sources/HTMLKit/Framework/Environment/Statement.swift @@ -1,22 +1,22 @@ -/// A type representing a conditional block within the environment +/// A type representing a conditional block within the environment. @_documentation(visibility: internal) public struct Statement: GlobalElement { - /// The compound condition + /// The compound condition. internal let compound: Conditional - /// The first statement + /// The first statement. internal let first: [Content] - /// The second statement + /// The second statement. internal let second: [Content] - /// Initializes a statement + /// Creates a statement. /// /// - Parameters: - /// - compound: The compound of conditionals - /// - first: The statement to execute if conditionals are true - /// - second: The statement to execute if conditionals are false + /// - compound: The compound of conditionals. + /// - first: The statement to execute if conditionals are true. + /// - second: The statement to execute if conditionals are false. public init(compound: Conditional, first: [Content], second: [Content]) { self.compound = compound diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift index 0354e2de..8873f64e 100644 --- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift +++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift @@ -353,7 +353,7 @@ public struct Renderer: Sendable { } } - /// Renders a environment modifier + /// Renders an environment modifier /// /// - Parameter modifier: The modifier to apply to /// @@ -367,7 +367,7 @@ public struct Renderer: Sendable { try render(contents: modifier.content, on: &result, within: context) } - /// Renders a environment value + /// Renders an environment value /// /// - Parameter value: The environment value to resolve /// @@ -410,7 +410,7 @@ public struct Renderer: Sendable { } } - /// Renders a environment value + /// Renders an environment value /// /// - Parameter value: The environment value to resolve /// @@ -453,7 +453,7 @@ public struct Renderer: Sendable { } } - /// Renders a environment statement + /// Renders an environment statement /// /// - Parameter statement: The statement to resolve /// @@ -574,7 +574,7 @@ public struct Renderer: Sendable { } } - /// Renders a environment string + /// Renders an environment string /// /// - Parameter envstring: The string to render /// @@ -740,7 +740,7 @@ public struct Renderer: Sendable { result += "" } - /// Renders an environment string within a environment loop + /// Renders an environment string within an environment loop /// /// - Parameters: /// - envstring: The environment string to render diff --git a/Sources/HTMLKitVapor/Configuration.swift b/Sources/HTMLKitVapor/Configuration.swift index 5be2748c..fc189c1a 100644 --- a/Sources/HTMLKitVapor/Configuration.swift +++ b/Sources/HTMLKitVapor/Configuration.swift @@ -1,30 +1,45 @@ import HTMLKit +import Vapor -/// A type that holds configuration for the renderer -public final class Configuration: @unchecked Sendable { +/// A type that holds configuration for the renderer. +internal final class Configuration: @unchecked Sendable { - /// Holds the localization configuration - internal var localization: HTMLKit.Localization + /// Holds the localization configuration. + internal var localization: Localization - /// Holds the environment configuration + /// Holds the environment configuration. internal var environment: HTMLKit.Environment - /// Holds the enabled features - internal var features: HTMLKit.Features + /// Holds the enabled features. + internal var features: Features - /// Initializes a configuration - public init() { + /// Create a configuration. + internal init() { self.localization = Localization() self.environment = Environment() self.features = [.escaping] } - /// Initializes a configuration - public init(localization: Localization, environment: Environment, features: Features) { + /// Create a configuration. + /// + /// - Parameters: + /// - localization: The localization configuration to hold. + /// - environment: The environment configuration to hold. + /// - features: The enabled features to hold. + internal init(localization: Localization, environment: HTMLKit.Environment, features: Features) { self.localization = localization self.environment = environment self.features = features } } + +extension Configuration { + + /// The key used to store the configuration in the storage. + internal struct StorageKey: Vapor.StorageKey { + + internal typealias Value = Configuration + } +} diff --git a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift index 8ad7764e..69fa0dd9 100644 --- a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift +++ b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift @@ -3,15 +3,15 @@ import Vapor extension Application { - /// Provides access to the provider for the application + /// Access the provider through the application. public var htmlkit: HtmlKit { return .init(application: self) } - /// Represents the provider for Vapor + /// A type that represents the provider. public struct HtmlKit: Sendable { - /// Manages environment settings + /// Manage the environment for the application. public var environment: HTMLKit.Environment { get { @@ -23,7 +23,7 @@ extension Application { } } - /// Manages localization settings + /// Manage the localization for the application. public var localization: HTMLKit.Localization { get { @@ -35,7 +35,7 @@ extension Application { } } - /// Manages feature flags + /// Manage the features for the application. public var features: HTMLKit.Features { get { @@ -47,32 +47,26 @@ extension Application { } } - /// The key used to store the configuration in Vapor's storage - private struct ConfigurationKey: StorageKey { - - public typealias Value = Configuration - } - - /// The configuration for the view renderer - internal var configuration: Configuration { + /// The configuration for the renderer. + private var configuration: Configuration { - if let configuration = application.storage[ConfigurationKey.self] { + if let configuration = application.storage[Configuration.StorageKey.self] { return configuration } let configuration = Configuration() - application.storage[ConfigurationKey.self] = configuration + application.storage[Configuration.StorageKey.self] = configuration return configuration } - /// Holds a reference to the Vapor application - internal let application: Application + /// Holds a reference to the application. + private let application: Application - /// Initializes the HTMLKit provider for a given Vapor application + /// Create the provider. /// - /// - Parameter application: The application instance from Vapor + /// - Parameter application: The application to provide for. public init(application: Application) { self.application = application @@ -82,21 +76,15 @@ extension Application { extension Request { - /// Access the provider on the request. + /// Access the provider through the request. public var htmlkit: HtmlKit { return .init(request: self) } - /// A type that represents the provider for Vapor. + /// A type that represents the provider. public struct HtmlKit: Sendable { - /// The key used to store the configuration in Vapor's storage. - private struct ConfigurationKey: StorageKey { - - public typealias Value = Configuration - } - - /// Manage the environment settings for the request. + /// Manage the environment for the request. public var environment: HTMLKit.Environment { get { @@ -108,36 +96,36 @@ extension Request { } } - /// The configuration for renderer. + /// The configuration for the renderer. private var configuration: Configuration { - if let configuration = request.storage[ConfigurationKey.self] { + if let configuration = request.storage[Configuration.StorageKey.self] { return configuration } let configuration = Configuration(localization: request.application.htmlkit.localization, environment: HTMLKit.Environment(from: request.application.htmlkit.environment), features: request.application.htmlkit.features) - request.storage[ConfigurationKey.self] = configuration + request.storage[Configuration.StorageKey.self] = configuration return configuration } - /// Holds a reference to the Vapor application + /// Holds a reference to the request. private let request: Request - /// Initializes the HTMLKit provider for a given Vapor application + /// Create the provider. /// - /// - Parameter request: The request instance from Vapor + /// - Parameter request: The request to provide for. public init(request: Request) { self.request = request } - /// Renders the given view and transforms it into a view response + /// Render the view and transform it into a response. /// - /// - Parameter view: The view to render + /// - Parameter view: The view to render. /// - /// - Returns: The view response + /// - Returns: The response. public func render(_ view: some HTMLKit.View) -> EventLoopFuture { let renderer = Renderer(localization: configuration.localization, @@ -157,11 +145,11 @@ extension Request { } } - /// Renders the given view and transforms it into a view response. + /// Render the view and transform it into a response. /// /// - Parameter view: The view to render. /// - /// - Returns: The view response. + /// - Returns: The response. public func render(_ view: some HTMLKit.View) async throws -> Vapor.View { return try await render(view).get() } diff --git a/Tests/HTMLKitTests/SecurityTests.swift b/Tests/HTMLKitTests/SecurityTests.swift index 5fd23c0b..da196f8e 100644 --- a/Tests/HTMLKitTests/SecurityTests.swift +++ b/Tests/HTMLKitTests/SecurityTests.swift @@ -42,7 +42,7 @@ struct SecurityTests { ) } - /// Tests the escaping within a environment context. + /// Tests the escaping within an environment context. /// /// The renderer is expected to proceed as in an html context. @Test