/// A static file shipped with the website service. /// /// Each case identifies a file stored under the static files root (the `Resources/Static` /// directory) and served by Hummingbird's `FileMiddleware` middleware. enum StaticFile: CaseIterable, Sendable { /// The `js/app.js` script. case appJS /// The `favicon.ico` icon. case faviconICO /// The `icon.png` icon. case iconPNG /// The `icon.svg` icon. case iconSVG /// The `robots.txt` crawler directives. case robotsTXT /// The `site.webmanifest` web application manifest. case siteWebmanifest /// The `css/style.css` stylesheet. case styleCSS } // MARK: - Enumerations extension StaticFile { /// A file extension used by a ``StaticFile``. enum Extension: String, Sendable { /// A Cascading Style Sheets file. case css /// A JavaScript file. case js /// A Portable Network Graphics image. case png /// A Windows icon image. case ico /// A Scalable Vector Graphics image. case svg /// A plain text file. case txt /// A web application manifest file. case webmanifest } } // MARK: - Extensions extension StaticFile { // MARK: Computed /// The file's content type. var contentType: String { switch fileExtension { case .css: "text/css" case .js: "text/javascript" case .png: "image/png" case .ico: "image/vnd.microsoft.icon" case .svg: "image/svg+xml" case .txt: "text/plain" case .webmanifest: "application/manifest+json" } } /// The file's extension. var fileExtension: Extension { switch self { case .styleCSS: .css case .appJS: .js case .faviconICO: .ico case .iconPNG: .png case .iconSVG: .svg case .robotsTXT: .txt case .siteWebmanifest: .webmanifest } } /// The file's name, without extension. var fileName: String { switch self { case .appJS: "app" case .faviconICO: "favicon" case .iconPNG, .iconSVG: "icon" case .robotsTXT: "robots" case .siteWebmanifest: "site" case .styleCSS: "style" } } /// The path relative to the static files root (e.g. `"css/style.css"`). /// /// This also matches the URL path the file is served at by `FileMiddleware`. var relativePath: String { let file = "\(fileName).\(fileExtension.rawValue)" return subdirectory .map { "\($0)/\(file)" } ?? file } // MARK: Methods /// Resolves the file's path against the given base directory. /// /// - Parameter basePath: the directory the static files are served from. /// - Returns: the path to the file, relative to the `basePath` path. public func path( relativeTo basePath: String ) -> String { guard !basePath.isEmpty else { return relativePath } return "\(basePath)/\(relativePath)" } } // MARK: - Helpers private extension StaticFile { // MARK: Computed /// The sub-directory within the static root that holds the file, if any. var subdirectory: String? { switch self { case .appJS: "js" case .styleCSS: "css" default: nil } } }