import Configuration import Hummingbird import Logging import Persistence import WebsiteCore package extension ConfigReader { // MARK: Type aliases /// The request context type the application serves its routes with; the security headers configuration is generic over it. typealias AppRequestContext = WebsiteRequestContext // MARK: Computed /// The `Cache-Control` policy applied to static files, grouped by media type. /// /// The max-ages are read from the `cache.maxAge.text`, `cache.maxAge.image`, and `cache.maxAge.default` keys. Text files (CSS, /// JavaScript, plain text) additionally require revalidation once stale; images and everything else are served public with their max-age alone. var cacheControl: CacheControl { let maxAgeDefault = int( forKey: .Cache.maxAgeDefault, default: .Cache.maxAgeDefault ) let maxAgeImage = int( forKey: .Cache.maxAgeImage, default: .Cache.maxAgeImage ) let maxAgeText = int( forKey: .Cache.maxAgeText, default: .Cache.maxAgeText ) return .init([ (.text, [.public, .maxAge(maxAgeText), .mustRevalidate]), (.image, [.public, .maxAge(maxAgeImage)]), (.init(type: .any), [.public, .maxAge(maxAgeDefault)]), ]) } /// The minimum response body size, in bytes, before a response is compressed — read from the `compression.minimumResponseSize` key. var compressionMinResponseSize: Int { int( forKey: .Compression.minResponseSize, default: .Compression.minResponseSize ) } /// The persistence backend the service runs against, derived from the `database.*` keys. /// /// When `database.driver` selects MySQL, the connection parameters are assembled from the `database.host`, `database.port`, /// `database.name`, `database.username`, `database.password` (empty when unset), `database.tls`, and /// `database.pool.maxPerEventLoop` keys. Any other driver value falls back to the in-memory database. var driver: Driver { switch string( forKey: .Database.driver, default: .Database.driver ) { case .Database.driverMySQL: return .mysql( .init( host: string( forKey: .Database.host, default: .Database.host ), port: int( forKey: .Database.port, default: .Database.port ), name: string( forKey: .Database.name, default: .Database.name ), username: string( forKey: .Database.username, default: .Database.username ), password: string( forKey: .Database.password, default: "" ), tls: tls, maxConnectionsPerEventLoop: int( forKey: .Database.poolMaxPerEventLoop, default: .Database.poolMaxPerEventLoop ) ) ) default: return .inMemory } } /// The minimum log level the application emits at, read from the `log.level` key. /// /// Falls back to `.info` when the key is unset or its value names no `Logger.Level` case. var logLevel: Logger.Level { string( forKey: .Log.level, as: Logger.Level.self, default: .info ) } /// Whether the executable runs in migrate-and-exit mode instead of serving, read from the `database.migrate` flag; off by default. var migrate: Bool { bool( forKey: .Database.migrate, default: false ) } /// The security headers middleware configuration, built from the `security.*` keys. /// /// Every header value has a default except `Strict-Transport-Security`, which is only sent when `security.strictTransportSecurity` /// is set — the header is a commitment browsers cache, so it must be opted into for deployments actually served over HTTPS. var securityHeaders: SecurityHeadersMiddleware.Configuration { .init( contentSecurityPolicy: string( forKey: .Security.contentSecurityPolicy, default: .Security.contentSecurityPolicy ), contentTypeOptions: string( forKey: .Security.contentTypeOptions, default: .Security.contentTypeOptions ), frameOptions: string( forKey: .Security.frameOptions, default: .Security.frameOptions ), referrerPolicy: string( forKey: .Security.referrerPolicy, default: .Security.referrerPolicy ), permissionsPolicy: string( forKey: .Security.permissionsPolicy, default: .Security.permissionsPolicy ), strictTransportSecurity: string( forKey: .Security.strictTransportSecurity ) ) } /// The name the server reports in its `Server` response header, read from the `http.serverName` key. var serverName: String { string( forKey: .HTTP.serverName, default: .Server.name ) } /// The directory the static files are served from, read from the `path.staticFiles` key. var staticFilesPath: String { string( forKey: .Path.staticFiles, default: .Path.staticResources ) } } // MARK: - Helpers private extension ConfigReader { // MARK: Properties /// The TLS posture for the MySQL connection, mapped from the `database.tls` key: `off` and `require` map to their postures, and any /// other value falls back to `prefer`. var tls: TLS { switch string( forKey: .Database.tls, default: .Database.tls ) { case .Database.tlsOff: .off case .Database.tlsRequire: .require default: .prefer } } }