Implemented the "metadata()" helper function for the LoggerMetadata+Helpers extension in the library target.

This commit is contained in:
2025-09-24 01:27:34 +02:00
parent 398b852ac8
commit 4798b72052
8 changed files with 285 additions and 3 deletions
@@ -0,0 +1,50 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
import protocol Hummingbird.RequestContext
import struct Hummingbird.HTTPResponse
import struct Hummingbird.Request
import struct Logging.Logger
extension Logger.Metadata {
// MARK: Functions
/// Generates a dictionary to use as metadata for events to log into the logging system.
/// - Parameters:
/// - context: A type that contains all the parameters associated with a given request, and that conforms to the `RequestContext` protocol.
/// - request: A type that contains all the parameters to process as a request.
/// - statusCode: A representation of a response status to provide as a response.
/// - redirect: A URI path to use in a redirection event, if any.
/// - Returns: A generated metadata dictionary for an event to log into the logging system.
static func metadata(
context: any RequestContext,
request: Request,
statusCode: HTTPResponse.Status,
redirect: String? = nil
) -> Self {
var metadata: Logger.Metadata = [
"hb.request.id": "\(context.id)",
"hb.request.method": "\(request.method.rawValue)",
"hb.request.path": "\(request.uri.path)",
"hb.request.status": "\(statusCode.code)"
]
if let redirect {
metadata["hb.request.redirect"] = "\(redirect)"
}
return metadata
}
}
@@ -13,7 +13,7 @@
import Foundation
import RegexBuilder
/// A use case that obtains some necessary data from a given URI path, that are essential for routing the documentation contents.
/// A use case that extracts data from a given URI path, essential for routing the documentation contents.
struct PrepareURIPathUseCase {
// MARK: Type aliases
@@ -0,0 +1,135 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
import Testing
import struct Hummingbird.HTTPRequest
import struct Hummingbird.HTTPResponse
import struct Hummingbird.Request
import struct Logging.Logger
@testable import DocCMiddleware
@Suite("Logger Metadata Helpers", .tags(.extension))
struct LoggerMetadata_HelpersTests {
// MARK: Functions tests
#if swift(>=6.2)
@Test
func `metadata with HTTP method and status code`() throws {
assertMetadata(
method: try randomMethod,
statusCode: try randomStatusCode
)
}
@Test
func `metadata with HTTP method, status code and redirection URI path`() throws {
assertMetadata(
method: try randomMethod,
statusCode: try randomStatusCode,
redirect: .uriRedirection
)
}
#else
@Test("metadata with HTTP method and status code")
func metadataWithMethodAndStatusCode() throws {
assertMetadata(
method: try randomMethod,
statusCode: try randomStatusCode
)
}
@Test("metadata with HTTP method, status code and redirection URI path")
func metadataWithMethodStatusCodeAndRedirection() throws {
assertMetadata(
method: try randomMethod,
statusCode: try randomStatusCode,
redirect: .uriRedirection
)
}
#endif
}
// MARK: - Assertions
private extension LoggerMetadata_HelpersTests {
// MARK: Functions
/// Asserts the generated metadata dictionary based on provided parameters.
/// - Parameters:
/// - method: A HTTP method of the request.
/// - statusCode: A status code of the response.
/// - redirect: A redirection URI path, if any.
func assertMetadata(
method: HTTPRequest.Method,
statusCode: HTTPResponse.Status,
redirect: String? = nil
) {
// GIVEN
let logger: Logger = .test
let context: MockRequestContext = .init(logger: logger)
let request: Request = .test(method: method)
// WHEN
let metadata: Logger.Metadata = .metadata(
context: context,
request: request,
statusCode: statusCode,
redirect: redirect
)
// THEN
#expect(metadata.keys.count == (redirect == nil ? 4 : 5))
#expect(metadata["hb.request.id"] == logger[metadataKey: "hb.request.id"])
#expect(metadata["hb.request.method"] == "\(method.rawValue)")
#expect(metadata["hb.request.path"] == "/")
#expect(metadata["hb.request.status"] == "\(statusCode.code)")
if let redirect {
#expect(metadata["hb.request.redirect"] == "\(redirect)")
}
}
}
// MARK: - Helpers
private extension LoggerMetadata_HelpersTests {
// MARK: Computed
/// Extracts a random HTTP method of the request from a list of pre-defined values.
var randomMethod: HTTPRequest.Method {
get throws {
try #require([.connect, .delete, .get, .head, .options, .patch, .post, .put, .trace].randomElement())
}
}
/// Extracts a random status code of the response from a list of pre-defined values.
var randomStatusCode: HTTPResponse.Status {
get throws {
try #require([.`continue`, .earlyHints, .ok, .accepted, .multipleChoices, .seeOther, .badRequest, .notFound, .internalServerError, .serviceUnavailable].randomElement())
}
}
}
// MARK: - Constants
private extension String {
/// A URI path to use as a redirection sample.
static let uriRedirection = "/some/redirect/path"
}
@@ -10,6 +10,7 @@
//
// ===----------------------------------------------------------------------===
import Foundation
import Logging
import Testing
@@ -22,7 +23,9 @@ extension Logger {
var logger = Logger(label: "test.hummingbird-docc-middleware.logger")
logger.logLevel = try! #require(Logger.Level.allCases.randomElement())
logger[metadataKey: "hb.request.id"] = "\(UUID().uuidString)"
return logger
}()
@@ -0,0 +1,41 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
import struct Hummingbird.HTTPRequest
import struct Hummingbird.Request
import struct Hummingbird.RequestBody
extension Request {
// MARK: Functions
/// Generates a request that is ready to use in test case.
/// - Parameters:
/// - method: A HTTP method.
/// - path: A URI path, if any.
/// - Returns: A generated request instance to use in test cases.
static func test(
method: HTTPRequest.Method,
path: String? = nil
) -> Self {
.init(
head: .init(
method: method,
scheme: nil,
authority: nil,
path: path
),
body: .init(buffer: .init())
)
}
}
@@ -18,6 +18,8 @@ extension Tag {
/// Tag that indicate a test case for an enumeration type.
@Tag static var enumeration: Self
/// Tag that indicate a test case for an extended type.
@Tag static var `extension`: Self
/// Tag that indicate a test case for a middleware type.
@Tag static var middleware: Self
/// Tag that indicate a test case for a use case type.
@@ -0,0 +1,51 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
import class NIOEmbedded.NIOAsyncTestingChannel
import protocol Hummingbird.RequestContext
import struct Hummingbird.ApplicationRequestContextSource
import struct Hummingbird.CoreRequestContextStorage
import struct Logging.Logger
/// A mock that conforms to the `RequestContext` protocol.
struct MockRequestContext {
// MARK: Properties
var coreContext: CoreRequestContextStorage
// MARK: Initializers
/// Initializes this mock.
/// - Parameter logger: A type that interacts with the logging system.
init(logger: Logger) {
self.coreContext = .init(source: ApplicationRequestContextSource(
channel: NIOAsyncTestingChannel(),
logger: logger
))
}
}
// MARK: - RequestContext
extension MockRequestContext: RequestContext {
// MARK: Initializers
init(source: ApplicationRequestContextSource) {
self.coreContext = .init(source: source)
}
}
@@ -10,5 +10,5 @@
//
// ===----------------------------------------------------------------------===
/// A namespace assigned for test arguments that would be input into test cases.
/// A namespace assigned for test arguments
enum Input {}