From e68d4464ad3cbc2be3169bcf6af942c034b629a4 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 27 Sep 2025 02:21:33 +0200 Subject: [PATCH 01/24] Fixed the logging event recording concurrency for the LogHandlerMock mock type in the test target. --- .../Types/Mocks/LogHandlerMock.swift | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/Tests/DocCMiddleware/Types/Mocks/LogHandlerMock.swift b/Tests/DocCMiddleware/Types/Mocks/LogHandlerMock.swift index b5c35ce..fa67e3c 100644 --- a/Tests/DocCMiddleware/Types/Mocks/LogHandlerMock.swift +++ b/Tests/DocCMiddleware/Types/Mocks/LogHandlerMock.swift @@ -33,9 +33,7 @@ struct LogHandlerMock { // MARK: Computed /// A list of all the logged events that are being persisted in the recorder. - var entries: [LogEntry] { - get async { await recorder.entries } - } + var entries: [LogEntry] { recorder.entries } } @@ -63,13 +61,25 @@ struct LogEntry: Equatable { // MARK: - LogRecorder extension LogHandlerMock { - /// An actor that persists all the events logged by the ``LogHandlerMock`` mock handler. - actor LogRecorder { + /// A class that records all the events logged by the ``LogHandlerMock`` mock handler. + /// + /// This class conforms to the `Sendable` protocol by using the `@unchecked` modifier because a `NSLock`type is used to handle the access to the logged events in a thread-safe way. + final class LogRecorder: @unchecked Sendable { // MARK: Properties + /// A list of all the logged events persisted in a thread-safe way. + private(set) var _entries: [LogEntry] = [] + + /// A type that coordinates the access to the persisted logged events in a thread-safe way. + private let lock: NSLock = .init() + + // MARK: Computed + /// A list of all the logged events. - private(set) var entries: [LogEntry] = [] + var entries: [LogEntry] { + lock.withLock { _entries } + } // MARK: Functions @@ -84,13 +94,15 @@ extension LogHandlerMock { metadata: Logger.Metadata?, message: Logger.Message, source: String - ) async { - entries += [.init( - level: level, - metadata: metadata, - message: message, - source: source - )] + ) { + lock.withLock { + _entries += [.init( + level: level, + metadata: metadata, + message: message, + source: source + )] + } } } @@ -130,12 +142,12 @@ extension LogHandlerMock: LogHandler { function: String, line: UInt ) { - Task { await recorder.record( + recorder.record( level: level, metadata: metadata, message: message, source: source - )} + ) } } -- 2.52.0 From 40c598d9eb6627952e3f48f681a8442fabbf2d2e Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 27 Sep 2025 02:21:56 +0200 Subject: [PATCH 02/24] Updated the package name in. the Package file. --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index ee93630..3aa71ce 100644 --- a/Package.swift +++ b/Package.swift @@ -44,7 +44,7 @@ let package = Package( // MARK: - Constants enum DocCMiddleware { - static let package = "hummingbird-docc-middleware" + static let package = "hummingbird-docc" static let target = "DocCMiddleware" static let test = "\(DocCMiddleware.target)Tests" } -- 2.52.0 From 79942ea58ccf0715f40db0aa733246771e4ff286 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 27 Sep 2025 02:32:00 +0200 Subject: [PATCH 03/24] Fixed some warnings that appeared in the test target after the LogHandlerMock concurrency fix. --- .../Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift | 2 +- .../Tests/Internal/Use Cases/ServeURIUseCaseTests.swift | 2 +- .../Tests/Public/Middlewares/DocCMiddlewareTests.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift b/Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift index 29953a9..90cf11b 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift +++ b/Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift @@ -107,7 +107,7 @@ private extension RedirectURIUseCaseTests { .contentLength: "0" ]) - let events = await logHandler.entries + let events = logHandler.entries if shouldEventBeLogged(logLevel) { #expect(!events.isEmpty) diff --git a/Tests/DocCMiddleware/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift b/Tests/DocCMiddleware/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift index bf5d27e..dc378fa 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift +++ b/Tests/DocCMiddleware/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift @@ -190,7 +190,7 @@ private extension ServeURIUseCaseTests { #expect(contentLength == 0) } - let events = await logHandler.entries + let events = logHandler.entries if shouldEventBeLogged( logLevel: logLevel, diff --git a/Tests/DocCMiddleware/Tests/Public/Middlewares/DocCMiddlewareTests.swift b/Tests/DocCMiddleware/Tests/Public/Middlewares/DocCMiddlewareTests.swift index 3eec0dd..6be95ab 100644 --- a/Tests/DocCMiddleware/Tests/Public/Middlewares/DocCMiddlewareTests.swift +++ b/Tests/DocCMiddleware/Tests/Public/Middlewares/DocCMiddlewareTests.swift @@ -385,7 +385,7 @@ private extension DocCMiddlewareTests { // THEN #expect(result.status == statusCode) - let events = await logHandler.entries + let events = logHandler.entries if statusCode == .movedPermanently, let uriRedirect { #expect(result.body.contentLength == 0) @@ -483,7 +483,7 @@ private extension DocCMiddlewareTests { #expect(contentLength == 0) } - let events = await logHandler.entries + let events = logHandler.entries if shouldEventBeLogged( logLevel: logLevel, -- 2.52.0 From f41537274ca3a8afa5c18fa212b4233e4c97c078 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 27 Sep 2025 02:41:37 +0200 Subject: [PATCH 04/24] Added the sample target in the Package file. --- Package.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 3aa71ce..a7798bb 100644 --- a/Package.swift +++ b/Package.swift @@ -30,6 +30,13 @@ let package = Package( path: "Sources/DocCMiddleware", swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")] ), + .target( + name: DocCMiddleware.sample, + dependencies: [ + .byName(name: DocCMiddleware.target) + ], + path: "Samples/DocCMiddleware" + ), .testTarget( name: DocCMiddleware.test, dependencies: [ @@ -45,6 +52,7 @@ let package = Package( enum DocCMiddleware { static let package = "hummingbird-docc" + static let sample = "\(DocCMiddleware.target)Sample" static let target = "DocCMiddleware" - static let test = "\(DocCMiddleware.target)Tests" + static let test = "\(DocCMiddleware.target)Test" } -- 2.52.0 From e076e4fa8076b71f85db1903b2e4daeffebf8a66 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 27 Sep 2025 18:51:48 +0200 Subject: [PATCH 05/24] Added the "ArgumentParser" package dependency to the sample target in the Package file. --- Package.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index a7798bb..8e548b3 100644 --- a/Package.swift +++ b/Package.swift @@ -18,6 +18,7 @@ let package = Package( ), ], dependencies: [ + .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.4.0"), .package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), ], @@ -33,9 +34,16 @@ let package = Package( .target( name: DocCMiddleware.sample, dependencies: [ - .byName(name: DocCMiddleware.target) + .byName(name: DocCMiddleware.target), + .product(name: "ArgumentParser", package: "swift-argument-parser"), ], - path: "Samples/DocCMiddleware" + path: "Samples/DocCMiddleware", + swiftSettings: [ + // Enable better optimizations when building in Release configuration. Despite the use of + // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release + // builds. See for details. + .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)), + ] ), .testTarget( name: DocCMiddleware.test, -- 2.52.0 From 0425457e2a4545846b7e8c47355be8dd572907c1 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 28 Sep 2025 21:24:19 +0200 Subject: [PATCH 06/24] Fixed the target type for the sample target in the Package file as an executable target. --- Package.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 8e548b3..83ba0e4 100644 --- a/Package.swift +++ b/Package.swift @@ -23,15 +23,7 @@ let package = Package( .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), ], targets: [ - .target( - name: DocCMiddleware.target, - dependencies: [ - .product(name: "Hummingbird", package: "hummingbird"), - ], - path: "Sources/DocCMiddleware", - swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")] - ), - .target( + .executableTarget( name: DocCMiddleware.sample, dependencies: [ .byName(name: DocCMiddleware.target), @@ -45,6 +37,14 @@ let package = Package( .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)), ] ), + .target( + name: DocCMiddleware.target, + dependencies: [ + .product(name: "Hummingbird", package: "hummingbird"), + ], + path: "Sources/DocCMiddleware", + swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")] + ), .testTarget( name: DocCMiddleware.test, dependencies: [ -- 2.52.0 From 41e26310a76e68147109bb83391848654c6c617d Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 28 Sep 2025 22:09:29 +0200 Subject: [PATCH 07/24] Implemented the SampleAppParameters type in the sample app target, and integrated it to the SampleApp type. --- .../Parameters/SampleAppParameters.swift | 37 +++++++++++++++++++ Samples/DocCMiddleware/SampleApp.swift | 24 ++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 Samples/DocCMiddleware/Parameters/SampleAppParameters.swift create mode 100644 Samples/DocCMiddleware/SampleApp.swift diff --git a/Samples/DocCMiddleware/Parameters/SampleAppParameters.swift b/Samples/DocCMiddleware/Parameters/SampleAppParameters.swift new file mode 100644 index 0000000..acb4ae4 --- /dev/null +++ b/Samples/DocCMiddleware/Parameters/SampleAppParameters.swift @@ -0,0 +1,37 @@ +// ===----------------------------------------------------------------------=== +// +// 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 ArgumentParser.ParsableArguments + +import struct ArgumentParser.Option + +extension SampleApp { + /// A type that conforms to the `ParsableArguments` protocol, which contains the input parameters required for the execution of the sample executable. + struct Parameters: ParsableArguments { + + // MARK: Properties + + /// A label given to the sample app to identify it within a communications channel. + @Option( + name: .shortAndLong, + help: "A label given to the sample app for the sole purpose of identification within a communications channel." + ) + var hostname: String = "127.0.0.1" + + /// A port number assigned to the sample app from where the app either sends or receives data. + @Option( + name: .shortAndLong, + help: "A port number assigned to the sample app from where the app either sends or receives data." + ) + var port: Int = 8080 + } +} diff --git a/Samples/DocCMiddleware/SampleApp.swift b/Samples/DocCMiddleware/SampleApp.swift new file mode 100644 index 0000000..e8ca588 --- /dev/null +++ b/Samples/DocCMiddleware/SampleApp.swift @@ -0,0 +1,24 @@ +// ===----------------------------------------------------------------------=== +// +// 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 ArgumentParser.OptionGroup + +/// A type that implements and runs the sample executable that showcases the `Hummingbird-DocC` middleware. +@main struct SampleApp { + + // MARK: Properties + + /// A type that contains all the necessary input parameters to run the sample executable. + @OptionGroup var parameters: Parameters + +} + -- 2.52.0 From 90c4033a495e9bf4fa577052dba82eef9d5886e0 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 28 Sep 2025 22:19:42 +0200 Subject: [PATCH 08/24] Defined the AppArguments protocol in the sample target and also, conformed the SampleAppArguments type to it. --- ...ameters.swift => SampleAppArguments.swift} | 14 +++++-- .../Protocols/AppArguments.swift | 40 +++++++++++++++++++ Samples/DocCMiddleware/SampleApp.swift | 2 +- .../Types/Extensions/Logger+Helpers.swift | 2 +- 4 files changed, 52 insertions(+), 6 deletions(-) rename Samples/DocCMiddleware/Parameters/{SampleAppParameters.swift => SampleAppArguments.swift} (70%) create mode 100644 Samples/DocCMiddleware/Protocols/AppArguments.swift diff --git a/Samples/DocCMiddleware/Parameters/SampleAppParameters.swift b/Samples/DocCMiddleware/Parameters/SampleAppArguments.swift similarity index 70% rename from Samples/DocCMiddleware/Parameters/SampleAppParameters.swift rename to Samples/DocCMiddleware/Parameters/SampleAppArguments.swift index acb4ae4..86afa3d 100644 --- a/Samples/DocCMiddleware/Parameters/SampleAppParameters.swift +++ b/Samples/DocCMiddleware/Parameters/SampleAppArguments.swift @@ -13,25 +13,31 @@ import protocol ArgumentParser.ParsableArguments import struct ArgumentParser.Option +import struct Logging.Logger extension SampleApp { - /// A type that conforms to the `ParsableArguments` protocol, which contains the input parameters required for the execution of the sample executable. - struct Parameters: ParsableArguments { + /// A type that conforms to the ``AppArguments`` and the `ParsableArguments` protocols, which contains the input parameters required for the + /// execution of the sample executable. + struct Arguments: AppArguments, ParsableArguments { // MARK: Properties - /// A label given to the sample app to identify it within a communications channel. @Option( name: .shortAndLong, help: "A label given to the sample app for the sole purpose of identification within a communications channel." ) var hostname: String = "127.0.0.1" - /// A port number assigned to the sample app from where the app either sends or receives data. @Option( name: .shortAndLong, help: "A port number assigned to the sample app from where the app either sends or receives data." ) var port: Int = 8080 + + @Option( + name: .long, + help: "A log level to configure in a type that interacts with the logging system." + ) + var logLevel: Logger.Level? } } diff --git a/Samples/DocCMiddleware/Protocols/AppArguments.swift b/Samples/DocCMiddleware/Protocols/AppArguments.swift new file mode 100644 index 0000000..9afafe4 --- /dev/null +++ b/Samples/DocCMiddleware/Protocols/AppArguments.swift @@ -0,0 +1,40 @@ +// ===----------------------------------------------------------------------=== +// +// 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 ArgumentParser.ExpressibleByArgument + +import struct Logging.Logger + +/// A protocol that defines the input arguments the sample executable requires to run. +protocol AppArguments { + + // MARK: Properties + + /// A label given to the sample app to identify it within a communications channel. + var hostname: String { get } + + /// A port number assigned to the sample app from where the app either sends or receives data. + var port: Int { get } + + /// A log level to configure in a type that interacts with the logging system. + var logLevel: Logger.Level? { get } + +} + +// MARK: - Conformances + +/// Extends the `Logger.Level` type so it can be used as an argument. +#if hasFeature(RetroactiveAttribute) +extension Logger.Level: @retroactive ExpressibleByArgument {} +#else +extension Logger.Level: ExpressibleByArgument {} +#endif diff --git a/Samples/DocCMiddleware/SampleApp.swift b/Samples/DocCMiddleware/SampleApp.swift index e8ca588..69e1620 100644 --- a/Samples/DocCMiddleware/SampleApp.swift +++ b/Samples/DocCMiddleware/SampleApp.swift @@ -18,7 +18,7 @@ import struct ArgumentParser.OptionGroup // MARK: Properties /// A type that contains all the necessary input parameters to run the sample executable. - @OptionGroup var parameters: Parameters + @OptionGroup var arguments: Arguments } diff --git a/Tests/DocCMiddleware/Types/Extensions/Logger+Helpers.swift b/Tests/DocCMiddleware/Types/Extensions/Logger+Helpers.swift index d0cec00..70e6b5f 100644 --- a/Tests/DocCMiddleware/Types/Extensions/Logger+Helpers.swift +++ b/Tests/DocCMiddleware/Types/Extensions/Logger+Helpers.swift @@ -53,5 +53,5 @@ extension Logger { private extension String { /// A label to assign to a test logger instance. - static let loggerLabel = "test.hummingbird-docc-middleware.logger" + static let loggerLabel = "test.hummingbird-docc.logger" } -- 2.52.0 From efedff51d10e77ea5a7b1dde1d6c46bcdce0f68b Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 28 Sep 2025 23:09:02 +0200 Subject: [PATCH 09/24] Updated the names of the targets in the Package file to "HummingbirdDocC". --- Package.swift | 30 +++++------ .../HummingbirdDocC/Builders/AppBuilder.swift | 51 +++++++++++++++++++ .../Parameters/SampleAppArguments.swift | 0 .../Protocols/AppArguments.swift | 0 .../SampleApp.swift | 24 +++++++++ .../Catalogs/DocCMiddleware.docc/Library.md | 0 .../Internal/Enumerations/AssetFile.swift | 0 .../Internal/Enumerations/AssetFolder.swift | 0 .../Enumerations/DocumentationFolder.swift | 0 .../Extensions/LoggerMetadata+Helpers.swift | 0 .../Extensions/String+Constants.swift | 0 .../Internal/Extensions/String+Formats.swift | 0 .../Internal/Protocols/Pathable.swift | 0 .../Pseudo Types/ContextualInfo.swift | 0 .../Internal/Use Cases/CheckURIUseCase.swift | 0 .../Use Cases/PrepareURIPathUseCase.swift | 0 .../Use Cases/RedirectURIUseCase.swift | 0 .../Internal/Use Cases/ServeURIUseCase.swift | 0 .../DoccMiddlewareConfiguration.swift | 0 .../Public/Middlewares/DocCMiddleware.swift | 0 .../Enumerations/AssetFileTests.swift | 2 +- .../Enumerations/AssetFolderTests.swift | 2 +- .../DocumentationFolderTests.swift | 2 +- .../LoggerMetadata+HelpersTests.swift | 2 +- .../Use Cases/CheckURIUseCaseTests.swift | 2 +- .../PrepareURIPathUseCaseTests.swift | 2 +- .../Use Cases/RedirectURIUseCaseTests.swift | 2 +- .../Use Cases/ServeURIUseCaseTests.swift | 2 +- .../Middlewares/DocCMiddlewareTests.swift | 2 +- .../Types/Extensions/Logger+Helpers.swift | 0 .../Extensions/LoggerLevel+Helpers.swift | 0 .../Types/Extensions/Request+Helpers.swift | 0 .../Types/Extensions/String+Constants.swift | 0 .../Types/Extensions/Tag+Constants.swift | 0 .../Types/Mocks/FileProviderMock.swift | 0 .../Types/Mocks/LogHandlerMock.swift | 0 .../Types/Mocks/RequestContextMock.swift | 0 .../Types/Namespaces/Input.swift | 0 .../Types/Namespaces/Output.swift | 0 .../Types/Stubs/FileProviderStub.swift | 0 40 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 Samples/HummingbirdDocC/Builders/AppBuilder.swift rename Samples/{DocCMiddleware => HummingbirdDocC}/Parameters/SampleAppArguments.swift (100%) rename Samples/{DocCMiddleware => HummingbirdDocC}/Protocols/AppArguments.swift (100%) rename Samples/{DocCMiddleware => HummingbirdDocC}/SampleApp.swift (60%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Catalogs/DocCMiddleware.docc/Library.md (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Enumerations/AssetFile.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Enumerations/AssetFolder.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Enumerations/DocumentationFolder.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Extensions/LoggerMetadata+Helpers.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Extensions/String+Constants.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Extensions/String+Formats.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Protocols/Pathable.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Pseudo Types/ContextualInfo.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Use Cases/CheckURIUseCase.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Use Cases/PrepareURIPathUseCase.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Use Cases/RedirectURIUseCase.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Internal/Use Cases/ServeURIUseCase.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Public/Configurations/DoccMiddlewareConfiguration.swift (100%) rename Sources/{DocCMiddleware => HummingbirdDocC}/Public/Middlewares/DocCMiddleware.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Tests/Internal/Enumerations/AssetFileTests.swift (97%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Tests/Internal/Enumerations/AssetFolderTests.swift (97%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Tests/Internal/Enumerations/DocumentationFolderTests.swift (97%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift (99%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift (98%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift (98%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift (98%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift (99%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Tests/Public/Middlewares/DocCMiddlewareTests.swift (99%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Extensions/Logger+Helpers.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Extensions/LoggerLevel+Helpers.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Extensions/Request+Helpers.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Extensions/String+Constants.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Extensions/Tag+Constants.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Mocks/FileProviderMock.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Mocks/LogHandlerMock.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Mocks/RequestContextMock.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Namespaces/Input.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Namespaces/Output.swift (100%) rename Tests/{DocCMiddleware => HummingbirdDocC}/Types/Stubs/FileProviderStub.swift (100%) diff --git a/Package.swift b/Package.swift index 83ba0e4..ed22d10 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( - name: DocCMiddleware.package, + name: HummingbirdDocC.package, platforms: [ .iOS(.v17), .macCatalyst(.v17), @@ -13,8 +13,8 @@ let package = Package( ], products: [ .library( - name: DocCMiddleware.package, - targets: [DocCMiddleware.target] + name: HummingbirdDocC.package, + targets: [HummingbirdDocC.target] ), ], dependencies: [ @@ -24,12 +24,12 @@ let package = Package( ], targets: [ .executableTarget( - name: DocCMiddleware.sample, + name: HummingbirdDocC.sample, dependencies: [ - .byName(name: DocCMiddleware.target), + .byName(name: HummingbirdDocC.target), .product(name: "ArgumentParser", package: "swift-argument-parser"), ], - path: "Samples/DocCMiddleware", + path: "Samples/HummingbirdDocC", swiftSettings: [ // Enable better optimizations when building in Release configuration. Despite the use of // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release @@ -38,29 +38,29 @@ let package = Package( ] ), .target( - name: DocCMiddleware.target, + name: HummingbirdDocC.target, dependencies: [ .product(name: "Hummingbird", package: "hummingbird"), ], - path: "Sources/DocCMiddleware", + path: "Sources/HummingbirdDocC", swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")] ), .testTarget( - name: DocCMiddleware.test, + name: HummingbirdDocC.test, dependencies: [ .product(name: "HummingbirdTesting", package: "hummingbird"), - .byName(name: DocCMiddleware.target) + .byName(name: HummingbirdDocC.target) ], - path: "Tests/DocCMiddleware" + path: "Tests/HummingbirdDocC" ), ] ) // MARK: - Constants -enum DocCMiddleware { +enum HummingbirdDocC { static let package = "hummingbird-docc" - static let sample = "\(DocCMiddleware.target)Sample" - static let target = "DocCMiddleware" - static let test = "\(DocCMiddleware.target)Test" + static let sample = "\(HummingbirdDocC.target)Sample" + static let target = "HummingbirdDocC" + static let test = "\(HummingbirdDocC.target)Test" } diff --git a/Samples/HummingbirdDocC/Builders/AppBuilder.swift b/Samples/HummingbirdDocC/Builders/AppBuilder.swift new file mode 100644 index 0000000..cc15e93 --- /dev/null +++ b/Samples/HummingbirdDocC/Builders/AppBuilder.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 Hummingbird.Router + +import protocol Hummingbird.ApplicationProtocol + +import struct Hummingbird.Application +import struct Hummingbird.BindAddress +import struct Logging.Logger + +struct AppBuilder { + + // MARK: Properties + + /// A type that interacts with the logging system. + private let logger: Logger + + // MARK: Initializers + + /// Initializes this builder. + /// - Parameter logger: A type that interacts with the logging system. + init(logger: Logger) { + self.logger = logger + } + + // MARK: Functions + + func callAsFunction( + _ arguments: AppArguments + ) -> some ApplicationProtocol { + let router = Router() + + return Application( + router: router, + configuration: .init( + address: .hostname(arguments.hostname, port: arguments.port) + ), + logger: logger + ) + } +} diff --git a/Samples/DocCMiddleware/Parameters/SampleAppArguments.swift b/Samples/HummingbirdDocC/Parameters/SampleAppArguments.swift similarity index 100% rename from Samples/DocCMiddleware/Parameters/SampleAppArguments.swift rename to Samples/HummingbirdDocC/Parameters/SampleAppArguments.swift diff --git a/Samples/DocCMiddleware/Protocols/AppArguments.swift b/Samples/HummingbirdDocC/Protocols/AppArguments.swift similarity index 100% rename from Samples/DocCMiddleware/Protocols/AppArguments.swift rename to Samples/HummingbirdDocC/Protocols/AppArguments.swift diff --git a/Samples/DocCMiddleware/SampleApp.swift b/Samples/HummingbirdDocC/SampleApp.swift similarity index 60% rename from Samples/DocCMiddleware/SampleApp.swift rename to Samples/HummingbirdDocC/SampleApp.swift index 69e1620..fc5ae0e 100644 --- a/Samples/DocCMiddleware/SampleApp.swift +++ b/Samples/HummingbirdDocC/SampleApp.swift @@ -10,7 +10,10 @@ // // ===----------------------------------------------------------------------=== +import protocol ArgumentParser.AsyncParsableCommand + import struct ArgumentParser.OptionGroup +import struct Logging.Logger /// A type that implements and runs the sample executable that showcases the `Hummingbird-DocC` middleware. @main struct SampleApp { @@ -22,3 +25,24 @@ import struct ArgumentParser.OptionGroup } +// MARK: - AsyncParsableCommand + +extension SampleApp: AsyncParsableCommand { + + // MARK: Functions + + func run() async throws { + let builder = AppBuilder(logger: { + var logger = Logger(label: "sample.hummingbird-docc.logger") + + logger.logLevel = arguments.logLevel ?? .trace + + return logger + }()) + + let application = builder(arguments) + + try await application.run() + } + +} diff --git a/Sources/DocCMiddleware/Catalogs/DocCMiddleware.docc/Library.md b/Sources/HummingbirdDocC/Catalogs/DocCMiddleware.docc/Library.md similarity index 100% rename from Sources/DocCMiddleware/Catalogs/DocCMiddleware.docc/Library.md rename to Sources/HummingbirdDocC/Catalogs/DocCMiddleware.docc/Library.md diff --git a/Sources/DocCMiddleware/Internal/Enumerations/AssetFile.swift b/Sources/HummingbirdDocC/Internal/Enumerations/AssetFile.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Enumerations/AssetFile.swift rename to Sources/HummingbirdDocC/Internal/Enumerations/AssetFile.swift diff --git a/Sources/DocCMiddleware/Internal/Enumerations/AssetFolder.swift b/Sources/HummingbirdDocC/Internal/Enumerations/AssetFolder.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Enumerations/AssetFolder.swift rename to Sources/HummingbirdDocC/Internal/Enumerations/AssetFolder.swift diff --git a/Sources/DocCMiddleware/Internal/Enumerations/DocumentationFolder.swift b/Sources/HummingbirdDocC/Internal/Enumerations/DocumentationFolder.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Enumerations/DocumentationFolder.swift rename to Sources/HummingbirdDocC/Internal/Enumerations/DocumentationFolder.swift diff --git a/Sources/DocCMiddleware/Internal/Extensions/LoggerMetadata+Helpers.swift b/Sources/HummingbirdDocC/Internal/Extensions/LoggerMetadata+Helpers.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Extensions/LoggerMetadata+Helpers.swift rename to Sources/HummingbirdDocC/Internal/Extensions/LoggerMetadata+Helpers.swift diff --git a/Sources/DocCMiddleware/Internal/Extensions/String+Constants.swift b/Sources/HummingbirdDocC/Internal/Extensions/String+Constants.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Extensions/String+Constants.swift rename to Sources/HummingbirdDocC/Internal/Extensions/String+Constants.swift diff --git a/Sources/DocCMiddleware/Internal/Extensions/String+Formats.swift b/Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Extensions/String+Formats.swift rename to Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift diff --git a/Sources/DocCMiddleware/Internal/Protocols/Pathable.swift b/Sources/HummingbirdDocC/Internal/Protocols/Pathable.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Protocols/Pathable.swift rename to Sources/HummingbirdDocC/Internal/Protocols/Pathable.swift diff --git a/Sources/DocCMiddleware/Internal/Pseudo Types/ContextualInfo.swift b/Sources/HummingbirdDocC/Internal/Pseudo Types/ContextualInfo.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Pseudo Types/ContextualInfo.swift rename to Sources/HummingbirdDocC/Internal/Pseudo Types/ContextualInfo.swift diff --git a/Sources/DocCMiddleware/Internal/Use Cases/CheckURIUseCase.swift b/Sources/HummingbirdDocC/Internal/Use Cases/CheckURIUseCase.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Use Cases/CheckURIUseCase.swift rename to Sources/HummingbirdDocC/Internal/Use Cases/CheckURIUseCase.swift diff --git a/Sources/DocCMiddleware/Internal/Use Cases/PrepareURIPathUseCase.swift b/Sources/HummingbirdDocC/Internal/Use Cases/PrepareURIPathUseCase.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Use Cases/PrepareURIPathUseCase.swift rename to Sources/HummingbirdDocC/Internal/Use Cases/PrepareURIPathUseCase.swift diff --git a/Sources/DocCMiddleware/Internal/Use Cases/RedirectURIUseCase.swift b/Sources/HummingbirdDocC/Internal/Use Cases/RedirectURIUseCase.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Use Cases/RedirectURIUseCase.swift rename to Sources/HummingbirdDocC/Internal/Use Cases/RedirectURIUseCase.swift diff --git a/Sources/DocCMiddleware/Internal/Use Cases/ServeURIUseCase.swift b/Sources/HummingbirdDocC/Internal/Use Cases/ServeURIUseCase.swift similarity index 100% rename from Sources/DocCMiddleware/Internal/Use Cases/ServeURIUseCase.swift rename to Sources/HummingbirdDocC/Internal/Use Cases/ServeURIUseCase.swift diff --git a/Sources/DocCMiddleware/Public/Configurations/DoccMiddlewareConfiguration.swift b/Sources/HummingbirdDocC/Public/Configurations/DoccMiddlewareConfiguration.swift similarity index 100% rename from Sources/DocCMiddleware/Public/Configurations/DoccMiddlewareConfiguration.swift rename to Sources/HummingbirdDocC/Public/Configurations/DoccMiddlewareConfiguration.swift diff --git a/Sources/DocCMiddleware/Public/Middlewares/DocCMiddleware.swift b/Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift similarity index 100% rename from Sources/DocCMiddleware/Public/Middlewares/DocCMiddleware.swift rename to Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift diff --git a/Tests/DocCMiddleware/Tests/Internal/Enumerations/AssetFileTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Enumerations/AssetFileTests.swift similarity index 97% rename from Tests/DocCMiddleware/Tests/Internal/Enumerations/AssetFileTests.swift rename to Tests/HummingbirdDocC/Tests/Internal/Enumerations/AssetFileTests.swift index 0ded28e..cd549d1 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Enumerations/AssetFileTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Enumerations/AssetFileTests.swift @@ -12,7 +12,7 @@ import Testing -@testable import enum DocCMiddleware.AssetFile +@testable import enum HummingbirdDocC.AssetFile @Suite("Asset File", .tags(.enumeration)) struct AssetFileTests { diff --git a/Tests/DocCMiddleware/Tests/Internal/Enumerations/AssetFolderTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Enumerations/AssetFolderTests.swift similarity index 97% rename from Tests/DocCMiddleware/Tests/Internal/Enumerations/AssetFolderTests.swift rename to Tests/HummingbirdDocC/Tests/Internal/Enumerations/AssetFolderTests.swift index a0b12d1..4af5efe 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Enumerations/AssetFolderTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Enumerations/AssetFolderTests.swift @@ -12,7 +12,7 @@ import Testing -@testable import enum DocCMiddleware.AssetFolder +@testable import enum HummingbirdDocC.AssetFolder @Suite("Asset Folder", .tags(.enumeration)) struct AssetFolderTests { diff --git a/Tests/DocCMiddleware/Tests/Internal/Enumerations/DocumentationFolderTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Enumerations/DocumentationFolderTests.swift similarity index 97% rename from Tests/DocCMiddleware/Tests/Internal/Enumerations/DocumentationFolderTests.swift rename to Tests/HummingbirdDocC/Tests/Internal/Enumerations/DocumentationFolderTests.swift index 3004b0c..f934d1a 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Enumerations/DocumentationFolderTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Enumerations/DocumentationFolderTests.swift @@ -12,7 +12,7 @@ import Testing -@testable import enum DocCMiddleware.DocumentationFolder +@testable import enum HummingbirdDocC.DocumentationFolder @Suite("Documentation Type", .tags(.enumeration)) struct DocumentationTypeTests { diff --git a/Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift similarity index 99% rename from Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift rename to Tests/HummingbirdDocC/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift index 282552e..cbfe537 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Extensions/LoggerMetadata+HelpersTests.swift @@ -17,7 +17,7 @@ import struct Hummingbird.HTTPResponse import struct Hummingbird.Request import struct Logging.Logger -@testable import DocCMiddleware +@testable import HummingbirdDocC @Suite("Logger Metadata Helpers", .tags(.extension)) struct LoggerMetadata_HelpersTests { diff --git a/Tests/DocCMiddleware/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift similarity index 98% rename from Tests/DocCMiddleware/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift rename to Tests/HummingbirdDocC/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift index 922fec2..083697c 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift @@ -14,7 +14,7 @@ import Testing import struct HummingbirdCore.URI -@testable import struct DocCMiddleware.CheckURIUseCase +@testable import struct HummingbirdDocC.CheckURIUseCase @Suite("Check URI use case", .tags(.useCase)) struct CheckURIUseCaseTests { diff --git a/Tests/DocCMiddleware/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift similarity index 98% rename from Tests/DocCMiddleware/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift rename to Tests/HummingbirdDocC/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift index e73ffa6..aa90000 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift @@ -12,7 +12,7 @@ import Testing -@testable import struct DocCMiddleware.PrepareURIPathUseCase +@testable import struct HummingbirdDocC.PrepareURIPathUseCase @Suite("Prepare URI Path Use Case", .tags(.useCase)) struct PrepareURIPathUseCaseTests { diff --git a/Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift similarity index 98% rename from Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift rename to Tests/HummingbirdDocC/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift index 90cf11b..46c24ad 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/RedirectURIUseCaseTests.swift @@ -18,7 +18,7 @@ import struct Hummingbird.HTTPResponse import struct Hummingbird.Request import struct Logging.Logger -@testable import struct DocCMiddleware.RedirectURIUseCase +@testable import struct HummingbirdDocC.RedirectURIUseCase @Suite("Redirect URI Use Case", .tags(.useCase)) struct RedirectURIUseCaseTests { diff --git a/Tests/DocCMiddleware/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift similarity index 99% rename from Tests/DocCMiddleware/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift rename to Tests/HummingbirdDocC/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift index dc378fa..5e18b20 100644 --- a/Tests/DocCMiddleware/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/ServeURIUseCaseTests.swift @@ -18,7 +18,7 @@ import struct Hummingbird.HTTPResponse import struct Hummingbird.Request import struct Logging.Logger -@testable import struct DocCMiddleware.ServeURIUseCase +@testable import struct HummingbirdDocC.ServeURIUseCase @Suite("Serve URI Use Case", .tags(.useCase)) struct ServeURIUseCaseTests { diff --git a/Tests/DocCMiddleware/Tests/Public/Middlewares/DocCMiddlewareTests.swift b/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift similarity index 99% rename from Tests/DocCMiddleware/Tests/Public/Middlewares/DocCMiddlewareTests.swift rename to Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift index 6be95ab..704f7e5 100644 --- a/Tests/DocCMiddleware/Tests/Public/Middlewares/DocCMiddlewareTests.swift +++ b/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift @@ -20,7 +20,7 @@ import struct Hummingbird.LocalFileSystem import struct Hummingbird.Request import struct Logging.Logger -@testable import struct DocCMiddleware.DocCMiddleware +@testable import struct HummingbirdDocC.DocCMiddleware @Suite("DocC Middleware", .tags(.middleware)) struct DocCMiddlewareTests { diff --git a/Tests/DocCMiddleware/Types/Extensions/Logger+Helpers.swift b/Tests/HummingbirdDocC/Types/Extensions/Logger+Helpers.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Extensions/Logger+Helpers.swift rename to Tests/HummingbirdDocC/Types/Extensions/Logger+Helpers.swift diff --git a/Tests/DocCMiddleware/Types/Extensions/LoggerLevel+Helpers.swift b/Tests/HummingbirdDocC/Types/Extensions/LoggerLevel+Helpers.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Extensions/LoggerLevel+Helpers.swift rename to Tests/HummingbirdDocC/Types/Extensions/LoggerLevel+Helpers.swift diff --git a/Tests/DocCMiddleware/Types/Extensions/Request+Helpers.swift b/Tests/HummingbirdDocC/Types/Extensions/Request+Helpers.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Extensions/Request+Helpers.swift rename to Tests/HummingbirdDocC/Types/Extensions/Request+Helpers.swift diff --git a/Tests/DocCMiddleware/Types/Extensions/String+Constants.swift b/Tests/HummingbirdDocC/Types/Extensions/String+Constants.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Extensions/String+Constants.swift rename to Tests/HummingbirdDocC/Types/Extensions/String+Constants.swift diff --git a/Tests/DocCMiddleware/Types/Extensions/Tag+Constants.swift b/Tests/HummingbirdDocC/Types/Extensions/Tag+Constants.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Extensions/Tag+Constants.swift rename to Tests/HummingbirdDocC/Types/Extensions/Tag+Constants.swift diff --git a/Tests/DocCMiddleware/Types/Mocks/FileProviderMock.swift b/Tests/HummingbirdDocC/Types/Mocks/FileProviderMock.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Mocks/FileProviderMock.swift rename to Tests/HummingbirdDocC/Types/Mocks/FileProviderMock.swift diff --git a/Tests/DocCMiddleware/Types/Mocks/LogHandlerMock.swift b/Tests/HummingbirdDocC/Types/Mocks/LogHandlerMock.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Mocks/LogHandlerMock.swift rename to Tests/HummingbirdDocC/Types/Mocks/LogHandlerMock.swift diff --git a/Tests/DocCMiddleware/Types/Mocks/RequestContextMock.swift b/Tests/HummingbirdDocC/Types/Mocks/RequestContextMock.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Mocks/RequestContextMock.swift rename to Tests/HummingbirdDocC/Types/Mocks/RequestContextMock.swift diff --git a/Tests/DocCMiddleware/Types/Namespaces/Input.swift b/Tests/HummingbirdDocC/Types/Namespaces/Input.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Namespaces/Input.swift rename to Tests/HummingbirdDocC/Types/Namespaces/Input.swift diff --git a/Tests/DocCMiddleware/Types/Namespaces/Output.swift b/Tests/HummingbirdDocC/Types/Namespaces/Output.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Namespaces/Output.swift rename to Tests/HummingbirdDocC/Types/Namespaces/Output.swift diff --git a/Tests/DocCMiddleware/Types/Stubs/FileProviderStub.swift b/Tests/HummingbirdDocC/Types/Stubs/FileProviderStub.swift similarity index 100% rename from Tests/DocCMiddleware/Types/Stubs/FileProviderStub.swift rename to Tests/HummingbirdDocC/Types/Stubs/FileProviderStub.swift -- 2.52.0 From 941dde758a9f1ff5ac6ae902b32ae957bbb17b51 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 28 Sep 2025 23:10:40 +0200 Subject: [PATCH 10/24] Updated the existing types to a new folder structure in the sample target. --- Samples/HummingbirdDocC/{ => App}/Builders/AppBuilder.swift | 0 .../HummingbirdDocC/{ => App}/Parameters/SampleAppArguments.swift | 0 Samples/HummingbirdDocC/{ => App}/Protocols/AppArguments.swift | 0 Samples/HummingbirdDocC/{ => App}/SampleApp.swift | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename Samples/HummingbirdDocC/{ => App}/Builders/AppBuilder.swift (100%) rename Samples/HummingbirdDocC/{ => App}/Parameters/SampleAppArguments.swift (100%) rename Samples/HummingbirdDocC/{ => App}/Protocols/AppArguments.swift (100%) rename Samples/HummingbirdDocC/{ => App}/SampleApp.swift (100%) diff --git a/Samples/HummingbirdDocC/Builders/AppBuilder.swift b/Samples/HummingbirdDocC/App/Builders/AppBuilder.swift similarity index 100% rename from Samples/HummingbirdDocC/Builders/AppBuilder.swift rename to Samples/HummingbirdDocC/App/Builders/AppBuilder.swift diff --git a/Samples/HummingbirdDocC/Parameters/SampleAppArguments.swift b/Samples/HummingbirdDocC/App/Parameters/SampleAppArguments.swift similarity index 100% rename from Samples/HummingbirdDocC/Parameters/SampleAppArguments.swift rename to Samples/HummingbirdDocC/App/Parameters/SampleAppArguments.swift diff --git a/Samples/HummingbirdDocC/Protocols/AppArguments.swift b/Samples/HummingbirdDocC/App/Protocols/AppArguments.swift similarity index 100% rename from Samples/HummingbirdDocC/Protocols/AppArguments.swift rename to Samples/HummingbirdDocC/App/Protocols/AppArguments.swift diff --git a/Samples/HummingbirdDocC/SampleApp.swift b/Samples/HummingbirdDocC/App/SampleApp.swift similarity index 100% rename from Samples/HummingbirdDocC/SampleApp.swift rename to Samples/HummingbirdDocC/App/SampleApp.swift -- 2.52.0 From 87a64af68a0f71f040426c1354f965e6e31ae45a Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 28 Sep 2025 23:32:37 +0200 Subject: [PATCH 11/24] Fixed the "lib-build" and the "lib-release" tasks in the Makefile file. --- .env | 6 +++--- Makefile | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.env b/.env index 1dde1ce..23ca037 100644 --- a/.env +++ b/.env @@ -13,10 +13,10 @@ # --- DOCUMENTATION --- DOCC_GITHUB_OUTPUT=./docs -DOCC_GITHUB_BASE_PATH=hummingbird-docc-middleware -DOCC_PREVIEW_URL=http://localhost:8080/documentation/doccmiddleware +DOCC_GITHUB_BASE_PATH=hummingbird-docc +DOCC_PREVIEW_URL=http://localhost:8080/documentation/hummingbirddocc DOCC_XCODE_OUTPUT=./${SPM_LIBRARY_TARGET}.doccarchive # -- SWIFT PACKAGE MANAGER --- -SPM_LIBRARY_TARGET=DocCMiddleware \ No newline at end of file +SPM_LIBRARY_TARGET=HummingbirdDocC diff --git a/Makefile b/Makefile index e11e383..b255468 100644 --- a/Makefile +++ b/Makefile @@ -20,10 +20,14 @@ export $(shell sed 's/=.*//' $(environment)) # LIBRARY lib-build: ## Builds the library - @swift build + @swift build \ + --target $(SPM_LIBRARY_TARGET) lib-release: ## Releases the library - @swift build -c release + @swift build \ + --target $(SPM_LIBRARY_TARGET) \ + --configuration release + lib-test: ## Runs the unit tests for the library @swift test \ -- 2.52.0 From 2c4083c0e3b49ed4d52dc89653b837ada7189d6e Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 28 Sep 2025 23:32:53 +0200 Subject: [PATCH 12/24] Added the "lib-sample" task to the Makefile file. --- .env | 1 + Makefile | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.env b/.env index 23ca037..514675a 100644 --- a/.env +++ b/.env @@ -20,3 +20,4 @@ DOCC_XCODE_OUTPUT=./${SPM_LIBRARY_TARGET}.doccarchive # -- SWIFT PACKAGE MANAGER --- SPM_LIBRARY_TARGET=HummingbirdDocC +SPM_SAMPLE_TARGET=HummingbirdDocCSample \ No newline at end of file diff --git a/Makefile b/Makefile index b255468..e2eb043 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,10 @@ lib-release: ## Releases the library --target $(SPM_LIBRARY_TARGET) \ --configuration release +lib-sample: ## Runs the sample app of the library + @swift run \ + --configuration release \ + --disable-sandbox lib-test: ## Runs the unit tests for the library @swift test \ -- 2.52.0 From 27d1d3b59fcec01c9911d1b09c150240eff9f13e Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 29 Sep 2025 02:18:15 +0200 Subject: [PATCH 13/24] Renamed the DocCMiddleware.Configuration type in the library target as DocCConfiguration. --- .../Configurations/DoccConfiguration.swift | 50 ++++++++++++++++++ .../DoccMiddlewareConfiguration.swift | 52 ------------------- .../Public/Middlewares/DocCMiddleware.swift | 8 +-- .../Middlewares/DocCMiddlewareTests.swift | 9 ++-- 4 files changed, 57 insertions(+), 62 deletions(-) create mode 100644 Sources/HummingbirdDocC/Public/Configurations/DoccConfiguration.swift delete mode 100644 Sources/HummingbirdDocC/Public/Configurations/DoccMiddlewareConfiguration.swift diff --git a/Sources/HummingbirdDocC/Public/Configurations/DoccConfiguration.swift b/Sources/HummingbirdDocC/Public/Configurations/DoccConfiguration.swift new file mode 100644 index 0000000..760b784 --- /dev/null +++ b/Sources/HummingbirdDocC/Public/Configurations/DoccConfiguration.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 class NIOPosix.NIOThreadPool + +/// A type that contains all the parameters to configure the ``DocCMiddleware`` middleware. +public struct DocCConfiguration: Sendable { + + // MARK: Properties + + /// A path to the physical location where the `DocC` documentation containers are stored. + let folderRoot: String + + /// A URI path that prefixes the `DocC` documentation resources. + let uriRoot: String + + /// A type that define a mechanism to use in case some blocking work needs to be performed for which no non-blocking API exists. + let threadPool: NIOThreadPool + + // MARK: Initializers + + /// Initializes this configuration type. + /// + /// > important: It is assumed that both the `uriRoot` and the `folderRoot` parameters should not be empty, and that they should be prefixed + /// with the `/` forward slash character. + /// + /// - Parameters: + /// - uriRoot: A URI path that prefixes the `DocC` documentation resources. + /// - folderRoot: A path to the physical location where the `DocC` documentation containers are stored. + /// - threadPool: A type that define a mechanism to use in case some blocking work needs to be performed for which no non-blocking API exists. + public init( + uriRoot: String, + folderRoot: String, + threadPool: NIOThreadPool = .singleton + ) { + self.folderRoot = folderRoot + self.uriRoot = uriRoot + self.threadPool = threadPool + } + +} diff --git a/Sources/HummingbirdDocC/Public/Configurations/DoccMiddlewareConfiguration.swift b/Sources/HummingbirdDocC/Public/Configurations/DoccMiddlewareConfiguration.swift deleted file mode 100644 index 0bc36c4..0000000 --- a/Sources/HummingbirdDocC/Public/Configurations/DoccMiddlewareConfiguration.swift +++ /dev/null @@ -1,52 +0,0 @@ -// ===----------------------------------------------------------------------=== -// -// 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 NIOPosix.NIOThreadPool - -extension DocCMiddleware { - /// A type that contains all the parameters to configure the ``DocCMiddleware`` middleware. - public struct Configuration: Sendable { - - // MARK: Properties - - /// A path to the physical location where the `DocC` documentation containers are stored. - let folderRoot: String - - /// A URI path that prefixes the `DocC` documentation resources. - let uriRoot: String - - /// A type that define a mechanism to use in case some blocking work needs to be performed for which no non-blocking API exists. - let threadPool: NIOThreadPool - - // MARK: Initializers - - /// Initializes this configuration type. - /// - /// > important: It is assumed that both the `uriRoot` and the `folderRoot` parameters should not be empty, and that they should be prefixed - /// with the `/` forward slash character. - /// - /// - Parameters: - /// - uriRoot: A URI path that prefixes the `DocC` documentation resources. - /// - folderRoot: A path to the physical location where the `DocC` documentation containers are stored. - /// - threadPool: A type that define a mechanism to use in case some blocking work needs to be performed for which no non-blocking API exists. - public init( - uriRoot: String, - folderRoot: String, - threadPool: NIOThreadPool = .singleton - ) { - self.folderRoot = folderRoot - self.uriRoot = uriRoot - self.threadPool = threadPool - } - - } -} diff --git a/Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift b/Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift index 2f33c20..045bf60 100644 --- a/Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift +++ b/Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift @@ -46,9 +46,6 @@ public struct DocCMiddleware { // MARK: Properties - /// A type that contains the parameters to configure the middleware. - let configuration: Configuration - /// A type that conforms to a protocol that defines file system interactions. let fileProvider: FileSystemProvider @@ -74,7 +71,7 @@ public struct DocCMiddleware { /// - configuration: A type that contains the parameters to configure the middleware. /// - logger: A type that interacts with the logging system. public init( - configuration: Configuration, + configuration: DocCConfiguration, logger: Logger ) where FileSystemProvider == LocalFileSystem { self.init( @@ -94,12 +91,11 @@ public struct DocCMiddleware { /// - fileProvider: A type that conforms to the protocol that defines file system interactions. /// - logger: A type that interacts with the logging system. init( - configuration: Configuration, + configuration: DocCConfiguration, fileProvider: FileSystemProvider, logger: Logger, ) { self.logger = logger - self.configuration = configuration self.fileProvider = fileProvider self.prepareURIPath = .init(uriRoot: configuration.uriRoot) self.redirectURI = .init(logger: logger) diff --git a/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift b/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift index 704f7e5..965599e 100644 --- a/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift +++ b/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift @@ -20,6 +20,7 @@ import struct Hummingbird.LocalFileSystem import struct Hummingbird.Request import struct Logging.Logger +@testable import struct HummingbirdDocC.DocCConfiguration @testable import struct HummingbirdDocC.DocCMiddleware @Suite("DocC Middleware", .tags(.middleware)) @@ -290,7 +291,7 @@ private extension DocCMiddlewareTests { /// - configuration: A type that contains the parameters to configure the middleware. /// - logger: A type that interacts with the logging system. func assertInit( - configuration: DocCMiddleware.Configuration, + configuration: DocCConfiguration, logger: Logger = .test() ) { // GIVEN @@ -318,7 +319,7 @@ private extension DocCMiddlewareTests { /// - logger: A type that interacts with the logging system. /// - fileProvider: A type that conforms to the protocol that defines file system interactions, if any. func assertInit( - configuration: DocCMiddleware.Configuration, + configuration: DocCConfiguration, logger: Logger = .test(), fileProvider: FileSystemProvider ) { @@ -448,7 +449,7 @@ private extension DocCMiddlewareTests { default: .init(fileIdentifier: .init(), shouldLoadFile: false) } - let context: any RequestContext = RequestContextMock(logger: logger) + let context: RequestContextMock = .init(logger: logger) let request: Request = .test( method: .get, path: uriPath @@ -475,7 +476,7 @@ private extension DocCMiddlewareTests { .contentLength: (statusCode == .ok ? "36" : "0") ]) - let contentLength = try #require(result.body.contentLength) + let contentLength = #require(result.body.contentLength) if statusCode == .ok { #expect(contentLength > 0) -- 2.52.0 From 480cd657c92a3d44a19fb6aa9dbf48ba30b01aa5 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 29 Sep 2025 18:18:20 +0200 Subject: [PATCH 14/24] Implemented the Resource model in the library target. --- .../Internal/Extensions/String+Formats.swift | 2 + .../Internal/Models/Resource.swift | 55 +++++++ .../Tests/Internal/Models/ResourceTests.swift | 147 ++++++++++++++++++ .../Types/Extensions/Tag+Constants.swift | 2 + 4 files changed, 206 insertions(+) create mode 100644 Sources/HummingbirdDocC/Internal/Models/Resource.swift create mode 100644 Tests/HummingbirdDocC/Tests/Internal/Models/ResourceTests.swift diff --git a/Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift b/Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift index d425b2b..8c7dd24 100644 --- a/Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift +++ b/Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift @@ -31,6 +31,8 @@ extension String { static let forwardSlash = "%@/" /// A format pattern used to generate relative paths for index files. static let index = "%@/%@/index.html" + /// A format pattern used to generate relative paths for resources. + static let resource = "/%@/%@" /// A format pattern used to generate relative paths that starts with the `/` string. static let root = "/%@" } diff --git a/Sources/HummingbirdDocC/Internal/Models/Resource.swift b/Sources/HummingbirdDocC/Internal/Models/Resource.swift new file mode 100644 index 0000000..ee765e5 --- /dev/null +++ b/Sources/HummingbirdDocC/Internal/Models/Resource.swift @@ -0,0 +1,55 @@ +// ===----------------------------------------------------------------------=== +// +// 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 +// +// ===----------------------------------------------------------------------=== + +/// A model that encapsulates the information related to a resource in a given `DocC` documentation archive. +struct Resource { + + // MARK: Properties + + /// An archive name in which the resource belongs to. + let archiveName: String + + /// A relative URI path to the resource. + let relativePath: String + + // MARK: Initializers + + /// Initializes this resource. + /// - Parameters: + /// - archiveName: An archive name in which the resource belongs to. + /// - relativePath: A relative URI path to the resource. + init( + archiveName: String, + relativePath: String + ) { + self.archiveName = archiveName + self.relativePath = relativePath + } + + // MARK: Computed + + /// A relative URI path to a documentation archive the resource belongs to. + lazy var archivePath: String = { + .init(format: .Format.Path.archive, archiveName) + }() + + /// A reference name for the documentation archive the resource belongs to. + lazy var archiveReference: String = { + archiveName.lowercased() + }() + + /// A relative URI path to the resource in its documentation archive. + lazy var fullPath: String = { + .init(format: .Format.Path.archive, archiveName, relativePath) + }() + +} diff --git a/Tests/HummingbirdDocC/Tests/Internal/Models/ResourceTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Models/ResourceTests.swift new file mode 100644 index 0000000..1a96374 --- /dev/null +++ b/Tests/HummingbirdDocC/Tests/Internal/Models/ResourceTests.swift @@ -0,0 +1,147 @@ +// ===----------------------------------------------------------------------=== +// +// 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 + +@testable import HummingbirdDocC.Resource + +@Suite("Resource", .tags(.model)) +struct ResourceTests { + + // MARK: Properties tests + +#if swift(>=6.2) + @Test + func `archive path`() { + assertArchivePath( + archiveName: "SomeDocument", + expects: "/SomeDocument.doccarchive" + ) + } + + @Test + func `archive reference`() { + assertArchiveReference( + archiveName: "SomeDocument", + expects: "somedocument" + ) + } + + @Test + func `full path`() { + assertFullPath()( + archiveName: "SomeDocument", + relativePath: .uriResource, + expects: "/somedocument" + .uriResource + ) + } +#else + @Test("archive path") + func archivePath() { + assertArchivePath( + archiveName: "SomeDocument", + expects: "/SomeDocument.doccarchive" + ) + } + + @Test("archive reference") + func archiveReference() { + assertArchiveReference( + archiveName: "SomeDocument", + expects: "somedocument" + ) + } + + @Test("full path") + func fullPath() { + assertFullPath()( + archiveName: "SomeDocument", + relativePath: .uriResource, + expects: "/somedocument" + .uriResource + ) + } +#endif + +} + +// MARK: - Assertions + +private extension ResourceTests { + + // MARK: Functions + + /// Asserts the `archivePath` computed property of a resource. + /// - Parameters: + /// - archiveName: A name of the archive the resource belongs to. + /// - archivePath: An expected path to a documentation archive related to a given archive name. + func assertArchivePath( + archiveName: String, + expects archivePath: String + ) { + // GIVEN + let resource = Resource( + archiveName: archiveName, + relativePath: .empty + ) + + // WHEN + let result = resource.archivePath + + // THEN + #expect(result == archivePath) + } + + /// Asserts the `archiveReference` computed property of a resource. + /// - Parameters: + /// - archiveName: A name of the archive the resource belongs to. + /// - archiveReference: An expected reference related to a given archive name. + func assertArchiveReference( + archiveName: String, + expects archiveReference: String + ) { + // GIVEN + let resource = Resource( + archiveName: archiveName, + relativePath: .empty + ) + + // WHEN + let result = resource.archiveReference + + // THEN + #expect(result == archiveReference) + } + + /// Asserts the `fullPath` computed property of a resource. + /// - Parameters: + /// - archiveName: A name of the archive the resource belongs to. + /// - relativePath: A relative URI path to a resource. + /// - fullPath: An expected relative URI path to a resource in its documentation archive. + func assertFullPath( + archiveName: String, + relativePath: String, + expects fullPath: String + ) { + // GIVEN + let resource = Resource( + archiveName: archiveName, + relativePath: relativePath + ) + + // WHEN + let result = resource.fullPath + + // THEN + #expect(result == fullPath) + } + +} diff --git a/Tests/HummingbirdDocC/Types/Extensions/Tag+Constants.swift b/Tests/HummingbirdDocC/Types/Extensions/Tag+Constants.swift index 31b3733..3ec4dc1 100644 --- a/Tests/HummingbirdDocC/Types/Extensions/Tag+Constants.swift +++ b/Tests/HummingbirdDocC/Types/Extensions/Tag+Constants.swift @@ -22,6 +22,8 @@ extension Tag { @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 model type. + @Tag static var model: Self /// Tag that indicate a test case for a use case type. @Tag static var useCase: Self -- 2.52.0 From 8a8576ef5f77b3d5b630336532088acf9c9ff459 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 29 Sep 2025 19:44:11 +0200 Subject: [PATCH 15/24] Implemented the "subtract(_: )" function for the String+Helpers extension in the library target. --- .../Internal/Extensions/String+Helpers.swift | 43 ++++++++ .../Extensions/String_HelpersTests.swift | 97 +++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift create mode 100644 Tests/HummingbirdDocC/Tests/Internal/Extensions/String_HelpersTests.swift diff --git a/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift b/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift new file mode 100644 index 0000000..acd84cf --- /dev/null +++ b/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift @@ -0,0 +1,43 @@ +// ===----------------------------------------------------------------------=== +// +// 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 RegexBuilder + +extension String { + + /// Subtracts a prefix from a string. + /// - Parameters: + /// - prefix: A prefix to remove from a string. + /// - Returns: A new string with a prefix removed, if any. + func subtract( + _ prefix: String + ) -> String? { + let reference = Reference(String.self) + let pattern = Regex { + prefix + Optionally { + Capture(as: reference) { + OneOrMore(.anyNonNewline) + } transform: { output in + String(output) + } + } + } + + guard let matches = self.prefixMatch(of: pattern) else { + return nil + } + + return matches.output.1 + } + +} diff --git a/Tests/HummingbirdDocC/Tests/Internal/Extensions/String_HelpersTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Extensions/String_HelpersTests.swift new file mode 100644 index 0000000..f9d28db --- /dev/null +++ b/Tests/HummingbirdDocC/Tests/Internal/Extensions/String_HelpersTests.swift @@ -0,0 +1,97 @@ +// ===----------------------------------------------------------------------=== +// +// 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 + +@testable import HummingbirdDocC + +@Suite("String Helpers", .tags(.extension)) +struct String_HelpersTests { + + // MARK: Functions tests + +#if swift(>=6.2) + @Test(arguments: zip( + Input.prefixesToSubtract, + Output.prefixesToSubtract + )) + func `subtract`( + prefix: String, + expects newString: String? + ) { + assertSubtract( + string: .sample, + prefix: prefix, + expects: newString + ) + } +#else + @Test("subtract", arguments: zip( + Input.prefixesToSubtract, + Output.prefixesToSubtract + )) + func subtract( + prefix: String, + expects newString: String? + ) { + assertSubtract( + string: .sample, + prefix: prefix, + expects: newString + ) + } +#endif + +} + +// MARK: - Assertions + +private extension String_HelpersTests { + + // MARK: Functions + + /// Asserts a string subtraction. + /// - Parameters: + /// - string: A string from where the subtraction will occur. + /// - prefix: A prefix to subtract from a string. + /// - newString: An expected new string created out of the subtraction, if any. + func assertSubtract( + string: String, + prefix: String, + expects newString: String? + ) { + // GIVEN + // WHEN + let result = string.subtract(prefix) + + // THEN + #expect(result == newString) + } + +} + +// MARK: - Constants + +private extension Input { + /// A list of prefix strings. + static let prefixesToSubtract: [String] = ["Some", .sample, "some", "Else"] +} + +private extension Output { + /// A list of outcomes that are expected from subtracting the prefix substrings out of the sample string. + static let prefixesToSubtract: [String?] = ["thing", .empty, nil, nil] +} + +private extension String { + /// A sample string. + static let sample = "Something" +} -- 2.52.0 From df0f87626619e39ca6ec53bf595fbac9e46c7c96 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 29 Sep 2025 21:38:03 +0200 Subject: [PATCH 16/24] Updated the name of the DocC documentation catalog in the library target to "HummingbirdDocC". --- .../{DocCMiddleware.docc => HummingbirdDocC.docc}/Library.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Sources/HummingbirdDocC/Catalogs/{DocCMiddleware.docc => HummingbirdDocC.docc}/Library.md (92%) diff --git a/Sources/HummingbirdDocC/Catalogs/DocCMiddleware.docc/Library.md b/Sources/HummingbirdDocC/Catalogs/HummingbirdDocC.docc/Library.md similarity index 92% rename from Sources/HummingbirdDocC/Catalogs/DocCMiddleware.docc/Library.md rename to Sources/HummingbirdDocC/Catalogs/HummingbirdDocC.docc/Library.md index 83274a1..337e27f 100644 --- a/Sources/HummingbirdDocC/Catalogs/DocCMiddleware.docc/Library.md +++ b/Sources/HummingbirdDocC/Catalogs/HummingbirdDocC.docc/Library.md @@ -1,4 +1,4 @@ -# ``DocCMiddleware`` +# ``HummingbirdDocC`` Summary -- 2.52.0 From 2f03908cc717c7ef0ca865e03ce33b293e2d2b73 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 29 Sep 2025 21:38:47 +0200 Subject: [PATCH 17/24] Updated the tasks in the Makefile file that are related to the DocC documentation generation and preview. --- .env | 9 ++++++--- Makefile | 34 ++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/.env b/.env index 514675a..d284bd8 100644 --- a/.env +++ b/.env @@ -12,10 +12,13 @@ # --- DOCUMENTATION --- -DOCC_GITHUB_OUTPUT=./docs +DOCC_ARCHIVE_BASE_PATH=archives/${DOCC_ARCHIVE_REFERENCE} +DOCC_ARCHIVE_OUTPUT=./${SPM_LIBRARY_TARGET}.doccarchive +DOCC_ARCHIVE_REFERENCE=hummingbirddocc +DOCC_CONFIG_MINIMUM_ACCESS_LEVEL=public +DOCC_CONFIG_PREVIEW_URL=http://localhost:8080/documentation/${DOCC_ARCHIVE_REFERENCE} DOCC_GITHUB_BASE_PATH=hummingbird-docc -DOCC_PREVIEW_URL=http://localhost:8080/documentation/hummingbirddocc -DOCC_XCODE_OUTPUT=./${SPM_LIBRARY_TARGET}.doccarchive +DOCC_GITHUB_OUTPUT=./docs # -- SWIFT PACKAGE MANAGER --- diff --git a/Makefile b/Makefile index e2eb043..7180b6d 100644 --- a/Makefile +++ b/Makefile @@ -49,8 +49,8 @@ pkg-reset: ## Resets the complete SPM cache/build folder of the package @swift package reset pkg-pristine: pkg-clean pkg-reset ## Deletes all built artifacts, caches, and documentations of the package + @rm -drf $(DOCC_ARCHIVE_OUTPUT) @rm -drf $(DOCC_GITHUB_OUTPUT) - @rm -drf $(DOCC_XCODE_OUTPUT) pkg-outdated: ## Lists the SPM package dependencies that can be updated @swift package update --dry-run @@ -60,7 +60,18 @@ pkg-update: ## Updates the SPM package dependencies # DOCUMENTATION -doc-generate: doc-generate-xcode doc-generate-github ## Generates the library documentation for both Github and Xcode +doc-generate: doc-generate-archive doc-generate-github ## Generates the library documentation for both Github and Xcode + +doc-generate-archive: ## Generates the library documentation archive for Xcode + @swift package \ + --allow-writing-to-directory $(DOCC_ARCHIVE_OUTPUT) \ + generate-documentation \ + --target $(SPM_LIBRARY_TARGET) \ + --hosting-base-path $(DOCC_ARCHIVE_BASE_PATH) \ + --output-path $(DOCC_ARCHIVE_OUTPUT) \ + --symbol-graph-minimum-access-level $(DOCC_CONFIG_MINIMUM_ACCESS_LEVEL) \ + --include-extended-types \ + --enable-inherited-docs doc-generate-github: ## Generates the library documentation for Github @swift package \ @@ -70,21 +81,20 @@ doc-generate-github: ## Generates the library documentation for Github --disable-indexing \ --transform-for-static-hosting \ --hosting-base-path $(DOCC_GITHUB_BASE_PATH) \ - --output-path $(DOCC_GITHUB_OUTPUT) - -doc-generate-xcode: ## Generates the library documentation for Xcode - @swift package \ - --allow-writing-to-directory $(DOCC_XCODE_OUTPUT) \ - generate-documentation \ - --target $(SPM_LIBRARY_TARGET) \ - --output-path $(DOCC_XCODE_OUTPUT) + --output-path $(DOCC_GITHUB_OUTPUT) \ + --symbol-graph-minimum-access-level $(DOCC_CONFIG_MINIMUM_ACCESS_LEVEL) \ + --include-extended-types \ + --enable-inherited-docs doc-preview: ## Previews the library documentation in Safari - @open -a safari $(DOCC_PREVIEW_URL) + @open -a safari $(DOCC_CONFIG_PREVIEW_URL) @swift package \ --disable-sandbox \ preview-documentation \ - --target $(SPM_LIBRARY_TARGET) + --target $(SPM_LIBRARY_TARGET) \ + --symbol-graph-minimum-access-level $(DOCC_CONFIG_MINIMUM_ACCESS_LEVEL) \ + --include-extended-types \ + --enable-inherited-docs # IDE -- 2.52.0 From 6b3cc44a152f6d46bb96d150a846f7a7e6992a54 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 30 Sep 2025 14:54:00 +0200 Subject: [PATCH 18/24] Made the "logLevel" property for the AppArguments protocol in the sample app target non-optional. --- Samples/HummingbirdDocC/App/Parameters/SampleAppArguments.swift | 2 +- Samples/HummingbirdDocC/App/Protocols/AppArguments.swift | 2 +- Samples/HummingbirdDocC/App/SampleApp.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Samples/HummingbirdDocC/App/Parameters/SampleAppArguments.swift b/Samples/HummingbirdDocC/App/Parameters/SampleAppArguments.swift index 86afa3d..f0ecde9 100644 --- a/Samples/HummingbirdDocC/App/Parameters/SampleAppArguments.swift +++ b/Samples/HummingbirdDocC/App/Parameters/SampleAppArguments.swift @@ -38,6 +38,6 @@ extension SampleApp { name: .long, help: "A log level to configure in a type that interacts with the logging system." ) - var logLevel: Logger.Level? + var logLevel: Logger.Level = .trace } } diff --git a/Samples/HummingbirdDocC/App/Protocols/AppArguments.swift b/Samples/HummingbirdDocC/App/Protocols/AppArguments.swift index 9afafe4..7fbb496 100644 --- a/Samples/HummingbirdDocC/App/Protocols/AppArguments.swift +++ b/Samples/HummingbirdDocC/App/Protocols/AppArguments.swift @@ -26,7 +26,7 @@ protocol AppArguments { var port: Int { get } /// A log level to configure in a type that interacts with the logging system. - var logLevel: Logger.Level? { get } + var logLevel: Logger.Level { get } } diff --git a/Samples/HummingbirdDocC/App/SampleApp.swift b/Samples/HummingbirdDocC/App/SampleApp.swift index fc5ae0e..5d35bc7 100644 --- a/Samples/HummingbirdDocC/App/SampleApp.swift +++ b/Samples/HummingbirdDocC/App/SampleApp.swift @@ -35,7 +35,7 @@ extension SampleApp: AsyncParsableCommand { let builder = AppBuilder(logger: { var logger = Logger(label: "sample.hummingbird-docc.logger") - logger.logLevel = arguments.logLevel ?? .trace + logger.logLevel = arguments.logLevel return logger }()) -- 2.52.0 From 790433f1a78128ab972fa4c35267f3f9c15c8727 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 30 Sep 2025 15:30:28 +0200 Subject: [PATCH 19/24] Removed the unnecessary "fullPath" computed property from the Resource model in the library target. --- .../Internal/Extensions/String+Formats.swift | 2 - .../Internal/Extensions/String+Helpers.swift | 2 +- .../Internal/Models/Resource.swift | 15 ++---- .../Tests/Internal/Models/ResourceTests.swift | 47 ++----------------- 4 files changed, 9 insertions(+), 57 deletions(-) diff --git a/Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift b/Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift index 8c7dd24..d425b2b 100644 --- a/Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift +++ b/Sources/HummingbirdDocC/Internal/Extensions/String+Formats.swift @@ -31,8 +31,6 @@ extension String { static let forwardSlash = "%@/" /// A format pattern used to generate relative paths for index files. static let index = "%@/%@/index.html" - /// A format pattern used to generate relative paths for resources. - static let resource = "/%@/%@" /// A format pattern used to generate relative paths that starts with the `/` string. static let root = "/%@" } diff --git a/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift b/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift index acd84cf..8dfc996 100644 --- a/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift +++ b/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift @@ -37,7 +37,7 @@ extension String { return nil } - return matches.output.1 + return matches.output.1 ?? .empty } } diff --git a/Sources/HummingbirdDocC/Internal/Models/Resource.swift b/Sources/HummingbirdDocC/Internal/Models/Resource.swift index ee765e5..db07960 100644 --- a/Sources/HummingbirdDocC/Internal/Models/Resource.swift +++ b/Sources/HummingbirdDocC/Internal/Models/Resource.swift @@ -11,7 +11,7 @@ // ===----------------------------------------------------------------------=== /// A model that encapsulates the information related to a resource in a given `DocC` documentation archive. -struct Resource { +struct Resource: Equatable { // MARK: Properties @@ -38,18 +38,13 @@ struct Resource { // MARK: Computed /// A relative URI path to a documentation archive the resource belongs to. - lazy var archivePath: String = { + var archivePath: String { .init(format: .Format.Path.archive, archiveName) - }() + } /// A reference name for the documentation archive the resource belongs to. - lazy var archiveReference: String = { + var archiveReference: String { archiveName.lowercased() - }() - - /// A relative URI path to the resource in its documentation archive. - lazy var fullPath: String = { - .init(format: .Format.Path.archive, archiveName, relativePath) - }() + } } diff --git a/Tests/HummingbirdDocC/Tests/Internal/Models/ResourceTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Models/ResourceTests.swift index 1a96374..a0da9e1 100644 --- a/Tests/HummingbirdDocC/Tests/Internal/Models/ResourceTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Models/ResourceTests.swift @@ -12,7 +12,7 @@ import Testing -@testable import HummingbirdDocC.Resource +@testable import struct HummingbirdDocC.Resource @Suite("Resource", .tags(.model)) struct ResourceTests { @@ -35,15 +35,6 @@ struct ResourceTests { expects: "somedocument" ) } - - @Test - func `full path`() { - assertFullPath()( - archiveName: "SomeDocument", - relativePath: .uriResource, - expects: "/somedocument" + .uriResource - ) - } #else @Test("archive path") func archivePath() { @@ -60,15 +51,6 @@ struct ResourceTests { expects: "somedocument" ) } - - @Test("full path") - func fullPath() { - assertFullPath()( - archiveName: "SomeDocument", - relativePath: .uriResource, - expects: "/somedocument" + .uriResource - ) - } #endif } @@ -88,7 +70,7 @@ private extension ResourceTests { expects archivePath: String ) { // GIVEN - let resource = Resource( + var resource = Resource( archiveName: archiveName, relativePath: .empty ) @@ -109,7 +91,7 @@ private extension ResourceTests { expects archiveReference: String ) { // GIVEN - let resource = Resource( + var resource = Resource( archiveName: archiveName, relativePath: .empty ) @@ -120,28 +102,5 @@ private extension ResourceTests { // THEN #expect(result == archiveReference) } - - /// Asserts the `fullPath` computed property of a resource. - /// - Parameters: - /// - archiveName: A name of the archive the resource belongs to. - /// - relativePath: A relative URI path to a resource. - /// - fullPath: An expected relative URI path to a resource in its documentation archive. - func assertFullPath( - archiveName: String, - relativePath: String, - expects fullPath: String - ) { - // GIVEN - let resource = Resource( - archiveName: archiveName, - relativePath: relativePath - ) - - // WHEN - let result = resource.fullPath - - // THEN - #expect(result == fullPath) - } } -- 2.52.0 From 2c3474a1b8b58ae4a9bb85374be6e5c53b115cf4 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 30 Sep 2025 15:49:53 +0200 Subject: [PATCH 20/24] Added the "init(uriRoot:)" initializer function for the CheckURIUseCase use case in the library target and also, improved its test cases. --- .../Internal/Use Cases/CheckURIUseCase.swift | 20 ++- .../Use Cases/CheckURIUseCaseTests.swift | 157 +++++++++++------- 2 files changed, 112 insertions(+), 65 deletions(-) diff --git a/Sources/HummingbirdDocC/Internal/Use Cases/CheckURIUseCase.swift b/Sources/HummingbirdDocC/Internal/Use Cases/CheckURIUseCase.swift index 81ddf5e..6c72f94 100644 --- a/Sources/HummingbirdDocC/Internal/Use Cases/CheckURIUseCase.swift +++ b/Sources/HummingbirdDocC/Internal/Use Cases/CheckURIUseCase.swift @@ -14,6 +14,22 @@ import struct HummingbirdCore.URI /// A use case that checks whether a given URI against a set of conditions, to determine whether the URI could be used by the middleware or not. struct CheckURIUseCase { + + // MARK: Properties + + /// A root path that prefixes the documentation resource. + private let uriRoot: String + + // MARK: Initializers + + /// Initializes this use case. + /// + /// > important: It is assumed that the `uriRoot` parameter is not empty and that it is prefixed by the `/` character. + /// + /// - Parameter uriRoot: A root path that prefixes the documentation resource. + init(uriRoot: String) { + self.uriRoot = uriRoot + } // MARK: Functions @@ -23,8 +39,8 @@ struct CheckURIUseCase { func callAsFunction(_ uri: URI) -> String? { guard let uriPath = uri.path.removingPercentEncoding, - !uriPath.contains(.Path.previousFolder), - uriPath.hasPrefix(.Path.forwardSlash) + uriPath.hasPrefix(uriRoot), + !uriPath.contains(.Path.previousFolder) else { return nil } diff --git a/Tests/HummingbirdDocC/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift index 083697c..8efd8b2 100644 --- a/Tests/HummingbirdDocC/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/CheckURIUseCaseTests.swift @@ -18,99 +18,130 @@ import struct HummingbirdCore.URI @Suite("Check URI use case", .tags(.useCase)) struct CheckURIUseCaseTests { - - // MARK: Properties - - private let useCase: CheckURIUseCase = .init() - + // MARK: Use case tests -#if swift(>=6.2) - @Test(arguments: zip( - Input.nonEncodedURIs, - Output.nonEncodedURIs - )) - func `check non encoded URIs`( - uri uriPath: String, - expects result: String? - ) { - assertURI(uriPath, expects: result) - } - - @Test(arguments: zip( - Input.percentEncodedURIs, - Output.percentEncodedURIs - )) - func `check percent-encoded URIs`( - uri uriPath: String, - expects result: String? - ) { - assertURI(uriPath, expects: result) - } -#else - @Test("check non-encoded URIs", arguments: zip( - Input.nonEncodedURIs, - Output.nonEncodedURIs - )) - func check_nonEncodedURIs( - uri uriPath: String, - expects result: String? - ) { - assertURI(uriPath, expects: result) - } - - @Test("check percent-encoded URIs", arguments: zip( - Input.percentEncodedURIs, - Output.percentEncodedURIs - )) - func check_percentEncodedURIs( - uri uriPath: String, - expects result: String? - ) { - assertURI(uriPath, expects: result) - } -#endif + #if swift(>=6.2) + @Test( + arguments: zip( + Input.nonEncodedURIs, + Output.nonEncodedURIs + ) + ) + func `check non encoded URIs`( + uri uriPath: String, + expects result: String? + ) { + assertURI(uriPath, expects: result) + } + + @Test( + arguments: zip( + Input.percentEncodedURIs, + Output.percentEncodedURIs + ) + ) + func `check percent-encoded URIs`( + uri uriPath: String, + expects result: String? + ) { + assertURI(uriPath, expects: result) + } + #else + @Test( + "check non-encoded URIs", + arguments: zip( + Input.nonEncodedURIs, + Output.nonEncodedURIs + ) + ) + func check_nonEncodedURIs( + uri uriPath: String, + expects result: String? + ) { + assertURI(uriPath, expects: result) + } + + @Test( + "check percent-encoded URIs", + arguments: zip( + Input.percentEncodedURIs, + Output.percentEncodedURIs + ) + ) + func check_percentEncodedURIs( + uri uriPath: String, + expects result: String? + ) { + assertURI(uriPath, expects: result) + } + #endif } // MARK: - Assertions -private extension CheckURIUseCaseTests { - +extension CheckURIUseCaseTests { + // MARK: Functions - + /// Asserts a URI path provided by the ``CheckURIPathUseCase`` use case based on a given path and an expected result. /// - Parameters: /// - uriPath: A URI path to use with a URI type. + /// - uriRoot: A URI path that prefixes the `DocC` documentation resources. /// - result: An expected result coming out of the use case. - func assertURI( + fileprivate func assertURI( _ uriPath: String, + uriRoot: String = .Sample.uriRoot, expects result: String? ) { // GIVEN + let useCase = CheckURIUseCase(uriRoot: uriRoot) let uri = URI(uriPath) - + // WHEN let output = useCase(uri) - + // THEN #expect(output == result) } - + } // MARK: - Constants -private extension Input { +extension Input { /// A list of non-encoded URI samples. - static let nonEncodedURIs: [String] = ["/", "/some/known/path", "", "/some/../path", "some/other/path"] + fileprivate static let nonEncodedURIs: [String] = [ + .Sample.uriRoot + .empty, + .Sample.uriRoot + .Path.forwardSlash, + .Sample.uriRoot + "/some/known/path", + .Sample.uriRoot + "/some/../path", + "some/other/root/some/known/path", + ] /// A list of percent-encoded URI samples. - static let percentEncodedURIs: [String] = ["%2F", "/some%2Fknown%3Fpath", "%20", "/some/%2E%2E/path", "some%2Fother%3Fpath"] + fileprivate static let percentEncodedURIs: [String] = [ + .Sample.uriRoot + "%2F", + .Sample.uriRoot + "/some%2Fknown%3Fpath", + .Sample.uriRoot + "/some/%2E%2E/path", + "some/other%2Froot/some%2Fknown%3Fpath", + ] } -private extension Output { +extension Output { /// A list of expected outputs for the non-encoded URI samples. - static let nonEncodedURIs: [String?] = ["/", "/some/known/path", "/", nil, nil] + fileprivate static let nonEncodedURIs: [String?] = [ + .Sample.uriRoot, + .Sample.uriRoot + .Path.forwardSlash, + .Sample.uriRoot + "/some/known/path", + nil, + nil, + ] /// A list of expected outputs for the percent-encoded URI samples. - static let percentEncodedURIs: [String?] = ["/", "/some/known?path", nil, nil, nil] + fileprivate static let percentEncodedURIs: [String?] = [ + .Sample.uriRoot + .Path.forwardSlash, + .Sample.uriRoot + "/some/known?path", + nil, + nil, + ] } -- 2.52.0 From 0fea58d295c7eb3587bd4e3279b85b6a34083df2 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 30 Sep 2025 16:34:48 +0200 Subject: [PATCH 21/24] Improved the "callAsFunction(_: )" function for the PrepareURIPathUseCase use case in the library target. --- .../Internal/Extensions/String+Helpers.swift | 7 +- .../Use Cases/PrepareURIPathUseCase.swift | 117 +++++++++--------- .../PrepareURIPathUseCaseTests.swift | 48 +++---- 3 files changed, 82 insertions(+), 90 deletions(-) diff --git a/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift b/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift index 8dfc996..dc00ff3 100644 --- a/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift +++ b/Sources/HummingbirdDocC/Internal/Extensions/String+Helpers.swift @@ -36,8 +36,11 @@ extension String { guard let matches = self.prefixMatch(of: pattern) else { return nil } - - return matches.output.1 ?? .empty + guard let subtracted = matches.output.1 else { + return .empty + } + + return subtracted } } diff --git a/Sources/HummingbirdDocC/Internal/Use Cases/PrepareURIPathUseCase.swift b/Sources/HummingbirdDocC/Internal/Use Cases/PrepareURIPathUseCase.swift index 9611a9b..d5f9b38 100644 --- a/Sources/HummingbirdDocC/Internal/Use Cases/PrepareURIPathUseCase.swift +++ b/Sources/HummingbirdDocC/Internal/Use Cases/PrepareURIPathUseCase.swift @@ -13,17 +13,12 @@ import Foundation import RegexBuilder -/// A use case that extracts data from a given URI path, essential for routing the documentation contents. +/// A use case that extracts a resource's information from a given URI path, essential for routing the documentation contents. struct PrepareURIPathUseCase { - // MARK: Type aliases - - /// A pseudo-type that contains the archive name, reference and URI path, plus the resource URI and relative paths used for routing the documentation contents. - typealias PreparedURIPaths = (archiveName: String, archiveReference: String, archivePath: String, resourcePath: String) - // MARK: Properties - /// A root path that suffixes the documentation resource. + /// A root path that prefixes the documentation resource. private let uriRoot: String // MARK: Initializers @@ -39,44 +34,26 @@ struct PrepareURIPathUseCase { // MARK: Functions - /// Extracts some necessary data essential for documentation contents routing from a given URI path. - /// - /// The necessary data to extract from a given URI path is: - /// 1. the `DocC` documentation archive name; - /// 2. the `DocC` documentation archive reference; - /// 3. the `DocC` documentation archive URI path; - /// 4. the `DocC` documentation resource URI path. - /// + /// Extracts resource's information that is essential for documentation contents routing from a given URI path. + /// > important: It is assumed that the `uriPath` parameter is a URI path that does not contain any percent encoded strings. /// /// - Parameter uriPath: A URI path to extract the data from. - /// - Returns: A pseudo-type that contains the archive' name, reference and URI path, plus the resource URI paths. - func callAsFunction(_ uriPath: String) -> PreparedURIPaths? { - guard let uriRest = restOfURIPath(from: uriPath) else { + /// - Returns: A resource type containing all the relevant information + func callAsFunction(_ uriPath: String) -> Resource? { + guard let uriRest = uriRest(from: uriPath) else { return nil } - let archiveName = uriRest - .split(separator: .Path.forwardSlash) - .map(String.init) - .first + let archiveName = archiveName(from: uriRest) - let archiveReference: String = if let archiveName { - archiveName.lowercased() - } else { - .empty - } - let archivePath: String = if let archiveName { - .init(format: .Format.Path.archive, archiveName) - } else { - .empty + guard let relativePath = relativePath(from: uriRest, at: archiveName) else { + return nil } - return ( - archiveName ?? .empty, - archiveReference, - archivePath, - uriRest + return .init( + archiveName: archiveName, + relativePath: relativePath ) } @@ -88,40 +65,60 @@ private extension PrepareURIPathUseCase { // MARK: Functions + /// Extracts the archive name from a given URI path. + /// + /// > important: It is assumed that a given URI path is a relative URI path is prefixed with the `/` character and that contains the name of a `DocC` + /// documentation archive in its first component. + /// + /// - Parameter uriPath: A relative URI path to extract the archive name from. + /// - Returns: An archive named extracted from a given URI path. + func archiveName(from uriPath: String) -> String { + uriPath + .split(separator: .Path.forwardSlash) + .map(String.init) + .first ?? .empty + } + /// Extracts the rest of the URI path from a given URI path against a defined URI root path. - /// - /// A given URI path is matched against a regular expression, which is generated from a provided URI root path. - /// So this function would return either a string that represents a partial URI path, or a `nil` instance depending the result of the match between - /// the URI path and the regular expression: - /// * A `nil` instance in case there is no match; - /// * A `/` string in case there is a perfect match; - /// * A partial URI path prefixed with the `/` character in case there is an offset in the match. - /// /// - Parameter uriPath: A URI path to get the rest of the URI path from. /// - Returns: A rest of the URI path prefixed by the `/`character in case where there is any offset path after extracting the root path from the given URI path or not. Otherwise, a `nil` value is returned. - func restOfURIPath(from uriPath: String) -> String? { - let restReference = Reference(String.self) - let uriPattern = Regex { - uriRoot - Optionally { - Capture(as: restReference) { - OneOrMore(.anyNonNewline) - } transform: { output in - String(output) - } - } - } - - guard let matches = uriPath.prefixMatch(of: uriPattern) else { + func uriRest(from uriPath: String) -> String? { + guard let uriRest = uriPath.subtract(uriRoot) else { return nil } - guard let uriRest = matches.output.1 else { - return .Path.forwardSlash + guard !uriRest.isEmpty else { + return uriRest } guard uriRest.hasPrefix(.Path.forwardSlash) else { return .init(format: .Format.Path.root, uriRest) } + return uriRest } + /// Extracts the relative URI path from a given URI path against the name of a documentation archive. + /// - Parameters: + /// - uriPath: A URI path to get the relative URI path from. + /// - archive: An archive name to subtract from a URI path. + /// - Returns: A relative URI path prefixed by the `/`character in case where there is any offset path after extracting the root path from the given URI path or not. Otherwise, a `nil` value is returned. + func relativePath( + from uriPath: String, + at archive: String + ) -> String? { + guard !archive.isEmpty else { + return .Path.forwardSlash + } + + let archivePath: String = .init(format: .Format.Path.root, archive) + + guard let relativePath = uriPath.subtract(archivePath) else { + return nil + } + guard relativePath != .empty else { + return .empty + } + + return relativePath + } + } diff --git a/Tests/HummingbirdDocC/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift index aa90000..53aafb2 100644 --- a/Tests/HummingbirdDocC/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift +++ b/Tests/HummingbirdDocC/Tests/Internal/Use Cases/PrepareURIPathUseCaseTests.swift @@ -12,6 +12,8 @@ import Testing +import struct HummingbirdDocC.Resource + @testable import struct HummingbirdDocC.PrepareURIPathUseCase @Suite("Prepare URI Path Use Case", .tags(.useCase)) @@ -26,7 +28,7 @@ struct PrepareURIPathUseCaseTests { )) func `extract data with URI root not suffixed with forward slash`( uri uriPath: String, - expects result: PrepareURIPathUseCase.PreparedURIPaths? + expects result: Resource? ) throws { try assertData( uriRoot: .uriRoot, @@ -41,7 +43,7 @@ struct PrepareURIPathUseCaseTests { )) func `extract data with URI root suffixed with forward slash`( uri uriPath: String, - expects result: PrepareURIPathUseCase.PreparedURIPaths? + expects result: Resource? ) throws { try assertData( uriRoot: .uriRootSlashed, @@ -56,7 +58,7 @@ struct PrepareURIPathUseCaseTests { )) func data_withURIRoot_notSuffixed_withForwardSlash( uri uriPath: String, - expects result: PrepareURIPathUseCase.PreparedURIPaths? + expects result: Resource? ) throws { try assertData( uriRoot: .uriRoot, @@ -71,7 +73,7 @@ struct PrepareURIPathUseCaseTests { )) func data_withURIRoot_suffixed_withForwardSlash( uri uriPath: String, - expects result: PrepareURIPathUseCase.PreparedURIPaths? + expects result: Resource? ) throws { try assertData( uriRoot: .uriRootSlashed, @@ -94,30 +96,20 @@ private extension PrepareURIPathUseCaseTests { /// - Parameters: /// - uriRoot: A URI path to initialize the use case with. /// - uriPath: A URI path to use with the use case. - /// - result: An expected result coming out of the use case. + /// - resource: An expected resource coming out of the use case, if any. func assertData( uriRoot: String, uriPath: String, - expects result: PrepareURIPathUseCase.PreparedURIPaths? + expects resource: Resource? ) throws { // GIVEN let useCase = PrepareURIPathUseCase(uriRoot: uriRoot) // WHEN - let output = useCase(uriPath) + let result = useCase(uriPath) // THEN - if !uriPath.contains(uriRoot) { - #expect(output == nil) - } else { - #expect(output != nil) - - let data = try #require(output) - - #expect(data.archiveName == result?.archiveName) - #expect(data.archivePath == result?.archivePath) - #expect(data.resourcePath == result?.resourcePath) - } + #expect(result == resource) } } @@ -126,29 +118,29 @@ private extension PrepareURIPathUseCaseTests { private extension Input { /// A list of URI paths to match against the root URI path not suffixed with a forward slash. - static let prepareURIPaths: [String] = [.uriOffset, .uriRoot, .uriOther] + static let prepareURIPaths: [String] = [.uriRoot, .uriOffset, .uriOther] /// A list of URI paths to match against the root URI path suffixed with a forward slash. - static let prepareURIPathsSlashed: [String] = [.uriOffsetSlashed, .uriRootSlashed, .uriOther] + static let prepareURIPathsSlashed: [String] = [.uriRootSlashed, .uriOffsetSlashed, .uriOther] } private extension Output { /// A list of expected outputs for the URI path samples, regardless their match against suffixed or not suffixed root URI paths. - static let prepareURIPaths: [PrepareURIPathUseCase.PreparedURIPaths?] = [ - ("SomeArchive", "somearchive", "/SomeArchive.doccarchive", "/SomeArchive/some/content/path"), - (.empty, .empty, .empty, .Path.forwardSlash), + static let prepareURIPaths: [Resource?] = [ + .init(archiveName: .empty, relativePath: .Path.forwardSlash), + .init(archiveName: "SomeArchive", relativePath: "/some/content/path"), nil ] } private extension String { /// A root URI path to initialize the use case with. - static let uriRoot: Self = "/some/path" + static let uriRoot = "/some/path" /// A root URI path suffixed with a forward slash to initialize the use case with. - static let uriRootSlashed: Self = "/some/path/" + static let uriRootSlashed = "/some/path/" /// A URI path prefixed with a root URI path not suffixed with a forward slash. - static let uriOffset: Self = .uriRoot + "/SomeArchive/some/content/path" + static let uriOffset = .uriRoot + "/SomeArchive/some/content/path" /// A URI path prefixed with a root URI path suffixed with a forward slash. - static let uriOffsetSlashed: Self = .uriRootSlashed + "SomeArchive/some/content/path" + static let uriOffsetSlashed = .uriRootSlashed + "SomeArchive/some/content/path" /// A URI path not related to any root URI path. - static let uriOther: Self = "/some/other/path" + static let uriOther = "/some/other/path" } -- 2.52.0 From 4dd7e62560a5b8fd38de51f9679618c86071703e Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 30 Sep 2025 17:12:45 +0200 Subject: [PATCH 22/24] Improved the overall implementation for the DocCMiddleware middleware in the library target. --- .../Public/Middlewares/DocCMiddleware.swift | 144 +++++++++--------- .../Middlewares/DocCMiddlewareTests.swift | 56 +++---- 2 files changed, 104 insertions(+), 96 deletions(-) diff --git a/Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift b/Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift index 045bf60..67712da 100644 --- a/Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift +++ b/Sources/HummingbirdDocC/Public/Middlewares/DocCMiddleware.swift @@ -23,26 +23,28 @@ import struct Logging.Logger /// /// This middleware routes the contents of a `DocC` documentation container, defined by its resource URI paths, following these rules: /// -/// 1. *Redirects the URI path `/` to the path `//`*; -/// 2. *Redirects the URI path `//` to the path `//documentation`* -/// 3. *Redirects the URI path `//documentation` to the path `//documentation/`* -/// 4. *Redirects the URI path `//tutorials` to the path `//tutorials/`* -/// 5. *Redirects the URI path `//documentation/` to the resource on `/.doccarchive/documentation//index.html`* -/// 6. *Redirects the URI path `//tutorials/` to the resource on `/.doccarchive/tutorials//index.html`* -/// 7. *Redirects the URI path `//data/documentation.json` to the resource on `/.doccarchive/data/documentation/.json`* -/// 8. *Redirects the URI path `//favicon.ico` to the resource on `/.doccarchive/favicon.ico`* -/// 9. *Redirects the URI path `//favicon.svg` to the resource on `/.doccarchive/favicon.svg`* -/// 10. *Redirects the URI path `//theme-settings.json` to the resource on `/.doccarchive/theme-settings.json`* -/// 11. *Redirects the URI path `//css/` to the resource on `/.doccarchive/css/`* -/// 12. *Redirects the URI path `//data/` to the resource on `/.doccarchive/data/`* -/// 13. *Redirects the URI path `//downloads/` to the resource on `/.doccarchive/downloads/`* -/// 14. *Redirects the URI path `//images/` to the resource on `/.doccarchive/images/`* -/// 15. *Redirects the URI path `//img/` to the resource on `/.doccarchive/img/`* -/// 16. *Redirects the URI path `//index/` to the resource on `/.doccarchive/index/`* -/// 17. *Redirects the URI path `//js/` to the resource on `/.doccarchive/js/`* -/// 18. *Redirects the URI path `//videos/` to the resource on `/.doccarchive/videos/`* +/// 1. *Redirects the URI path `/` or `/` to the path `//`*; +/// 2. *Redirects the URI path `//documentation` to the path `//documentation/`* +/// 3. *Redirects the URI path `//tutorials` to the path `//tutorials/`* +/// 4. *Redirects the URI path `//documentation/` to the resource on `/.doccarchive/documentation//index.html`* +/// 5. *Redirects the URI path `//tutorials/` to the resource on `/.doccarchive/tutorials//index.html`* +/// 6. *Redirects the URI path `//data/documentation.json` to the resource on `/.doccarchive/data/documentation/.json`* +/// 7. *Redirects the URI path `//favicon.ico` to the resource on `/.doccarchive/favicon.ico`* +/// 8. *Redirects the URI path `//favicon.svg` to the resource on `/.doccarchive/favicon.svg`* +/// 9. *Redirects the URI path `//theme-settings.json` to the resource on `/.doccarchive/theme-settings.json`* +/// 10. *Redirects the URI path `//css/` to the resource on `/.doccarchive/css/`* +/// 11. *Redirects the URI path `//data/` to the resource on `/.doccarchive/data/`* +/// 12. *Redirects the URI path `//downloads/` to the resource on `/.doccarchive/downloads/`* +/// 13. *Redirects the URI path `//images/` to the resource on `/.doccarchive/images/`* +/// 14. *Redirects the URI path `//img/` to the resource on `/.doccarchive/img/`* +/// 15. *Redirects the URI path `//index/` to the resource on `/.doccarchive/index/`* +/// 16. *Redirects the URI path `//js/` to the resource on `/.doccarchive/js/`* +/// 17. *Redirects the URI path `//videos/` to the resource on `/.doccarchive/videos/`* /// -public struct DocCMiddleware { +public struct DocCMiddleware< + Context: RequestContext, + FileSystemProvider: FileProvider +> { // MARK: Properties @@ -53,7 +55,7 @@ public struct DocCMiddleware { let logger: Logger /// A use case that checks whether a received URI could be processed or not. - private let checkURI: CheckURIUseCase = .init() + private let checkURI: CheckURIUseCase /// A use case that extracts data from a given URI path, essential for routing the documentation contents. private let prepareURIPath: PrepareURIPathUseCase @@ -97,6 +99,7 @@ public struct DocCMiddleware { ) { self.logger = logger self.fileProvider = fileProvider + self.checkURI = .init(uriRoot: configuration.uriRoot) self.prepareURIPath = .init(uriRoot: configuration.uriRoot) self.redirectURI = .init(logger: logger) self.serveURI = .init( @@ -105,95 +108,98 @@ public struct DocCMiddleware { ) } + // MARK: Computed + + /// A list of relative root URI paths to match against the relative path of a resource. + var rootPaths: [String] {[ + .empty, .Path.forwardSlash + ]} + } // MARK: - RouterMiddleware extension DocCMiddleware: RouterMiddleware { - - // MARK: Type aliases - - public typealias Context = RequestContext - public typealias Input = Request - public typealias Output = Response - + // MARK: Functions public func handle( - _ request: Input, - context: any Context, - next: (Input, any Context) async throws -> Output + _ request: Request, + context: Context, + next: (Request, Context) async throws -> Response ) async throws -> Output { guard let uriPath = checkURI(request.uri), - let uriData = prepareURIPath(uriPath) + let resource = prepareURIPath(uriPath) else { return try await next(request, context) } - let rootPaths: [String] = [ - String(format: .Format.Path.root, uriData.archiveName), - String(format: .Format.Path.folder, uriData.archiveName) - ] + // Root URI Paths matching. + if rootPaths.contains(resource.relativePath) { + let uriRoot: String = if resource.relativePath.isEmpty { + .init(format: .Format.Path.forwardSlash, uriPath) + } else { + uriPath + } - if rootPaths.contains(uriData.resourcePath) { + // Rule #1: Redirects the URI path / or // to the path //documentation return redirectURI( - uriPath.hasSuffix(.Path.forwardSlash) - // Rule #2: Redirects the URI path // to the path //documentation - ? String(format: .Format.Path.documentation, uriPath) - // Rule #1: Redirects the URI path / to the path // - : String(format: .Format.Path.forwardSlash, uriPath), + String(format: .Format.Path.documentation, uriRoot), with: (request, context) ) } + // Asset files matching. for assetFile in AssetFile.allCases { - if uriData.resourcePath.contains(assetFile.path) { + if resource.relativePath.hasPrefix(assetFile.path) { return try await serveURI( assetFile == .documentation - // Rule #7: Redirects the URI path //data/documentation.json to the resource on /.doccarchive/data/documentation/.json - ? String(format: .Format.Path.documentationJSON, uriData.archiveReference) - // Rule #8: Redirects the URI path `//favicon.ico` to the resource on `/.doccarchive/favicon.ico` - // Rule #9: Redirects the URI path `//favicon.svg` to the resource on `/.doccarchive/favicon.svg` - // Rule #10: Redirects the URI path `//theme-settings.json` to the resource on `/.doccarchive/theme-settings.json` - : uriData.resourcePath, - at: uriData.archivePath, + // Rule #6: Redirects the URI path //data/documentation.json to the resource on /.doccarchive/data/documentation/.json + ? String(format: .Format.Path.documentationJSON, resource.archiveReference) + // Rule #7: Redirects the URI path `//favicon.ico` to the resource on `/.doccarchive/favicon.ico` + // Rule #8: Redirects the URI path `//favicon.svg` to the resource on `/.doccarchive/favicon.svg` + // Rule #9: Redirects the URI path `//theme-settings.json` to the resource on `/.doccarchive/theme-settings.json` + : resource.relativePath, + at: resource.archivePath, with: (request, context) ) } } for assetFolder in AssetFolder.allCases { - if uriData.resourcePath.contains(assetFolder.path) { - // Rule #11: Redirects the URI path `//css/` to the resource on `/.doccarchive/css/` - // Rule #12: Redirects the URI path `//data/` to the resource on `/.doccarchive/data/` - // Rule #13: Redirects the URI path `//downloads/` to the resource on `/.doccarchive/downloads/` - // Rule #14: Redirects the URI path `//images/` to the resource on `/.doccarchive/images/` - // Rule #15: Redirects the URI path `//img/` to the resource on `/.doccarchive/img/` - // Rule #16: Redirects the URI path `//index/` to the resource on `/.doccarchive/index/` - // Rule #17: Redirects the URI path `//js/` to the resource on `/.doccarchive/js/` - // Rule #18: Redirects the URI path `//videos/` to the resource on `/.doccarchive/videos/` + if resource.relativePath.hasPrefix(assetFolder.path) { + // Rule #10: Redirects the URI path `//css/` to the resource on `/.doccarchive/css/` + // Rule #11: Redirects the URI path `//data/` to the resource on `/.doccarchive/data/` + // Rule #12: Redirects the URI path `//downloads/` to the resource on `/.doccarchive/downloads/` + // Rule #13: Redirects the URI path `//images/` to the resource on `/.doccarchive/images/` + // Rule #14: Redirects the URI path `//img/` to the resource on `/.doccarchive/img/` + // Rule #15: Redirects the URI path `//index/` to the resource on `/.doccarchive/index/` + // Rule #16: Redirects the URI path `//js/` to the resource on `/.doccarchive/js/` + // Rule #17: Redirects the URI path `//videos/` to the resource on `/.doccarchive/videos/` return try await serveURI( - uriData.resourcePath, - at: uriData.archivePath, + resource.relativePath, + at: resource.archivePath, with: (request, context) ) } } - + for documentationFolder in DocumentationFolder.allCases { - if uriData.resourcePath.contains(documentationFolder.path) { - if uriData.resourcePath.hasSuffix(.Path.forwardSlash) { - // Rule #5: Redirects the URI path //documentation/ to the resource on /.doccarchive/documentation//index.html - // Rule #6: Redirects the URI path //tutorials/ to the resource on /.doccarchive/tutorials//index.html + if resource.relativePath.hasPrefix(documentationFolder.path) { + let pathSuffix: String = .init(format: .Format.Path.forwardSlash, documentationFolder.path) + + if uriPath.hasSuffix(pathSuffix) { + // Rule #4: Redirects the URI path //documentation/ to the resource on /.doccarchive/documentation//index.html + // Rule #5: Redirects the URI path //tutorials/ to the resource on /.doccarchive/tutorials//index.html return try await serveURI( - String(format: .Format.Path.index, documentationFolder.path, uriData.archiveReference), - at: uriData.archivePath, + String(format: .Format.Path.index, documentationFolder.path, resource.archiveReference), + at: resource.archivePath, with: (request, context) ) } else { - // Rule #3: Redirects the URI path //documentation to the path //documentation/ - // Rule #4: Redirects the URI path //tutorials to the path //tutorials/ + // Rule #2: Redirects the URI path //documentation to the path //documentation/ + // Rule #3: Redirects the URI path //tutorials to the path //tutorials/ return redirectURI( String(format: .Format.Path.forwardSlash, uriPath), with: (request, context) diff --git a/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift b/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift index 965599e..d73c2d5 100644 --- a/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift +++ b/Tests/HummingbirdDocC/Tests/Public/Middlewares/DocCMiddlewareTests.swift @@ -296,16 +296,12 @@ private extension DocCMiddlewareTests { ) { // GIVEN // WHEN - let middleware = DocCMiddleware( + let middleware = DocCMiddleware( configuration: configuration, logger: logger ) // THEN - #expect(middleware.configuration.folderRoot == configuration.folderRoot) - #expect(middleware.configuration.uriRoot == configuration.uriRoot) - #expect(middleware.configuration.threadPool === configuration.threadPool) - #expect(middleware.logger.label == logger.label) #expect(middleware.logger.logLevel == logger.logLevel) #expect(middleware.logger.metadataProvider == nil) @@ -325,17 +321,13 @@ private extension DocCMiddlewareTests { ) { // GIVEN // WHEN - let middleware = DocCMiddleware( + let middleware = DocCMiddleware( configuration: configuration, fileProvider: fileProvider, logger: logger ) // THEN - #expect(middleware.configuration.folderRoot == configuration.folderRoot) - #expect(middleware.configuration.uriRoot == configuration.uriRoot) - #expect(middleware.configuration.threadPool === configuration.threadPool) - #expect(middleware.logger.label == logger.label) #expect(middleware.logger.logLevel == logger.logLevel) #expect(middleware.logger.metadataProvider == nil) @@ -363,13 +355,13 @@ private extension DocCMiddlewareTests { handler: logHandler ) - let context: any RequestContext = RequestContextMock(logger: logger) + let context: RequestContextMock = .init(logger: logger) let request: Request = .test( method: .get, path: uriPath ) - let middleware = DocCMiddleware( + let middleware = DocCMiddleware( configuration: .init( uriRoot: .Sample.uriRoot, folderRoot: .Sample.uriFolder @@ -455,7 +447,7 @@ private extension DocCMiddlewareTests { path: uriPath ) - let middleware = DocCMiddleware( + let middleware = DocCMiddleware( configuration: .init( uriRoot: .Sample.uriRoot, folderRoot: .Sample.uriFolder @@ -476,7 +468,7 @@ private extension DocCMiddlewareTests { .contentLength: (statusCode == .ok ? "36" : "0") ]) - let contentLength = #require(result.body.contentLength) + let contentLength = try #require(result.body.contentLength) if statusCode == .ok { #expect(contentLength > 0) @@ -553,7 +545,12 @@ private extension DocCMiddlewareTests { private extension Input { /// A list of relative URI paths to match against the URI path redirections done by the middleware. - static let redirectURIPaths: [String] = [.empty, .Path.forwardSlash, "/documentation", "/tutorials"] + static let redirectURIPaths: [String] = [ + .empty, + .Path.forwardSlash, + "/documentation", + "/tutorials" + ] /// A list of relative URI paths to match against the URI path servings done by the middleware. static let serveURIPaths: [String] = [ "/documentation/", @@ -575,22 +572,27 @@ private extension Input { private extension Output { /// A list of expected relative URI path redirections outputs coming out of the URI path redirections done by the middleware. - static let redirectURIPaths: [String] = [.Path.forwardSlash, "/documentation", "/documentation/", "/tutorials/"] + static let redirectURIPaths: [String] = [ + "/documentation", + "/documentation", + "/documentation/", + "/tutorials/" + ] /// A list of expected relative file URI paths of the logged messages coming out of the URI path servings done by the middleware. static let serveURIFilePaths: [String] = [ "/SomeDocument.doccarchive/documentation/somedocument/index.html", "/SomeDocument.doccarchive/tutorials/somedocument/index.html", "/SomeDocument.doccarchive/data/documentation/somedocument.json", - "/SomeDocument.doccarchive/SomeDocument/favicon.ico", - "/SomeDocument.doccarchive/SomeDocument/favicon.svg", - "/SomeDocument.doccarchive/SomeDocument/theme-settings.json", - "/SomeDocument.doccarchive/SomeDocument/css/file.css", - "/SomeDocument.doccarchive/SomeDocument/data/data.bin", - "/SomeDocument.doccarchive/SomeDocument/downloads/file.txt", - "/SomeDocument.doccarchive/SomeDocument/images/image.png", - "/SomeDocument.doccarchive/SomeDocument/img/image.jpg", - "/SomeDocument.doccarchive/SomeDocument/index/file", - "/SomeDocument.doccarchive/SomeDocument/js/file.js", - "/SomeDocument.doccarchive/SomeDocument/videos/video.mp4" + "/SomeDocument.doccarchive/favicon.ico", + "/SomeDocument.doccarchive/favicon.svg", + "/SomeDocument.doccarchive/theme-settings.json", + "/SomeDocument.doccarchive/css/file.css", + "/SomeDocument.doccarchive/data/data.bin", + "/SomeDocument.doccarchive/downloads/file.txt", + "/SomeDocument.doccarchive/images/image.png", + "/SomeDocument.doccarchive/img/image.jpg", + "/SomeDocument.doccarchive/index/file", + "/SomeDocument.doccarchive/js/file.js", + "/SomeDocument.doccarchive/videos/video.mp4" ] } -- 2.52.0 From 867d4054b8393ff2c8ebb803e44ce54c484fe20f Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 30 Sep 2025 17:27:55 +0200 Subject: [PATCH 23/24] Fixed the DOCC_ARCHIVE_BASE_PATH variable in the .env file. --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index d284bd8..8916f17 100644 --- a/.env +++ b/.env @@ -12,7 +12,7 @@ # --- DOCUMENTATION --- -DOCC_ARCHIVE_BASE_PATH=archives/${DOCC_ARCHIVE_REFERENCE} +DOCC_ARCHIVE_BASE_PATH=archives/${SPM_LIBRARY_TARGET} DOCC_ARCHIVE_OUTPUT=./${SPM_LIBRARY_TARGET}.doccarchive DOCC_ARCHIVE_REFERENCE=hummingbirddocc DOCC_CONFIG_MINIMUM_ACCESS_LEVEL=public -- 2.52.0 From ed9e24ec5c8f0918d00433e8ee95db566c8e6e1f Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 30 Sep 2025 17:29:28 +0200 Subject: [PATCH 24/24] Implemented the "router()" helper function for the AppBuilder type in the sample target. --- .../App/Builders/AppBuilder.swift | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/Samples/HummingbirdDocC/App/Builders/AppBuilder.swift b/Samples/HummingbirdDocC/App/Builders/AppBuilder.swift index cc15e93..9fb1639 100644 --- a/Samples/HummingbirdDocC/App/Builders/AppBuilder.swift +++ b/Samples/HummingbirdDocC/App/Builders/AppBuilder.swift @@ -15,7 +15,11 @@ import class Hummingbird.Router import protocol Hummingbird.ApplicationProtocol import struct Hummingbird.Application +import struct Hummingbird.BasicRequestContext import struct Hummingbird.BindAddress +import struct Hummingbird.LogRequestsMiddleware +import struct HummingbirdDocC.DocCConfiguration +import struct HummingbirdDocC.DocCMiddleware import struct Logging.Logger struct AppBuilder { @@ -38,14 +42,45 @@ struct AppBuilder { func callAsFunction( _ arguments: AppArguments ) -> some ApplicationProtocol { - let router = Router() - return Application( - router: router, + router: router(), configuration: .init( - address: .hostname(arguments.hostname, port: arguments.port) + address: .hostname( + arguments.hostname, + port: arguments.port + ) ), logger: logger ) } + +} + +// MARK: - Helpers + +private extension AppBuilder { + + // MARK: Type aliases + + typealias AppRequestContext = BasicRequestContext + + // MARK: Functions + + func router() -> Router { + let router = Router() + + router.addMiddleware { + LogRequestsMiddleware(logger.logLevel) + DocCMiddleware ( + configuration: DocCConfiguration( + uriRoot: "/archives", + folderRoot: "Samples/HummingbirdDocC/Archives" + ), + logger: logger + ) + } + + return router + } + } -- 2.52.0