Implemented the RedirectURIUseCase use case in the library target.
This commit is contained in:
@@ -0,0 +1,78 @@
|
|||||||
|
// ===----------------------------------------------------------------------===
|
||||||
|
//
|
||||||
|
// 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.Request
|
||||||
|
import struct Hummingbird.Response
|
||||||
|
import struct Logging.Logger
|
||||||
|
|
||||||
|
/// A use case that produces a redirect response based on a given URI path.
|
||||||
|
struct RedirectURIUseCase {
|
||||||
|
|
||||||
|
// MARK: Type aliases
|
||||||
|
|
||||||
|
/// A pseudo-type that contains data about a request and its related context.
|
||||||
|
typealias ContextualInfo = (request: Request, context: any RequestContext)
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
/// A type that interacts with the logging system.
|
||||||
|
private let logger: Logger
|
||||||
|
|
||||||
|
// MARK: Initializers
|
||||||
|
|
||||||
|
/// Initializes this use case.
|
||||||
|
/// - Parameter logger: A type that interacts with the logging system.
|
||||||
|
init(logger: Logger) {
|
||||||
|
self.logger = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
/// Produces a redirect response based on a given URI path
|
||||||
|
/// - Parameters:
|
||||||
|
/// - uriPath: A URI path to use in the redirection.
|
||||||
|
/// - contextualInfo: A pseudo-type that contains data about a request and its related context.
|
||||||
|
/// - Returns: A redirection response created out of a given URI path plus contextual information.
|
||||||
|
func callAsFunction(
|
||||||
|
_ uriPath: String,
|
||||||
|
with contextualInfo: ContextualInfo
|
||||||
|
) -> Response {
|
||||||
|
defer {
|
||||||
|
logger.log(
|
||||||
|
level: .debug,
|
||||||
|
"The URI path is redirected to this path: \(uriPath)",
|
||||||
|
metadata: .metadata(
|
||||||
|
context: contextualInfo.context,
|
||||||
|
request: contextualInfo.request,
|
||||||
|
statusCode: .movedPermanently,
|
||||||
|
redirect: uriPath
|
||||||
|
),
|
||||||
|
source: .source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .redirect(
|
||||||
|
to: uriPath,
|
||||||
|
type: .permanent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - String+Constants
|
||||||
|
|
||||||
|
private extension String {
|
||||||
|
/// A name of the middleware that triggered a logging event.
|
||||||
|
static let source = "DocCMiddleware"
|
||||||
|
}
|
||||||
@@ -126,10 +126,3 @@ private extension LoggerMetadata_HelpersTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Constants
|
|
||||||
|
|
||||||
private extension String {
|
|
||||||
/// A URI path to use as a redirection sample.
|
|
||||||
static let uriRedirection = "/some/redirect/path"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
// ===----------------------------------------------------------------------===
|
||||||
|
//
|
||||||
|
// 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 Hummingbird
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
import struct Logging.Logger
|
||||||
|
|
||||||
|
@testable import struct DocCMiddleware.RedirectURIUseCase
|
||||||
|
|
||||||
|
@Suite("Redirect URI Use Case", .tags(.useCase))
|
||||||
|
struct RedirectURIUseCaseTests {
|
||||||
|
|
||||||
|
// MARK: Use case tests
|
||||||
|
|
||||||
|
#if swift(>=6.2)
|
||||||
|
@Test
|
||||||
|
func `response when logger expects an event`() async throws {
|
||||||
|
try await assertResponse(
|
||||||
|
logLevel: try randomLogLevelWithEvent,
|
||||||
|
uriRedirection: .uriRedirection,
|
||||||
|
expects: .movedPermanently
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `response when logger does not expects an event`() async throws {
|
||||||
|
try await assertResponse(
|
||||||
|
logLevel: try randomLogLevelWithNoEvent,
|
||||||
|
uriRedirection: .uriRedirection,
|
||||||
|
expects: .movedPermanently
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
@Test("response when logger expects an event")
|
||||||
|
func responseWhenLoggerExpectsEvent() async throws {
|
||||||
|
try await assertResponse(
|
||||||
|
logLevel: try randomLogLevelWithEvent,
|
||||||
|
uriRedirection: .uriRedirection,
|
||||||
|
expects: .movedPermanently
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("response when logger does not expects an event")
|
||||||
|
func responseWhenLogLevel() async throws {
|
||||||
|
try await assertResponse(
|
||||||
|
logLevel: try randomLogLevelWithNoEvent,
|
||||||
|
uriRedirection: .uriRedirection,
|
||||||
|
expects: .movedPermanently
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Assertions
|
||||||
|
|
||||||
|
private extension RedirectURIUseCaseTests {
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
/// Asserts the response returned by the ``RedirectURIUseCase`` use case based on the given `logLevel` logging level and the `uriRedirection`
|
||||||
|
/// URI path plus the expected status code of the response.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - logLevel: A representation of the logging level to set in the `Logger` instance.
|
||||||
|
/// - uriRedirection: A URI path to use in the redirection.
|
||||||
|
/// - statusCode: An expected status code from the response coming out of the use case.
|
||||||
|
/// - Throws: An error in case an issue is encountered while asserting the use case.
|
||||||
|
func assertResponse(
|
||||||
|
logLevel: Logger.Level,
|
||||||
|
uriRedirection: String,
|
||||||
|
expects statusCode: HTTPResponse.Status
|
||||||
|
) async throws {
|
||||||
|
let logHandler = LogHandlerMock()
|
||||||
|
let logger = Logger.test(
|
||||||
|
level: logLevel,
|
||||||
|
handler: logHandler
|
||||||
|
)
|
||||||
|
|
||||||
|
let context: any RequestContext = RequestContextMock(logger: logger)
|
||||||
|
let request: Request = .test(method: .get)
|
||||||
|
|
||||||
|
let useCase = RedirectURIUseCase(logger: logger)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
let result = useCase(
|
||||||
|
uriRedirection,
|
||||||
|
with: (request, context)
|
||||||
|
)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
#expect(result.status == .movedPermanently)
|
||||||
|
#expect(result.body.contentLength == 0)
|
||||||
|
#expect(result.headers == [
|
||||||
|
.location: uriRedirection,
|
||||||
|
.contentLength: "0"
|
||||||
|
])
|
||||||
|
|
||||||
|
let events = await logHandler.entries
|
||||||
|
|
||||||
|
if shouldEventBeLogged(logLevel) {
|
||||||
|
#expect(!events.isEmpty)
|
||||||
|
#expect(events.count == 1)
|
||||||
|
|
||||||
|
let loggedEvent = try #require(events.first)
|
||||||
|
|
||||||
|
#expect(loggedEvent == .init(
|
||||||
|
level: .debug,
|
||||||
|
metadata: [
|
||||||
|
"hb.request.id": "\(context.id)",
|
||||||
|
"hb.request.method": "\(request.method.rawValue)",
|
||||||
|
"hb.request.path": "\(request.uri.path)",
|
||||||
|
"hb.request.status": "\(statusCode.code)",
|
||||||
|
"hb.request.redirect": "\(uriRedirection)"
|
||||||
|
],
|
||||||
|
message: "The URI path is redirected to this path: \(uriRedirection)",
|
||||||
|
source: "DocCMiddleware"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
#expect(events.isEmpty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private extension RedirectURIUseCaseTests {
|
||||||
|
|
||||||
|
// MARK: Computed
|
||||||
|
|
||||||
|
/// Extracts a random logging level that support event logging for the use case.
|
||||||
|
var randomLogLevelWithEvent: Logger.Level {
|
||||||
|
get throws {
|
||||||
|
try #require([.debug, .trace].randomElement())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a random logging level that does not support event logging for the use case.
|
||||||
|
var randomLogLevelWithNoEvent: Logger.Level {
|
||||||
|
get throws {
|
||||||
|
try #require([.critical, .error, .info, .notice, .warning].randomElement())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
/// Checks whether a logging event should be logged or not, based on a given logging level.
|
||||||
|
/// - Parameter level: A representation of a logging level defined in the `Logger` instance.
|
||||||
|
/// - Returns: A boolean value that indicates whether a logging event should have been logged or not.
|
||||||
|
func shouldEventBeLogged(_ logLevel: Logger.Level) -> Bool {
|
||||||
|
[Logger.Level.trace, .debug].contains(logLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// ===----------------------------------------------------------------------===
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// ===----------------------------------------------------------------------===
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
|
||||||
|
// MARK: Constants
|
||||||
|
|
||||||
|
/// A URI path to use as a redirection sample.
|
||||||
|
static let uriRedirection = "/some/redirect/path"
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user