import Hummingbird import Logging import NIOPosix struct DocCMiddleware< Context: RequestContext, AssetProvider: FileProvider >: RouterMiddleware { // MARK: Properties private let assetProvider: AssetProvider // MARK: Initialisers init( _ rootFolder: String, threadPool: NIOThreadPool = .singleton, logger: Logger = .init(label: "DocCMiddleware") ) where AssetProvider == LocalFileSystem { self.assetProvider = LocalFileSystem( rootFolder: rootFolder, threadPool: threadPool, logger: logger ) } init(assetProvider: AssetProvider) { self.assetProvider = assetProvider } // MARK: Functions func handle( _ input: Request, context: Context, next: (Request, Context) async throws -> Response ) async throws -> Response { guard let uriPath = input.uri.path.removingPercentEncoding, !uriPath.contains(.previousFolder), uriPath.hasPrefix(.forwardSlash) else { throw HTTPError(.badRequest) } /* Send all requests starting with /documentation/ or /tutorials/ to the index.html file Send all requests starting with /css/, /data/, /downloads/, /images/, /img/, /index/, /js/, or /videos/ to their respective folders Send all requests to favicon.ico, favicon.svg, and theme-settings.json to their respective files Send all requests to /data/documentation.json to the file in the data/documentation/ folder that has the name of the module and ends with .json Redirect requests to / and /documentation to /documentation/ Redirect requests to /tutorials to /tutorials/ */ guard uriPath.starts(with: /^\/archives\/\w+/) else { return try await next(input, context) } let pathArchive = Path.archivePath(from: uriPath) let nameArchive = Path.archiveName(from: uriPath) let uriResource = Path.resourcePath(from: uriPath) if uriResource == .forwardSlash { return .redirect(to: uriPath + "/documentation") } for staticFile in StaticFile.allCases { if uriResource.contains(staticFile.path) { if staticFile == .documentation { // Send all requests to /data/documentation.json to the file in the data/documentation/ folder that has the name of the module and ends with .json return try await serveFile( "/data/documentation/\(nameArchive).json", at: pathArchive, context: context ) } else { // Send all requests to favicon.ico, favicon.svg, and theme-settings.json to their respective files return try await serveFile( uriResource, at: pathArchive, context: context ) } } } for assetPrefix in AssetPrefix.allCases { if uriResource.contains(assetPrefix.path) { return try await serveFile( uriResource, at: pathArchive, context: context ) } } for indexPrefix in IndexPrefix.allCases { if uriResource.contains(indexPrefix.path) { if uriResource.hasSuffix(.forwardSlash) { return try await serveFile( "\(indexPrefix.path)/\(nameArchive)/index.html", at: pathArchive, context: context ) } else { return .redirect(to: uriPath + "/") } } } throw HTTPError(.notFound) } } // MARK: - Helpers private extension DocCMiddleware { // MARK: Functions func serveFile( _ path: String, at folder: String, context: Context ) async throws -> Response { let filePath = folder + path guard let fileIdentifier = assetProvider.getFileIdentifier(filePath) else { throw HTTPError(.notFound) } let body = try await assetProvider.loadFile(id: fileIdentifier, context: context) return .init(status: .ok, headers: [:], body: body) } }