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 += "\(element.name)>"
@@ -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 += "\(element.name)>"
@@ -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 {