diff --git a/Sources/HTMLKit/Framework/Rendering/Encoder.swift b/Sources/HTMLKit/Framework/Rendering/Encoder.swift new file mode 100644 index 00000000..9295bd6e --- /dev/null +++ b/Sources/HTMLKit/Framework/Rendering/Encoder.swift @@ -0,0 +1,79 @@ +import Foundation + +/// A type responsible for encoding characters like `<`, `>`, `&` etc. to their safe equivalents +internal struct Encoder { + + /// A enumeration of potential encoding mechanism + internal enum Mechanism { + + /// Encodes characters in a attribute + case attribute + + /// Encodes characters in a entity + case entity + + /// The characters to replace based on the mechanism + var characters: Set { + + switch self { + case .attribute: + return ["&", "'", "\""] + + case .entity: + return ["<", ">"] + } + } + } + + /// A mapping table of characters with their corresponding replacements + private var characterTable: [Unicode.Scalar: String] { + return [ + "&": "&", + "<": "<", + ">": ">", + "\"": """, + "'": "'", + ] + } + + /// Encodes a string into a safe equivalent + /// + /// - Parameters: + /// - string: The string to encode + /// - mechansim: The mechnism to use + /// + /// - Returns: The encoded string + internal func encode(_ string: String, as mechansim: Mechanism) -> String { + return replace(set: mechansim.characters, on: string) + } + + /// Replaces occurrences of characters in a string with their corresponding replacements + /// + /// - Parameters: + /// - set: The set of character to check against + /// - string: The string where replacements will be made + /// + /// - Returns: The replaced string + private func replace(set: Set, on string: String) -> String { + + var result = "" + + for scalar in string.unicodeScalars { + + if set.contains(scalar) { + + if let replacement = characterTable[scalar] { + result.append(contentsOf: replacement) + + } else { + result.append(contentsOf: String(scalar)) + } + + } else { + result.append(contentsOf: String(scalar)) + } + } + + return result + } +} diff --git a/Sources/HTMLKit/Framework/Rendering/Features.swift b/Sources/HTMLKit/Framework/Rendering/Features.swift index fc40de09..550a6957 100644 --- a/Sources/HTMLKit/Framework/Rendering/Features.swift +++ b/Sources/HTMLKit/Framework/Rendering/Features.swift @@ -1,10 +1,17 @@ -/// An option set of features. +/// An option set of different features +/// +/// The feature set provides the flexibility to enable experimental features if desired. public struct Features: Swift.OptionSet { public var rawValue: Int + /// A flag that indicates whether the renderer should use markdown public static let markdown = Features(rawValue: 1 << 0) + /// A flag that indicates whether the renderer should encode input + public static let escaping = Features(rawValue: 1 << 1) + + /// Initializes the feature set public init(rawValue: Int) { self.rawValue = rawValue } diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift index 503f28c6..3de6b7ad 100644 --- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift +++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift @@ -1,49 +1,91 @@ -/* - Abstract: - The file contains the renderer. The renderer runs through the content and transforms it into string output. - */ - import Foundation import OrderedCollections import Logging +/// The renderer responsible for processing content and transforming it into a string representation +/// +/// ```swift +/// Html { +/// Head { +/// Title { +/// "Lorem ipsum..." +/// } +/// } +/// } +/// ``` +/// +/// ```html +/// Lorem ipsum... +/// ``` +/// @_documentation(visibility: internal) -public final class Renderer { +public struct Renderer { - /// The context environment + /// A enumeration of potential errors during rendering + public enum Error: Swift.Error { + + /// Indicates a unknown conten type + case unknownContentType + + /// Indicates a unknown value type + case unknownValueType + + /// Returns a description about the failure reason + public var description: String { + + switch self { + case .unknownContentType: + return "The type of the content could not be determined." + + case .unknownValueType: + return "The type of the value could not be determined." + } + } + } + + /// The context environment used during rendering private var environment: Environment /// The localization configuration private var localization: Localization? - /// The security configuration - private var security: Security - /// The markdown parser private var markdown: Markdown - /// The feature flag used to manage the visibility of new and untested features. + /// The feature flag used to manage the visibility of new and untested features private var features: Features /// The logger used to log all operations private var logger: Logger + + /// The encoder used to encode html entities + private var encoder: Encoder - /// Initiates the renderer. - public init(localization: Localization? = nil, + /// Initializes the renderer + /// + /// - Parameters: + /// - localization: The localization + /// - environment: The environment + /// - security: The security configuration + /// - features: The feature set + public init(localization: Localization? = nil, environment: Environment = Environment(), - security: Security = Security(), - features: Features = [], + features: Features = [.escaping], logger: Logger = Logger(label: "HTMLKit")) { self.localization = localization self.environment = environment - self.security = security self.markdown = Markdown() self.features = features self.logger = logger + self.encoder = Encoder() } - /// Renders a view and transforms it into a string representation. + /// Renders the provided view + /// + /// - Parameter view: The view to render + /// + /// - Returns: A string representation public func render(view: some View) throws -> String { var result = "" @@ -55,84 +97,100 @@ public final class Renderer { return result } - /// Reads the view content and transforms it. + /// Process the view content + /// + /// - Parameter contents: The content to process + /// + /// - Returns: A string representation of the content private func render(contents: [Content]) throws -> String { var result = "" for content in contents { - if let contents = content as? [Content] { - result += try render(contents: contents) - } - - if let element = content as? (any View) { - result += try render(view: element) - } - - if let element = content as? (any ContentNode) { + switch content { + case let content as [Content]: + result += try render(contents: content) + + case let view as View: + result += try render(view: view) + + case let element as any ContentNode: result += try render(element: element) - } - - if let element = content as? (any EmptyNode) { + + case let element as EmptyNode: result += try render(element: element) - } - - if let element = content as? (any DocumentNode) { + + case let element as DocumentNode: result += render(element: element) - } - - if let element = content as? (any CommentNode) { + + case let element as CommentNode: result += render(element: element) - } - - if let element = content as? (any CustomNode) { + + case let element as any CustomNode: result += try render(element: element) - } - - if let string = content as? LocalizedString { - result += try render(localized: string) - } - - if let modifier = content as? EnvironmentModifier { + + case let modifier as EnvironmentModifier: result += try render(modifier: modifier) - } - - if let value = content as? EnvironmentValue { - result += escape(content: try render(value: value)) - } - - if let statement = content as? Statement { + + case let value as EnvironmentValue: + result += escape(content: try render(envvalue: value)) + + case let statement as Statement: result += try render(statement: statement) - } - - if let loop = content as? Sequence { - result += try render(loop: loop) - } - - if let string = content as? MarkdownString { + + case let sequence as Sequence: + result += try render(loop: sequence) + + case let string as LocalizedString: + result += try render(localized: string) + + case let string as MarkdownString: if !features.contains(.markdown) { result += escape(content: string.raw) } else { - result += try render(markdown: string) + result += try render(markstring: string) } - } - - if let envstring = content as? EnvironmentString { - result += try render(envstring: envstring) - } - - if let element = content as? String { - result += escape(content: element) + + case let string as EnvironmentString: + result += try render(envstring: string) + + case let doubleValue as Double: + result += String(doubleValue) + + case let floatValue as Float: + result += String(floatValue) + + case let intValue as Int: + result += String(intValue) + + case let stringValue as String: + result += escape(content: stringValue) + + case let date as Date: + + let formatter = DateFormatter() + formatter.timeZone = environment.timeZone ?? TimeZone.current + formatter.dateStyle = .medium + formatter.timeStyle = .short + + result = formatter.string(from: date) + + default: + throw Error.unknownContentType } } return result } - /// Renders a content element. A content element usually has descendants, which need to be rendered as well. + /// Renders a content element + /// + /// - Parameter element: The element to transform + /// + /// - Returns: The string representation private func render(element: some ContentNode) throws -> String { var result = "" @@ -146,75 +204,7 @@ public final class Renderer { result += ">" if let contents = element.content as? [Content] { - - for content in contents { - - if let contents = content as? [Content] { - result += try render(contents: contents) - } - - if let element = content as? (any View) { - result += try render(view: element) - } - - if let element = content as? (any ContentNode) { - result += try render(element: element) - } - - if let element = content as? (any EmptyNode) { - result += try render(element: element) - } - - if let element = content as? (any DocumentNode) { - result += render(element: element) - } - - if let element = content as? (any CommentNode) { - result += render(element: element) - } - - if let element = content as? (any CustomNode) { - result += try render(element: element) - } - - if let string = content as? LocalizedString { - result += try render(localized: string) - } - - if let modifier = content as? EnvironmentModifier { - result += try render(modifier: modifier) - } - - if let value = content as? EnvironmentValue { - result += escape(content: try render(value: value)) - } - - if let loop = content as? Sequence { - result += try render(loop: loop) - } - - if let statement = content as? Statement { - result += try render(statement: statement) - } - - if let string = content as? MarkdownString { - - if !features.contains(.markdown) { - result += escape(content: string.raw) - - } else { - result += try render(markdown: string) - } - } - - if let envstring = content as? EnvironmentString { - result += try render(envstring: envstring) - } - - if let element = content as? String { - result += escape(content: element) - } - } + result += try render(contents: contents) } result += "" @@ -222,7 +212,11 @@ public final class Renderer { return result } - /// Renders a empty element. An empty element has no descendants. + /// Renders a empty element + /// + /// - Parameter element: The element to transform + /// + /// - Returns: The string representation private func render(element: some EmptyNode) throws -> String { var result = "" @@ -238,7 +232,11 @@ public final class Renderer { return result } - /// Renders a document element. The document element holds the metadata. + /// Renders a document element + /// + /// - Parameter element: The element to transform + /// + /// - Returns: The string representation private func render(element: some DocumentNode) -> String { var result = "" @@ -252,7 +250,11 @@ public final class Renderer { return result } - /// Renders a comment element. + /// Renders a comment element + /// + /// - Parameter element: The element to transform + /// + /// - Returns: The string representation private func render(element: some CommentNode) -> String { var result = "" @@ -266,7 +268,11 @@ public final class Renderer { return result } - /// Renders a custom element. + /// Renders a custom element + /// + /// - Parameter element: The element to transform + /// + /// - Returns: The string representation private func render(element: some CustomNode) throws -> String { var result = "" @@ -280,71 +286,7 @@ public final class Renderer { result += ">" if let contents = element.content as? [Content] { - - for content in contents { - - if let contents = content as? [Content] { - result += try render(contents: contents) - } - - if let element = content as? (any View) { - result += try render(view: element) - } - - if let element = content as? (any ContentNode) { - result += try render(element: element) - } - - if let element = content as? (any EmptyNode) { - result += try render(element: element) - } - - if let element = content as? (any DocumentNode) { - result += render(element: element) - } - - if let element = content as? (any CommentNode) { - result += render(element: element) - } - - if let element = content as? (any CustomNode) { - result += try render(element: element) - } - - if let string = content as? LocalizedString { - result += try render(localized: string) - } - - if let value = content as? EnvironmentValue { - result += escape(content: try render(value: value)) - } - - if let statement = content as? Statement { - result += try render(statement: statement) - } - - if let loop = content as? Sequence { - result += try render(loop: loop) - } - - if let string = content as? MarkdownString { - - if !features.contains(.markdown) { - result += escape(content: string.raw) - - } else { - result += try render(markdown: string) - } - } - - if let envstring = content as? EnvironmentString { - result += try render(envstring: envstring) - } - - if let element = content as? String { - result += escape(content: element) - } - } + result += try render(contents: contents) } result += "" @@ -352,10 +294,14 @@ public final class Renderer { return result } - /// Renders a localized string key. + /// Renders a localized string + /// + /// - Parameter string: The localized string to transform + /// + /// - Returns: The string representation private func render(localized string: LocalizedString) throws -> String { - guard let localization = self.localization else { + guard let localization = localization else { // Bail early with the fallback since the localization is not in use return string.key.literal } @@ -383,7 +329,11 @@ public final class Renderer { } } - /// Renders a environment modifier. + /// Renders a environment modifier + /// + /// - Parameter modifier: The modifier to apply to + /// + /// - Returns: The string interpolation of the fellow content private func render(modifier: EnvironmentModifier) throws -> String { if let value = modifier.value { @@ -393,10 +343,14 @@ public final class Renderer { return try render(contents: modifier.content) } - /// Renders a environment value. - private func render(value: EnvironmentValue) throws -> String { + /// Renders a environment value + /// + /// - Parameter value: The environment value to resolve + /// + /// - Returns: The string representation + private func render(envvalue: EnvironmentValue) throws -> String { - let value = try self.environment.resolve(value: value) + let value = try self.environment.resolve(value: envvalue) switch value { case let floatValue as Float: @@ -414,14 +368,14 @@ public final class Renderer { case let dateValue as Date: let formatter = DateFormatter() - formatter.timeZone = self.environment.timeZone ?? TimeZone.current + formatter.timeZone = environment.timeZone ?? TimeZone.current formatter.dateStyle = .medium formatter.timeStyle = .short return formatter.string(from: dateValue) default: - throw Environment.Errors.unableToCastEnvironmentValue + throw Error.unknownValueType } } @@ -458,7 +412,11 @@ public final class Renderer { return try render(contents: statement.second) } - /// Renders the node attributes. + /// Renders the attributes + /// + /// - Parameter attributes: The attributes to render + /// + /// - Returns: The string representation private func render(attributes: OrderedDictionary) throws -> String { var result = "" @@ -468,7 +426,7 @@ public final class Renderer { result += " \(attribute.key)=\"" if let value = attribute.value as? EnvironmentValue { - result += escape(attribute: try render(value: value)) + result += escape(attribute: try render(envvalue: value)) } else { result += escape(attribute: "\(attribute.value)") @@ -480,44 +438,61 @@ public final class Renderer { return result } - /// Renders the markdown content. - private func render(markdown: MarkdownString) throws -> String { - return self.markdown.render(string: escape(content: markdown.raw)) + /// Renders the markdown string + /// + /// - Parameter markstring: The string to render + /// + /// - Returns: The string representation + private func render(markstring: MarkdownString) throws -> String { + return markdown.render(string: escape(content: markstring.raw)) } - /// Renders a environment interpolation + /// Renders a environment string + /// + /// - Parameter envstring: The string to render + /// + /// - Returns: The string representation private func render(envstring: EnvironmentString) throws -> String { return try render(contents: envstring.values) } - /// Converts specific charaters into encoded values. + /// Escapes special characters in the given attribute value + /// + /// The special characters for the attribute are the backslash, the ampersand and the single quotation mark. + /// + /// - Parameter value: The attribute value to be escaped + /// + /// - Returns: The escaped value private func escape(attribute value: String) -> String { - if security.autoEscaping { - return value.replacingOccurrences(of: "&", with: "&") - .replacingOccurrences(of: "\"", with: """) - .replacingOccurrences(of: "'", with: "'") + if !features.contains(.escaping) { + return value } - return value + return encoder.encode(value, as: .attribute) } - /// Converts specific charaters into encoded values. + /// Escapes special characters in the given content value + /// + /// The special characters for the content are the Greater than, Less than symbol. + /// + /// - Parameter value: The content value to be escaped + /// + /// - Returns: The escaped value private func escape(content value: String) -> String { - if security.autoEscaping { - return value.replacingOccurrences(of: "<", with: "<") - .replacingOccurrences(of: ">", with: ">") + if !features.contains(.escaping) { + return value } - return value + return encoder.encode(value, as: .entity) } /// Renders an environment loop /// - /// - Parameter loop: The loop to resolve + /// - Parameter loop: The sequence to resolve /// - /// - Returns: The rendered loop + /// - Returns: The string representation private func render(loop: Sequence) throws -> String { let value = try environment.resolve(value: loop.value) @@ -545,45 +520,32 @@ public final class Renderer { for content in contents { - if let element = content as? (any ContentNode) { + switch content { + case let element as any ContentNode: try render(loop: element, with: value, on: &result) - } - - if let element = content as? (any CustomNode) { + + case let element as any CustomNode: try render(loop: element, with: value, on: &result) - } - - if let element = content as? (any EmptyNode) { + + case let element as EmptyNode: result += try render(element: element) - } - - if let element = content as? (any CommentNode) { + + case let element as CommentNode: result += render(element: element) - } - - if let string = content as? LocalizedString { + + case let string as LocalizedString: result += try render(localized: string) - } - - if let string = content as? MarkdownString { - if !features.contains(.markdown) { - result += escape(content: string.raw) - - } else { - result += try render(markdown: string) - } - } - - if let envstring = content as? EnvironmentString { - result += try render(envstring: envstring) - } - - if let element = content as? String { - result += escape(content: element) - } - - if content is EnvironmentValue { + case let string as MarkdownString: + result += try render(markstring: string) + + case let string as EnvironmentString: + result += try render(envstring: string) + + case let string as String: + result += escape(content: string) + + case is EnvironmentValue: switch value { case let floatValue as Float: @@ -598,9 +560,21 @@ public final class Renderer { case let stringValue as String: result += stringValue + case let dateValue as Date: + + let formatter = DateFormatter() + formatter.timeZone = environment.timeZone ?? TimeZone.current + formatter.dateStyle = .medium + formatter.timeStyle = .short + + result += formatter.string(from: dateValue) + default: - break + throw Error.unknownValueType } + + default: + throw Error.unknownContentType } } } @@ -633,7 +607,7 @@ public final class Renderer { /// - Parameters: /// - element: The element to render /// - value: The value to resolve the environment value with - /// - result: The rendered content + /// - result: The string representation private func render(loop element: some CustomNode, with value: Any, on result: inout String) throws { result += "<\(element.name)" @@ -656,7 +630,7 @@ public final class Renderer { /// - Parameters: /// - envstring: The environment string to render /// - value: The raw value to resolve the environment value with - /// - result: The result + /// - result: The string representation private func render(loop envstring: EnvironmentString, with value: Any, on result: inout String) throws { try render(loop: envstring.values, with: value, on: &result) } diff --git a/Sources/HTMLKit/Framework/Security/Security.swift b/Sources/HTMLKit/Framework/Security/Security.swift deleted file mode 100644 index b3319770..00000000 --- a/Sources/HTMLKit/Framework/Security/Security.swift +++ /dev/null @@ -1,9 +0,0 @@ -public final class Security { - - public var autoEscaping: Bool - - public init() { - - self.autoEscaping = true - } -} diff --git a/Sources/HTMLKitVapor/Configuration.swift b/Sources/HTMLKitVapor/Configuration.swift index a0525177..c2064de5 100644 --- a/Sources/HTMLKitVapor/Configuration.swift +++ b/Sources/HTMLKitVapor/Configuration.swift @@ -1,6 +1,6 @@ import HTMLKit -/// Holds the renderer configuration +/// A type that holds configuration for the renderer public final class Configuration { /// Holds the localization configuration @@ -9,18 +9,14 @@ public final class Configuration { /// Holds the environment configuration internal var environment: HTMLKit.Environment - /// Holds the security configuration - internal var security: HTMLKit.Security - /// Holds the enabled features internal var features: HTMLKit.Features - /// Creates a configuration + /// Initializes a configuration public init() { self.localization = Localization() self.environment = Environment() - self.security = Security() - self.features = [] + self.features = [.escaping] } } diff --git a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift index 311adc2c..105122d9 100644 --- a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift +++ b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift @@ -1,94 +1,78 @@ -/* - Abstract: - The file contains the extensions of some Vapor directives. - */ - import HTMLKit import Vapor extension Application { - /// Access to the vapor provider + /// Provides access to the provider for the application public var htmlkit: HtmlKit { return .init(application: self) } - /// The vapor provider + /// Represents the provider for Vapor public struct HtmlKit { - public var security: HTMLKit.Security { - - get { - self.configuration.security - } - - nonmutating set { - self.configuration.security = newValue - } - } - + /// Manages environment settings public var environment: HTMLKit.Environment { get { - self.configuration.environment + configuration.environment } nonmutating set { - self.configuration.environment = newValue + configuration.environment = newValue } } + /// Manages localization settings public var localization: HTMLKit.Localization { get { - self.configuration.localization + configuration.localization } nonmutating set { - self.configuration.localization = newValue + configuration.localization = newValue } } + /// Manages feature flags public var features: HTMLKit.Features { get { - self.configuration.features + configuration.features } nonmutating set { - self.configuration.features = newValue + configuration.features = newValue } } + /// The key used to store the configuration in Vapor's storage internal struct ConfigurationKey: StorageKey { public typealias Value = Configuration } - /// The configuration storage + /// The configuration for the view renderer internal var configuration: Configuration { - if let configuration = self.application.storage[ConfigurationKey.self] { + if let configuration = application.storage[ConfigurationKey.self] { return configuration } let configuration = Configuration() - self.application.storage[ConfigurationKey.self] = configuration + application.storage[ConfigurationKey.self] = configuration return configuration } - /// The view renderer - internal var renderer: ViewRenderer { - - return .init(eventLoop: application.eventLoopGroup.next(), configuration: configuration, logger: application.logger) - } - - /// The application dependency + /// Holds a reference to the Vapor application internal let application: Application - /// Creates the provider + /// Initializes the HTMLKit provider for a given Vapor application + /// + /// - Parameter application: The application instance from Vapor public init(application: Application) { self.application = application @@ -101,7 +85,7 @@ extension Request { /// The accept language header of the request private var acceptLanguage: String? { - if let languageHeader = self.headers.first(name: .acceptLanguage) { + if let languageHeader = headers.first(name: .acceptLanguage) { return languageHeader.components(separatedBy: ",").first } @@ -111,18 +95,32 @@ extension Request { /// Access to the view renderer public var htmlkit: ViewRenderer { - if let acceptLanguage = self.acceptLanguage { - self.application.htmlkit.environment.upsert(HTMLKit.Locale(tag: acceptLanguage), for: \HTMLKit.EnvironmentKeys.locale) + if let acceptLanguage = acceptLanguage { + application.htmlkit.environment.upsert(HTMLKit.Locale(tag: acceptLanguage), for: \HTMLKit.EnvironmentKeys.locale) } - return .init(eventLoop: self.eventLoop, configuration: self.application.htmlkit.configuration, logger: self.logger) + return .init(eventLoop: eventLoop, configuration: application.htmlkit.configuration, logger: logger) } } +extension HTMLKit.Renderer.Error: AbortError { + + @_implements(AbortError, reason) + public var abortReason: String { description } + + public var status: HTTPResponseStatus { .internalServerError } +} + +extension HTMLKit.Renderer.Error: DebuggableError { + + @_implements(DebuggableError, reason) + public var debuggableReason: String { description } +} + extension HTMLKit.Environment.Errors: AbortError { @_implements(AbortError, reason) - public var abortReason: String { self.description } + public var abortReason: String { description } public var status: HTTPResponseStatus { .internalServerError } } @@ -130,13 +128,13 @@ extension HTMLKit.Environment.Errors: AbortError { extension HTMLKit.Environment.Errors: DebuggableError { @_implements(DebuggableError, reason) - public var debuggableReason: String { self.description } + public var debuggableReason: String { description } } extension HTMLKit.Localization.Errors: AbortError { @_implements(AbortError, reason) - public var abortReason: String { self.description } + public var abortReason: String { description } public var status: HTTPResponseStatus { .internalServerError } } @@ -144,5 +142,5 @@ extension HTMLKit.Localization.Errors: AbortError { extension HTMLKit.Localization.Errors: DebuggableError { @_implements(DebuggableError, reason) - public var debuggableReason: String { self.description } + public var debuggableReason: String { description } } diff --git a/Sources/HTMLKitVapor/ViewRenderer.swift b/Sources/HTMLKitVapor/ViewRenderer.swift index a3a0f357..2f6411e2 100644 --- a/Sources/HTMLKitVapor/ViewRenderer.swift +++ b/Sources/HTMLKitVapor/ViewRenderer.swift @@ -1,36 +1,39 @@ -/* - Abstract: - The file contains the Vapor view renderer. - */ - import HTMLKit import Vapor -/// The view renderer +/// A type responsible for rendering views in Vapor using HTMLKit public class ViewRenderer { - /// The event loop the view renderer is running on + /// The event loop that the renderer operates on internal var eventLoop: EventLoop - /// The renderer for the view renderer + /// The actual renderer internal var renderer: Renderer /// The logger used to log all operations private var logger: Logger - /// Creates the view renderer + /// 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, - security: configuration.security, features: configuration.features, logger: logger) self.logger = logger } - /// Renders a view and transforms it into a view response. + /// 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 { @@ -38,14 +41,18 @@ public class ViewRenderer { var buffer = ByteBufferAllocator().buffer(capacity: 4096) buffer.writeString(try renderer.render(view: view)) - return self.eventLoop.makeSucceededFuture(View(data: buffer)) + return eventLoop.makeSucceededFuture(View(data: buffer)) } catch(let error) { - return self.eventLoop.makeFailedFuture(error) + return eventLoop.makeFailedFuture(error) } } - /// Renders a view and transforms it into a view response. + /// 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/HTMLKitTests/PerformanceTests.swift b/Tests/HTMLKitTests/PerformanceTests.swift index dba956bd..a0869274 100644 --- a/Tests/HTMLKitTests/PerformanceTests.swift +++ b/Tests/HTMLKitTests/PerformanceTests.swift @@ -12,10 +12,7 @@ final class PerformanceTests: XCTestCase { let context = SampleContext(id: 0, title: "TestPage", excerpt: "Testpage", modified: Date(), posted: Date()) - let security = Security() - security.autoEscaping = false - - let renderer = Renderer(security: security) + let renderer = Renderer(features: []) measure { @@ -29,10 +26,7 @@ final class PerformanceTests: XCTestCase { let context = SampleContext(id: 0, title: "TestPage", excerpt: "Testpage", modified: Date(), posted: Date()) - let security = Security() - security.autoEscaping = true - - let renderer = Renderer(security: security) + let renderer = Renderer() measure { @@ -46,10 +40,7 @@ final class PerformanceTests: XCTestCase { let context = SampleContext(id: 0, title: "TestPage", excerpt: "Testpage", modified: Date(), posted: Date()) - let security = Security() - security.autoEscaping = true - - let renderer = Renderer(security: security, features: [.markdown]) + let renderer = Renderer(features: [.escaping, .markdown]) measure { diff --git a/Tests/HTMLKitTests/SecurityTests.swift b/Tests/HTMLKitTests/SecurityTests.swift index b598aa0d..140e0817 100644 --- a/Tests/HTMLKitTests/SecurityTests.swift +++ b/Tests/HTMLKitTests/SecurityTests.swift @@ -13,7 +13,7 @@ final class SecurityTests: XCTestCase { @ContentBuilder var body: Content } - var renderer = Renderer(features: [.markdown]) + var renderer = Renderer(features: [.escaping, .markdown]) func testEncodingAttributeContext() throws {