import Hummingbird import Logging /// A controller type that provides information about the *DocC* archives containers. struct ArchiveController { // MARK: Properties private let archivesPath: String private let fileService: any FileServicing private let logger: Logger // MARK: Initialisers /// Initialises this controller. /// - Parameters: /// - archivesPath: A path in the local file system where the *DocC* archive contained are located. /// - fileService: A service that interfaces with the local file system. /// - logger: A service that interfaces with the logging system. init( _ archivesPath: String, fileService: any FileServicing = FileService(), logger: Logger ) { self.archivesPath = archivesPath self.fileService = fileService self.logger = logger } // MARK: Functions /// Registers the controller to a given router. /// - Parameter router: A router to register this controller to. func register(to router: Router) { router.get(.archives, use: listArchives) } } // MARK: - Helpers private extension ArchiveController { // MARK: Functions @Sendable func listArchives( _ request: Request, context: Context ) async throws (HTTPError) -> ArchiveList { do { let nameArchives = try await fileService .listItems(in: archivesPath) .filter { $0.hasSuffix(.suffixArchive) } .map { $0.dropLast(String.suffixArchive.count) } .map(String.init) .sorted { $0 < $1 } let archiveList: ArchiveList = .init(nameArchives) defer { logger.debug( "A codable response returned: \(String(describing: archiveList))", metadata: .metadata( context: context, request: request, statusCode: .ok ), source: .source ) } return archiveList } catch .folderNotFound { defer { logger.error( "The resource has not been found.", metadata: .metadata( context: context, request: request, statusCode: .notFound ), source: .source ) } throw .init(.notFound) } catch .folderPathEmpty, .folderNotDirectory { defer { logger.error( "The folder of the resource has not been located.", metadata: .metadata( context: context, request: request, statusCode: .notAcceptable ), source: .source ) } throw .init(.notAcceptable) } catch { defer { logger.error( "The request has issues.", metadata: .metadata( context: context, request: request, statusCode: .badRequest ), source: .source ) } throw .init(.badRequest) } } } // MARK: - Logger,Metadata+Functions private extension Logger.Metadata { // MARK: Functions static func metadata( context: Context, request: Request, statusCode: HTTPResponse.Status ) -> Logger.Metadata {[ "hb.request.id": "\(context.id)", "hb.request.method": "\(request.method.rawValue)", "hb.request.path": "\(request.uri.path)", "hb.request.status": "\(statusCode.code)" ]} } // MARK: - RouterPath+Constants private extension RouterPath { static let archives: RouterPath = .init("archives") } // MARK: - String+Constants private extension String { static let source = "ArchiveController" static let suffixArchive: String = ".doccarchive" }