Moved the PostgreSQL TLS context building to the Service initialiser in the Persistence package.
This commit is contained in:
@@ -2,16 +2,25 @@ import FluentPostgresDriver
|
|||||||
import FluentSQLiteDriver
|
import FluentSQLiteDriver
|
||||||
import HummingbirdFluent
|
import HummingbirdFluent
|
||||||
import Logging
|
import Logging
|
||||||
|
import PostgresNIO
|
||||||
|
|
||||||
/// A factory building the `Fluent` service the application persists through.
|
/// A factory building the `Fluent` service the application persists through.
|
||||||
///
|
///
|
||||||
/// Built once around the driver the executable picks at startup and called as a function to produce the configured service: `let fluent = service()`.
|
/// Built once around the driver the executable picks at startup and called as a function to produce the configured service: `let fluent = service()`.
|
||||||
public struct Service: Sendable {
|
public struct Service: Sendable {
|
||||||
|
|
||||||
|
// MARK: Enumerations
|
||||||
|
|
||||||
|
/// The persistence backend resolved at construction, with the PostgreSQL TLS mode already built.
|
||||||
|
private enum Backend {
|
||||||
|
case postgres(Configuration, PostgresConnection.Configuration.TLS)
|
||||||
|
case inMemory
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
/// The persistence backend to register.
|
/// The resolved persistence backend to register.
|
||||||
private let driver: Driver
|
private let backend: Backend
|
||||||
|
|
||||||
/// The logger the database emits through.
|
/// The logger the database emits through.
|
||||||
private let logger: Logger
|
private let logger: Logger
|
||||||
@@ -19,14 +28,27 @@ public struct Service: Sendable {
|
|||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
/// Creates a factory for a `Fluent` service backed by the given driver.
|
/// Creates a factory for a `Fluent` service backed by the given driver.
|
||||||
|
///
|
||||||
|
/// The TLS context for the PostgreSQL backend is built here, once — the factory holds only resolved configuration, so producing the
|
||||||
|
/// service afterwards cannot fail.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - driver: the persistence backend to register.
|
/// - driver: the persistence backend to register.
|
||||||
/// - logger: the logger the database emits through.
|
/// - logger: the logger the database emits through.
|
||||||
|
/// - Throws: an error when the TLS context for the PostgreSQL backend cannot be built.
|
||||||
public init(
|
public init(
|
||||||
driver: Driver,
|
driver: Driver,
|
||||||
logger: Logger
|
logger: Logger
|
||||||
) {
|
) throws {
|
||||||
self.driver = driver
|
switch driver {
|
||||||
|
case .postgres(let configuration):
|
||||||
|
self.backend = .postgres(
|
||||||
|
configuration,
|
||||||
|
try configuration.tls.postgresTLS()
|
||||||
|
)
|
||||||
|
case .inMemory:
|
||||||
|
self.backend = .inMemory
|
||||||
|
}
|
||||||
|
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,15 +59,14 @@ public struct Service: Sendable {
|
|||||||
/// The selected backend is registered as the *default* database, so repositories resolve it with a plain `fluent.db()` and stay agnostic of which
|
/// The selected backend is registered as the *default* database, so repositories resolve it with a plain `fluent.db()` and stay agnostic of which
|
||||||
/// driver is in use. The returned service is not yet running; add it to the application's service group (`app.addServices(_:)`) so it starts and shuts
|
/// driver is in use. The returned service is not yet running; add it to the application's service group (`app.addServices(_:)`) so it starts and shuts
|
||||||
/// its connection pool down alongside the server.
|
/// its connection pool down alongside the server.
|
||||||
/// - Throws: an error when the TLS context for the PostgreSQL backend cannot be built.
|
|
||||||
/// - Returns: the configured `Fluent` service, ready to be added to the service group.
|
/// - Returns: the configured `Fluent` service, ready to be added to the service group.
|
||||||
public func callAsFunction() throws -> Fluent {
|
public func callAsFunction() -> Fluent {
|
||||||
let fluent = Fluent(
|
let fluent = Fluent(
|
||||||
logger: logger
|
logger: logger
|
||||||
)
|
)
|
||||||
|
|
||||||
switch driver {
|
switch backend {
|
||||||
case .postgres(let configuration):
|
case .postgres(let configuration, let tls):
|
||||||
fluent.databases.use(
|
fluent.databases.use(
|
||||||
.postgres(
|
.postgres(
|
||||||
configuration: .init(
|
configuration: .init(
|
||||||
@@ -54,7 +75,7 @@ public struct Service: Sendable {
|
|||||||
username: configuration.username,
|
username: configuration.username,
|
||||||
password: configuration.password,
|
password: configuration.password,
|
||||||
database: configuration.name,
|
database: configuration.name,
|
||||||
tls: try configuration.tls.postgresTLS()
|
tls: tls
|
||||||
),
|
),
|
||||||
maxConnectionsPerEventLoop: configuration.maxConnectionsPerEventLoop
|
maxConnectionsPerEventLoop: configuration.maxConnectionsPerEventLoop
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ struct ProbeTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func `reports a reachable database`() async throws {
|
func `reports a reachable database`() async throws {
|
||||||
let service = Service(
|
let service = try Service(
|
||||||
driver: .inMemory,
|
driver: .inMemory,
|
||||||
logger: Logger(label: "test")
|
logger: Logger(label: "test")
|
||||||
)
|
)
|
||||||
let fluent = try service()
|
let fluent = service()
|
||||||
let probe = Probe(fluent: fluent)
|
let probe = Probe(fluent: fluent)
|
||||||
|
|
||||||
let isReachable = await probe()
|
let isReachable = await probe()
|
||||||
@@ -33,7 +33,7 @@ struct ProbeTests {
|
|||||||
func `reports an unreachable database`() async throws {
|
func `reports an unreachable database`() async throws {
|
||||||
// Port 1 on the loopback interface has nothing listening, so the connection is refused
|
// Port 1 on the loopback interface has nothing listening, so the connection is refused
|
||||||
// immediately instead of timing out.
|
// immediately instead of timing out.
|
||||||
let service = Service(
|
let service = try Service(
|
||||||
driver: .postgres(
|
driver: .postgres(
|
||||||
.init(
|
.init(
|
||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
@@ -47,7 +47,7 @@ struct ProbeTests {
|
|||||||
),
|
),
|
||||||
logger: Logger(label: "test")
|
logger: Logger(label: "test")
|
||||||
)
|
)
|
||||||
let fluent = try service()
|
let fluent = service()
|
||||||
let probe = Probe(fluent: fluent)
|
let probe = Probe(fluent: fluent)
|
||||||
|
|
||||||
let isReachable = await probe()
|
let isReachable = await probe()
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ struct ServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func `registers an SQLite database as the default for the in-memory driver`() async throws {
|
func `registers an SQLite database as the default for the in-memory driver`() async throws {
|
||||||
let service = Service(
|
let service = try Service(
|
||||||
driver: .inMemory,
|
driver: .inMemory,
|
||||||
logger: Logger(label: "test")
|
logger: Logger(label: "test")
|
||||||
)
|
)
|
||||||
|
|
||||||
let fluent = try service()
|
let fluent = service()
|
||||||
let database = fluent.db() as? any SQLDatabase
|
let database = fluent.db() as? any SQLDatabase
|
||||||
|
|
||||||
try await fluent.shutdown()
|
try await fluent.shutdown()
|
||||||
@@ -34,7 +34,7 @@ struct ServiceTests {
|
|||||||
func `registers a PostgreSQL database as the default for the postgres driver`() async throws {
|
func `registers a PostgreSQL database as the default for the postgres driver`() async throws {
|
||||||
// Resolving the default database opens no connection — pooling is lazy — so no server
|
// Resolving the default database opens no connection — pooling is lazy — so no server
|
||||||
// needs to be listening on the configured host and port.
|
// needs to be listening on the configured host and port.
|
||||||
let service = Service(
|
let service = try Service(
|
||||||
driver: .postgres(
|
driver: .postgres(
|
||||||
.init(
|
.init(
|
||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
@@ -49,7 +49,7 @@ struct ServiceTests {
|
|||||||
logger: Logger(label: "test")
|
logger: Logger(label: "test")
|
||||||
)
|
)
|
||||||
|
|
||||||
let fluent = try service()
|
let fluent = service()
|
||||||
let database = fluent.db() as? any SQLDatabase
|
let database = fluent.db() as? any SQLDatabase
|
||||||
|
|
||||||
try await fluent.shutdown()
|
try await fluent.shutdown()
|
||||||
@@ -61,12 +61,12 @@ struct ServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func `builds a usable in-memory database`() async throws {
|
func `builds a usable in-memory database`() async throws {
|
||||||
let service = Service(
|
let service = try Service(
|
||||||
driver: .inMemory,
|
driver: .inMemory,
|
||||||
logger: Logger(label: "test")
|
logger: Logger(label: "test")
|
||||||
)
|
)
|
||||||
|
|
||||||
let fluent = try service()
|
let fluent = service()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let database = try #require(fluent.db() as? any SQLDatabase)
|
let database = try #require(fluent.db() as? any SQLDatabase)
|
||||||
@@ -116,12 +116,12 @@ private extension ServiceTests {
|
|||||||
revertAfter: Bool = false
|
revertAfter: Bool = false
|
||||||
) async throws {
|
) async throws {
|
||||||
let prepareDB = PrepareDB()
|
let prepareDB = PrepareDB()
|
||||||
let service = Service(
|
let service = try Service(
|
||||||
driver: driver,
|
driver: driver,
|
||||||
logger: Logger(label: "test")
|
logger: Logger(label: "test")
|
||||||
)
|
)
|
||||||
|
|
||||||
let fluent = try service()
|
let fluent = service()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
await prepareDB(for: fluent)
|
await prepareDB(for: fluent)
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ func application(
|
|||||||
logger.warning("String Catalog is \(isCatalogMissing ? "missing" : "undecodable"); pages will serve raw localization keys")
|
logger.warning("String Catalog is \(isCatalogMissing ? "missing" : "undecodable"); pages will serve raw localization keys")
|
||||||
}
|
}
|
||||||
|
|
||||||
let persistence = Service(
|
let persistence = try Service(
|
||||||
driver: reader.driver,
|
driver: reader.driver,
|
||||||
logger: logger
|
logger: logger
|
||||||
)
|
)
|
||||||
let fluent = try persistence()
|
let fluent = persistence()
|
||||||
|
|
||||||
let fingerprintAssets = FingerprintAssets(logger: logger)
|
let fingerprintAssets = FingerprintAssets(logger: logger)
|
||||||
let prepareDB = PrepareDB()
|
let prepareDB = PrepareDB()
|
||||||
@@ -88,12 +88,12 @@ func migration(
|
|||||||
serverName: reader.serverName,
|
serverName: reader.serverName,
|
||||||
logLevel: reader.logLevel
|
logLevel: reader.logLevel
|
||||||
)
|
)
|
||||||
let service = Service(
|
let service = try Service(
|
||||||
driver: reader.driver,
|
driver: reader.driver,
|
||||||
logger: logger
|
logger: logger
|
||||||
)
|
)
|
||||||
|
|
||||||
let fluent = try service()
|
let fluent = service()
|
||||||
let prepareDB = PrepareDB()
|
let prepareDB = PrepareDB()
|
||||||
|
|
||||||
await prepareDB(for: fluent)
|
await prepareDB(for: fluent)
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ struct HealthControllerTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func `serves ready at the readiness path when the database is reachable`() async throws {
|
func `serves ready at the readiness path when the database is reachable`() async throws {
|
||||||
let service = Service(
|
let service = try Service(
|
||||||
driver: .inMemory,
|
driver: .inMemory,
|
||||||
logger: Logger(label: "test")
|
logger: Logger(label: "test")
|
||||||
)
|
)
|
||||||
let fluent = try service()
|
let fluent = service()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await app(
|
try await app(
|
||||||
@@ -69,7 +69,7 @@ struct HealthControllerTests {
|
|||||||
func `serves unavailable at the readiness path when the database is unreachable`() async throws {
|
func `serves unavailable at the readiness path when the database is unreachable`() async throws {
|
||||||
// Port 1 on the loopback interface has nothing listening, so the probe's connection is refused
|
// Port 1 on the loopback interface has nothing listening, so the probe's connection is refused
|
||||||
// immediately instead of timing out.
|
// immediately instead of timing out.
|
||||||
let service = Service(
|
let service = try Service(
|
||||||
driver: .postgres(
|
driver: .postgres(
|
||||||
.init(
|
.init(
|
||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
@@ -83,7 +83,7 @@ struct HealthControllerTests {
|
|||||||
),
|
),
|
||||||
logger: Logger(label: "test")
|
logger: Logger(label: "test")
|
||||||
)
|
)
|
||||||
let fluent = try service()
|
let fluent = service()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await app(
|
try await app(
|
||||||
|
|||||||
Reference in New Issue
Block a user