Merge branch 'setup'
This commit is contained in:
+12
-1
@@ -3,7 +3,9 @@ import Configuration
|
||||
extension AbsoluteConfigKey {
|
||||
/// A namespace for the static files cache configuration keys, as absolute keys.
|
||||
public enum Cache {
|
||||
/// The absolute configuration key for the max-age, in seconds, applied to text-based static files.
|
||||
/// The absolute configuration key for the max-age, in seconds, applied to fingerprinted assets and fonts.
|
||||
public static let maxAgeAsset: AbsoluteConfigKey = .init(.Cache.maxAgeAsset)
|
||||
/// The absolute configuration key for the max-age, in seconds, applied to unversioned text-based static files.
|
||||
public static let maxAgeText: AbsoluteConfigKey = .init(.Cache.maxAgeText)
|
||||
/// The absolute configuration key for the max-age, in seconds, applied to image static files.
|
||||
public static let maxAgeImage: AbsoluteConfigKey = .init(.Cache.maxAgeImage)
|
||||
@@ -50,6 +52,15 @@ extension AbsoluteConfigKey {
|
||||
/// The absolute configuration key for the minimum log level.
|
||||
public static let level: AbsoluteConfigKey = .init(.Log.level)
|
||||
}
|
||||
/// A namespace for the rate limit configuration keys, as absolute keys.
|
||||
public enum RateLimit {
|
||||
/// The absolute configuration key for the number of requests admitted per client per window.
|
||||
public static let limit: AbsoluteConfigKey = .init(.RateLimit.limit)
|
||||
/// The absolute configuration key for the window length, in seconds.
|
||||
public static let window: AbsoluteConfigKey = .init(.RateLimit.window)
|
||||
/// The absolute configuration key for keying clients by the first `X-Forwarded-For` entry.
|
||||
public static let trustForwardedFor: AbsoluteConfigKey = .init(.RateLimit.trustForwardedFor)
|
||||
}
|
||||
/// A namespace for the path configuration keys, as absolute keys.
|
||||
public enum Path {
|
||||
/// The absolute configuration key for the directory the static files are served from.
|
||||
|
||||
@@ -3,7 +3,9 @@ import Configuration
|
||||
extension ConfigKey {
|
||||
/// A namespace for the static files cache configuration keys.
|
||||
public enum Cache {
|
||||
/// The configuration key for the max-age, in seconds, applied to text-based static files (CSS, JavaScript, plain text).
|
||||
/// The configuration key for the max-age, in seconds, applied to fingerprinted assets (CSS, JavaScript) and fonts.
|
||||
public static let maxAgeAsset: ConfigKey = "cache.maxAge.asset"
|
||||
/// The configuration key for the max-age, in seconds, applied to unversioned text-based static files (e.g. plain text).
|
||||
public static let maxAgeText: ConfigKey = "cache.maxAge.text"
|
||||
/// The configuration key for the max-age, in seconds, applied to image static files (ICO, PNG, SVG).
|
||||
public static let maxAgeImage: ConfigKey = "cache.maxAge.image"
|
||||
@@ -50,6 +52,15 @@ extension ConfigKey {
|
||||
/// The configuration key for the minimum log level.
|
||||
public static let level: ConfigKey = "log.level"
|
||||
}
|
||||
/// A namespace for the rate limit configuration keys.
|
||||
public enum RateLimit {
|
||||
/// The configuration key for the number of requests admitted per client per window.
|
||||
public static let limit: ConfigKey = "rateLimit.limit"
|
||||
/// The configuration key for the window length, in seconds.
|
||||
public static let window: ConfigKey = "rateLimit.window"
|
||||
/// The configuration key for keying clients by the first `X-Forwarded-For` entry (enable only behind a trusted proxy).
|
||||
public static let trustForwardedFor: ConfigKey = "rateLimit.trustForwardedFor"
|
||||
}
|
||||
/// A namespace for the path configuration keys.
|
||||
public enum Path {
|
||||
/// The configuration key for the directory the static files are served from.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import HTTPTypes
|
||||
|
||||
extension HTTPField.Name {
|
||||
/// The `Permissions-Policy` field name (not provided as a standard `HTTPField.Name`).
|
||||
static let permissionsPolicy = Self("Permissions-Policy")!
|
||||
/// The `Referrer-Policy` field name (not provided as a standard `HTTPField.Name`).
|
||||
static let referrerPolicy = Self("Referrer-Policy")!
|
||||
/// The `X-Frame-Options` field name (not provided as a standard `HTTPField.Name`).
|
||||
static let frameOptions = Self("X-Frame-Options")!
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
extension Int {
|
||||
/// A namespace for the cache's default configuration values.
|
||||
public enum Cache {
|
||||
/// The default max-age, in seconds, applied to text-based static files (1 hour).
|
||||
/// The default max-age, in seconds, applied to fingerprinted assets and fonts (1 year).
|
||||
public static let maxAgeAsset = 31_536_000
|
||||
/// The default max-age, in seconds, applied to unversioned text-based static files (1 hour).
|
||||
public static let maxAgeText = 3_600
|
||||
/// The default max-age, in seconds, applied to image static files (1 week).
|
||||
public static let maxAgeImage = 604_800
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import Foundation
|
||||
import Localization
|
||||
|
||||
public extension LanguageList {
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates a language list backed by the module's String Catalog.
|
||||
init() {
|
||||
self.init(bundle: .module)
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import Foundation
|
||||
import Infrastructure
|
||||
|
||||
public extension LocalizationMiddleware {
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates a localization middleware that negotiates against the module's String Catalog languages.
|
||||
init() {
|
||||
self.init(bundle: .module)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
import Infrastructure
|
||||
|
||||
public extension NotFoundMiddleware {
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates a not-found middleware that renders the website's error page, localized to the module's String Catalog languages.
|
||||
/// - Parameter assetVersion: the version token appended to the page's asset URLs, or `nil` (the default) to leave them unversioned.
|
||||
init(
|
||||
assetVersion: String? = nil
|
||||
) {
|
||||
self.init(bundle: .module) {
|
||||
NotFoundPage(
|
||||
locale: $0,
|
||||
assetVersion: assetVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
import Hummingbird
|
||||
|
||||
/// A result builder that collects ``RouteCollection`` values into a stack.
|
||||
///
|
||||
/// Mirrors the `MiddlewareFixedTypeBuilder` Hummingbird uses for `addMiddleware`, letting route
|
||||
/// collections be listed declaratively rather than added one statement at a time.
|
||||
@resultBuilder
|
||||
public enum RouteCollectionBuilder<Context: RequestContext> {
|
||||
|
||||
public static func buildExpression(
|
||||
_ collection: RouteCollection<Context>
|
||||
) -> [RouteCollection<Context>] {
|
||||
[collection]
|
||||
}
|
||||
|
||||
public static func buildBlock(
|
||||
_ collections: [RouteCollection<Context>]...
|
||||
) -> [RouteCollection<Context>] {
|
||||
collections.flatMap { $0 }
|
||||
}
|
||||
|
||||
public static func buildOptional(
|
||||
_ collections: [RouteCollection<Context>]?
|
||||
) -> [RouteCollection<Context>] {
|
||||
collections ?? []
|
||||
}
|
||||
|
||||
public static func buildEither(
|
||||
first collections: [RouteCollection<Context>]
|
||||
) -> [RouteCollection<Context>] {
|
||||
collections
|
||||
}
|
||||
|
||||
public static func buildEither(
|
||||
second collections: [RouteCollection<Context>]
|
||||
) -> [RouteCollection<Context>] {
|
||||
collections
|
||||
}
|
||||
|
||||
public static func buildArray(
|
||||
_ collections: [[RouteCollection<Context>]]
|
||||
) -> [RouteCollection<Context>] {
|
||||
collections.flatMap { $0 }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
public extension RouterMethods {
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Adds route collections to the router using the ``RouteCollectionBuilder`` result builder.
|
||||
///
|
||||
/// Mirrors `addMiddleware`, letting controllers be listed declaratively:
|
||||
///
|
||||
/// ```swift
|
||||
/// router.addRoutes {
|
||||
/// RootController<AppRequestContext>().routes
|
||||
/// HealthController<AppRequestContext>().routes
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Each collection is added at the router's root, exactly as a sequence of
|
||||
/// `addRoutes(_:)` calls would.
|
||||
/// - Parameter build: the route-collection stack result builder.
|
||||
/// - Returns: the router, so calls can be chained.
|
||||
@discardableResult
|
||||
func addRoutes(
|
||||
@RouteCollectionBuilder<Context> _ build: () -> [RouteCollection<Context>]
|
||||
) -> Self {
|
||||
for collection in build() {
|
||||
addRoutes(collection)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,27 +23,6 @@ extension String {
|
||||
/// The directory, relative to the working directory, that the website's static files are served from.
|
||||
public static let staticResources = "Resources/Static"
|
||||
}
|
||||
/// A namespace for the security headers' default configuration values.
|
||||
///
|
||||
/// `Strict-Transport-Security` is intentionally absent: it is only safe over HTTPS and is
|
||||
/// "sticky" in browsers, so it stays off unless explicitly configured in production.
|
||||
public enum Security {
|
||||
/// The default `Content-Security-Policy`.
|
||||
///
|
||||
/// Restricts every resource to the site's own origin (`default-src 'self'`), blocks plugins
|
||||
/// (`object-src 'none'`), pins the document base URL (`base-uri 'self'`), and forbids framing
|
||||
/// (`frame-ancestors 'none'`). Both pages link external stylesheets, so no inline-style
|
||||
/// exception is required.
|
||||
public static let contentSecurityPolicy = "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
|
||||
/// The default `X-Content-Type-Options` (disables MIME sniffing).
|
||||
public static let contentTypeOptions = "nosniff"
|
||||
/// The default `X-Frame-Options` (forbids framing the page).
|
||||
public static let frameOptions = "DENY"
|
||||
/// The default `Referrer-Policy`.
|
||||
public static let referrerPolicy = "strict-origin-when-cross-origin"
|
||||
/// The default `Permissions-Policy` (denies access to powerful browser features the site does not use).
|
||||
public static let permissionsPolicy = "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
|
||||
}
|
||||
/// A namespace for the server string constants.
|
||||
public enum Server {
|
||||
/// The website server's name.
|
||||
|
||||
Reference in New Issue
Block a user