Implemented (a first version of) the DocCMiddleware middleware in the library target.
This commit is contained in:
parent
54aa5c70c6
commit
19a54b25ae
22
Library/Sources/Internal/Enumerations/AssetPrefix.swift
Normal file
22
Library/Sources/Internal/Enumerations/AssetPrefix.swift
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
enum AssetPrefix: String, CaseIterable {
|
||||||
|
case css
|
||||||
|
case data
|
||||||
|
case downloads
|
||||||
|
case images
|
||||||
|
case img
|
||||||
|
case index
|
||||||
|
case js
|
||||||
|
case videos
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Pathable
|
||||||
|
|
||||||
|
extension AssetPrefix: Pathable {
|
||||||
|
|
||||||
|
// MARK: Computed
|
||||||
|
|
||||||
|
var path: String {
|
||||||
|
.init(format: .Format.pathRoot, rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
Library/Sources/Internal/Enumerations/IndexPrefix.swift
Normal file
16
Library/Sources/Internal/Enumerations/IndexPrefix.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
enum IndexPrefix: String, CaseIterable {
|
||||||
|
case documentation
|
||||||
|
case tutorials
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Pathable
|
||||||
|
|
||||||
|
extension IndexPrefix: Pathable {
|
||||||
|
|
||||||
|
// MARK: Computed
|
||||||
|
|
||||||
|
var path: String {
|
||||||
|
.init(format: .Format.pathRoot, rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
Library/Sources/Internal/Enumerations/StaticFile.swift
Normal file
21
Library/Sources/Internal/Enumerations/StaticFile.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
enum StaticFile: String, CaseIterable {
|
||||||
|
case documentation = "documentation.json"
|
||||||
|
case faviconICO = "favicon.ico"
|
||||||
|
case faviconSVG = "favicon.svg"
|
||||||
|
case themeSettings = "theme-settings.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Pathable
|
||||||
|
|
||||||
|
extension StaticFile: Pathable {
|
||||||
|
|
||||||
|
// MARK: Computed
|
||||||
|
|
||||||
|
var path: String {
|
||||||
|
switch self {
|
||||||
|
case .documentation: "/data/" + rawValue
|
||||||
|
default: .init(format: .Format.pathRoot, rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
41
Library/Sources/Internal/Extensions/Path+Functions.swift
Normal file
41
Library/Sources/Internal/Extensions/Path+Functions.swift
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
enum Path {
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
static func archiveName(from path: String) -> String {
|
||||||
|
let pathComponents = path.split(separator: .forwardSlash)
|
||||||
|
|
||||||
|
return pathComponents.count > 1
|
||||||
|
? String(pathComponents[1]).lowercased()
|
||||||
|
: .empty
|
||||||
|
}
|
||||||
|
|
||||||
|
static func resourcePath(from path: String) -> String {
|
||||||
|
let matches = path.matches(of: /\//)
|
||||||
|
|
||||||
|
return matches.count > 2
|
||||||
|
? .init(path[matches[2].startIndex...])
|
||||||
|
: .forwardSlash
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - String+Constants
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
static let empty = ""
|
||||||
|
static let forwardSlash = "/"
|
||||||
|
static let previousFolder = ".."
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Character+Constants
|
||||||
|
|
||||||
|
private extension Character {
|
||||||
|
static let forwardSlash: Character = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - String+Formats
|
||||||
|
|
||||||
|
private extension String.Format {
|
||||||
|
static let archiveDocC = "%@.doccarchive"
|
||||||
|
}
|
138
Library/Sources/Internal/Middlewares/DocCMiddleware.swift
Normal file
138
Library/Sources/Internal/Middlewares/DocCMiddleware.swift
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
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/
|
||||||
|
*/
|
||||||
|
|
||||||
|
print(uriPath)
|
||||||
|
|
||||||
|
guard uriPath.starts(with: /^\/docs\/\w+/) else {
|
||||||
|
return try await next(input, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
let nameArchive = Path.archiveName(from: uriPath)
|
||||||
|
let uriResource = Path.resourcePath(from: uriPath)
|
||||||
|
|
||||||
|
print(nameArchive)
|
||||||
|
print(uriResource)
|
||||||
|
|
||||||
|
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: "/docs", 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: "/docs", context: context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for assetPrefix in AssetPrefix.allCases {
|
||||||
|
if uriResource.contains(assetPrefix.path) {
|
||||||
|
return try await serveFile(uriResource, at: "/docs", context: context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for indexPrefix in IndexPrefix.allCases {
|
||||||
|
if uriResource.contains(indexPrefix.path) {
|
||||||
|
if uriResource.hasSuffix(.forwardSlash) {
|
||||||
|
return try await serveFile(
|
||||||
|
"/documentation/\(nameArchive)/index.html",
|
||||||
|
at: "/docs",
|
||||||
|
context: context
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return .redirect(to: uriPath + "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .init(status: .ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private extension DocCMiddleware {
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
func serveFile(
|
||||||
|
_ path: String,
|
||||||
|
at folder: String? = nil,
|
||||||
|
context: Context
|
||||||
|
) async throws -> Response {
|
||||||
|
let filePath = if let folder {
|
||||||
|
folder + path
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
print(filePath)
|
||||||
|
|
||||||
|
guard let fileIdentifier = assetProvider.getFileIdentifier(filePath) else {
|
||||||
|
throw HTTPError(.notFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = try await assetProvider.loadFile(id: fileIdentifier, context: context)
|
||||||
|
|
||||||
|
print(fileIdentifier)
|
||||||
|
print(body)
|
||||||
|
|
||||||
|
return .init(status: .ok, headers: [:], body: body)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
Library/Sources/Internal/Protocols/Pathable.swift
Normal file
16
Library/Sources/Internal/Protocols/Pathable.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
protocol Pathable {
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
var path: String { get }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - String+Formats
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
enum Format {
|
||||||
|
static let pathDocs = "/docs/%@"
|
||||||
|
static let pathRoot = "/%@"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user