diff --git a/Sources/DocCMiddleware/Internal/Extensions/LoggerMetadata+Helpers.swift b/Sources/DocCMiddleware/Internal/Extensions/LoggerMetadata+Helpers.swift new file mode 100644 index 0000000..bddde68 --- /dev/null +++ b/Sources/DocCMiddleware/Internal/Extensions/LoggerMetadata+Helpers.swift @@ -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 + } + +} diff --git a/Sources/DocCMiddleware/Internal/Use Cases/PrepareURIPathUseCase.swift b/Sources/DocCMiddleware/Internal/Use Cases/PrepareURIPathUseCase.swift index 0588860..e0bce51 100644 --- a/Sources/DocCMiddleware/Internal/Use Cases/PrepareURIPathUseCase.swift +++ b/Sources/DocCMiddleware/Internal/Use Cases/PrepareURIPathUseCase.swift @@ -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 diff --git a/Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift b/Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift new file mode 100644 index 0000000..6349e31 --- /dev/null +++ b/Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift @@ -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" +} diff --git a/Tests/DocCMiddleware/Types/Extensions/Logger+Constants.swift b/Tests/DocCMiddleware/Types/Extensions/Logger+Constants.swift index a006a09..d3e2cbe 100644 --- a/Tests/DocCMiddleware/Types/Extensions/Logger+Constants.swift +++ b/Tests/DocCMiddleware/Types/Extensions/Logger+Constants.swift @@ -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 }() diff --git a/Tests/DocCMiddleware/Types/Extensions/Request+Helpers.swift b/Tests/DocCMiddleware/Types/Extensions/Request+Helpers.swift new file mode 100644 index 0000000..54ebd8a --- /dev/null +++ b/Tests/DocCMiddleware/Types/Extensions/Request+Helpers.swift @@ -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()) + ) + } + +} diff --git a/Tests/DocCMiddleware/Types/Extensions/Tag+Constants.swift b/Tests/DocCMiddleware/Types/Extensions/Tag+Constants.swift index 2c26954..31b3733 100644 --- a/Tests/DocCMiddleware/Types/Extensions/Tag+Constants.swift +++ b/Tests/DocCMiddleware/Types/Extensions/Tag+Constants.swift @@ -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. diff --git a/Tests/DocCMiddleware/Types/Mocks/MockRequestContext.swift b/Tests/DocCMiddleware/Types/Mocks/MockRequestContext.swift new file mode 100644 index 0000000..5c285e3 --- /dev/null +++ b/Tests/DocCMiddleware/Types/Mocks/MockRequestContext.swift @@ -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) + } + +} diff --git a/Tests/DocCMiddleware/Types/Namespaces/Input.swift b/Tests/DocCMiddleware/Types/Namespaces/Input.swift index a71d255..cd82e1a 100644 --- a/Tests/DocCMiddleware/Types/Namespaces/Input.swift +++ b/Tests/DocCMiddleware/Types/Namespaces/Input.swift @@ -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 {}