Added (first version of) sample Hummingbird app. (#4)

This PR contains the work done to:
* Implemented a basic `Hummingbird` application in which to integrate the `HummingbirdDocC` library.
* Added the *ArgumentParser* package dependency to the `Package.swift` file;
* Added a new *sample* target to the `Package.swift` file;
* Added library and documentation tasks to the `Makefile` file.

Reviewed-on: #4
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit was merged in pull request #4.
This commit is contained in:
2025-09-30 15:38:12 +00:00
committed by Javier Cicchelli
parent 3a9e3d176f
commit 1382f33ae6
49 changed files with 1095 additions and 488 deletions
@@ -0,0 +1,57 @@
// ===----------------------------------------------------------------------===
//
// 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 Foundation
import Testing
import protocol Logging.LogHandler
import struct Logging.Logger
extension Logger {
// MARK: Functions
/// Generates a logger instance that is ready to use in test cases.
/// - Parameters:
/// - level: A logger level, if any.
/// - handler: A custom log handler, if any.
/// - Returns: A generated logger instance ready to use in test cases.
static func test(
level: Logger.Level? = nil,
handler: (any LogHandler)? = nil
) -> Self {
var logger: Logger = if let handler {
.init(label: .loggerLabel) { _ in handler }
} else {
.init(label: .loggerLabel)
}
logger.logLevel = if let level {
level
} else {
try! #require(Logger.Level.allCases.randomElement())
}
logger[metadataKey: "hb.request.id"] = "\(UUID().uuidString)"
return logger
}
}
// MARK: - Constants
private extension String {
/// A label to assign to a test logger instance.
static let loggerLabel = "test.hummingbird-docc.logger"
}
@@ -0,0 +1,61 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
import Testing
import struct Logging.Logger
extension Logger.Level {
// MARK: Functions
/// Extracts a random logging level value out of an inclusive subset of logging levels, arranged by severity.
/// - Parameter level: A representation of a logging level that defines a subset of values to choose from, if any.
/// - Returns: A randomized logging value.
/// - Throws: An error thrown in case an issue is encountered when deciding for a random value.
static func random(upTo level: Self? = nil) throws -> Self {
guard let level else {
return try #require(Self.allCases.randomElement())
}
let levels: [Self] = switch level {
case .trace: [.trace]
case .debug: [.debug, .trace]
case .info: [.debug, .info, .trace]
case .notice: [.debug, .info, .notice, .trace]
case .warning: [.debug, .info, .notice, .trace, .warning]
case .error: [.debug, .error, .info, .notice, .trace, .warning]
case .critical: Self.allCases
}
return try #require(levels.randomElement())
}
/// /// Extracts a random logging level value out of an exclusive subset of logging levels, arranged by severity.
/// - Parameter level: A representation of a logging level that defines a subset of values to choose from.
/// - Returns: A randomized logging value.
/// - Throws: An error thrown in case an issue is encountered when deciding for a random value.
static func random(fromExclusive level: Self) throws -> Self {
let levels: [Self] = switch level {
case .trace: [.critical, .debug, .error, .info, .notice, .warning]
case .debug: [.critical, .error, .info, .notice, .warning]
case .info: [.critical, .error, .notice, .warning]
case .notice: [.critical, .error, .warning]
case .warning: [.critical, .error]
case .error: [.critical]
case .critical: []
}
return try #require(levels.randomElement())
}
}
@@ -0,0 +1,41 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
import struct Hummingbird.HTTPRequest
import struct Hummingbird.Request
import struct Hummingbird.RequestBody
extension Request {
// MARK: Functions
/// Generates a request that is ready to use in test case.
/// - Parameters:
/// - method: A HTTP method.
/// - path: A URI path, if any.
/// - Returns: A generated request instance to use in test cases.
static func test(
method: HTTPRequest.Method,
path: String? = nil
) -> Self {
.init(
head: .init(
method: method,
scheme: nil,
authority: nil,
path: path
),
body: .init(buffer: .init())
)
}
}
@@ -0,0 +1,32 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
extension String {
// MARK: Constants
/// A namespace that defines sample values.
enum Sample {
/// A URI path to use as a documentation root sample.
static let uriDocument = uriRoot + "/SomeDocument"
/// A URI path to use as a file sample.
static let uriFile = uriFolder + uriResource
/// A URI path to use as a folder sample.
static let uriFolder = "/some/folder/path"
/// A URI path to use as a redirection sample.
static let uriRedirection = "/some/redirect/path"
/// A URI path to use as a resource sample.
static let uriResource = "/some/path/to/resource"
/// A URI path to use as a root sample.
static let uriRoot = "/some/root/path"
}
}
@@ -0,0 +1,30 @@
// ===----------------------------------------------------------------------===
//
// 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
extension Tag {
// MARK: Constants
/// Tag that indicate a test case for an enumeration type.
@Tag static var enumeration: Self
/// Tag that indicate a test case for an extended type.
@Tag static var `extension`: Self
/// Tag that indicate a test case for a middleware type.
@Tag static var middleware: Self
/// Tag that indicate a test case for a model type.
@Tag static var model: Self
/// Tag that indicate a test case for a use case type.
@Tag static var useCase: Self
}
@@ -0,0 +1,102 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
import protocol Hummingbird.FileProvider
import protocol Hummingbird.RequestContext
import struct Foundation.Data
import struct Foundation.UUID
import struct Hummingbird.ResponseBody
/// A mock that conforms to the `FileProvider` protocol.
struct FileProviderMock {
// MARK: Properties
/// A type that identifies a sample file.
private let fileIdentifier: UUID?
/// A flag that indicates whether a file should be loaded or not.
private let shouldLoadFile: Bool
// MARK: Initializers
/// Initializes this mock.
/// - Parameters:
/// - fileIdentifier: A type that identifies a sample file, if any.
/// - shouldLoadFile: A flag that indicates whether a file should be loaded or not.
init(
fileIdentifier: UUID? = nil,
shouldLoadFile: Bool = true
) {
self.fileIdentifier = fileIdentifier
self.shouldLoadFile = shouldLoadFile
}
}
// MARK: - FileProvider
extension FileProviderMock: FileProvider {
// MARK: Type aliases
typealias FileAttributes = String
typealias FileIdentifier = String
// MARK: Functions
func getFileIdentifier(_ path: String) -> String? {
fileIdentifier?.uuidString
}
func getAttributes(id: String) async throws -> String? {
nil
}
func loadFile(
id: String,
context: some RequestContext
) async throws -> ResponseBody {
guard shouldLoadFile else {
throw FileProviderMockError.fileNotLoaded
}
guard let content = fileIdentifier?.uuidString else {
return .init()
}
return .init(byteBuffer: .init(
data: .init(content.utf8)
))
}
func loadFile(
id: String,
range: ClosedRange<Int>,
context: some RequestContext
) async throws -> ResponseBody {
try await loadFile(
id: id,
context: context
)
}
}
// MARK: - FileProviderMockError
/// An error type that can only be thrown by the ``FileProviderMock`` mock.
enum FileProviderMockError: Error {
/// An error encountered while mocking the loading of a file.
case fileNotLoaded
}
@@ -0,0 +1,153 @@
// ===----------------------------------------------------------------------===
//
// 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 Foundation
import protocol Logging.LogHandler
import struct Logging.Logger
/// A mock that conforms to the `LogHandler` protocol.
struct LogHandlerMock {
// MARK: Properties
/// A representation of the logging level assigned to this mock.
private var _logLevel: Logger.Level = .debug
/// A dictionary that contains all the metadata assigned to this mock.
private var _metadata: Logger.Metadata = [:]
/// A logging event recorder attached to this mock.
private let recorder: LogRecorder = .init()
// MARK: Computed
/// A list of all the logged events that are being persisted in the recorder.
var entries: [LogEntry] { recorder.entries }
}
// MARK: - LogEntry
/// A type that contains the information logged in a logging event.
struct LogEntry: Equatable {
// MARK: Properties
/// A representation of the level attached to a logged event.
let level: Logger.Level
/// A metadata dictionary that contains additional information attached to a logged event.
let metadata: Logger.Metadata?
/// A message attached to a logged event.
let message: Logger.Message
/// A source from where a logged event was triggered.
let source: String
}
// MARK: - LogRecorder
extension LogHandlerMock {
/// 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.
var entries: [LogEntry] {
lock.withLock { _entries }
}
// MARK: Functions
/// Records data related to a logged event.
/// - Parameters:
/// - level: A representation of the level attached to a logged event.
/// - metadata: A metadata dictionary that contains additional information attached to a logged event.
/// - message: A message attached to a logged event.
/// - source: A source from where a logged event was triggered.
func record(
level: Logger.Level,
metadata: Logger.Metadata?,
message: Logger.Message,
source: String
) {
lock.withLock {
_entries += [.init(
level: level,
metadata: metadata,
message: message,
source: source
)]
}
}
}
}
// MARK: - LogHandler
extension LogHandlerMock: LogHandler {
// MARK: Properties
var metadata: Logger.Metadata {
get { _metadata }
set(newValue) { _metadata = newValue }
}
var logLevel: Logger.Level {
get { _logLevel }
set(newValue) { _logLevel = newValue }
}
// MARK: Subscripts
subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
get { _metadata[metadataKey] }
set(newValue) { _metadata[metadataKey] = newValue }
}
// MARK: Functions
func log(
level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt
) {
recorder.record(
level: level,
metadata: metadata,
message: message,
source: source
)
}
}
@@ -0,0 +1,51 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
import class NIOEmbedded.NIOAsyncTestingChannel
import protocol Hummingbird.RequestContext
import struct Hummingbird.ApplicationRequestContextSource
import struct Hummingbird.CoreRequestContextStorage
import struct Logging.Logger
/// A mock that conforms to the `RequestContext` protocol.
struct RequestContextMock {
// MARK: Properties
var coreContext: CoreRequestContextStorage
// MARK: Initializers
/// Initializes this mock.
/// - Parameter logger: A type that interacts with the logging system.
init(logger: Logger) {
self.coreContext = .init(source: ApplicationRequestContextSource(
channel: NIOAsyncTestingChannel(),
logger: logger
))
}
}
// MARK: - RequestContext
extension RequestContextMock: RequestContext {
// MARK: Initializers
init(source: ApplicationRequestContextSource) {
self.coreContext = .init(source: source)
}
}
@@ -0,0 +1,14 @@
// ===----------------------------------------------------------------------===
//
// 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 namespace assigned for test arguments
enum Input {}
@@ -0,0 +1,14 @@
// ===----------------------------------------------------------------------===
//
// 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 namespace assigned for test arguments that would be expected outputs coming from results of test cases.
enum Output {}
@@ -0,0 +1,53 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the Hummingbird DocC Middleware open source project
//
// Copyright (c) 2025 Röck+Cöde VoF. and the Hummingbird DocC Middleware project authors
// Licensed under the EUPL 1.2 or later.
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of Hummingbird DocC Middleware project authors
//
// ===----------------------------------------------------------------------===
import Hummingbird
/// A stub that conforms to the `FileProvider` protocol.
struct FileProviderStub {}
// MARK: - FileProvider
extension FileProviderStub: FileProvider {
// MARK: Type aliases
typealias FileAttributes = String
typealias FileIdentifier = String
// MARK: Functions
func getFileIdentifier(_ path: String) -> String? {
nil
}
func getAttributes(id: String) async throws -> String? {
nil
}
func loadFile(
id: String,
context: some RequestContext
) async throws -> ResponseBody {
.init()
}
func loadFile(
id: String,
range: ClosedRange<Int>,
context: some RequestContext
) async throws -> ResponseBody {
.init()
}
}