Skip to content

Commit 738020f

Browse files
committed
Add preamble service that runs closure before running service
1 parent 7ee57f9 commit 738020f

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftServiceLifecycle open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftServiceLifecycle project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftServiceLifecycle project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import Logging
15+
16+
/// Service that runs a preamble closure before running a child service
17+
public struct PreambleService<S: Service>: Service {
18+
let preamble: @Sendable () async throws -> Void
19+
let service: S
20+
21+
/// Initialize PreambleService
22+
/// - Parameters:
23+
/// - service: Child service
24+
/// - preamble: Preamble closure to run before running the service
25+
public init(service: S, preamble: @escaping @Sendable () async throws -> Void) {
26+
self.service = service
27+
self.preamble = preamble
28+
}
29+
30+
public func run() async throws {
31+
try await preamble()
32+
try await service.run()
33+
}
34+
}
35+
36+
extension PreambleService where S == ServiceGroup {
37+
/// Initialize PreambleService with a child ServiceGroup
38+
/// - Parameters:
39+
/// - services: Array of services to create ServiceGroup from
40+
/// - logger: Logger used by ServiceGroup
41+
/// - preamble: Preamble closure to run before starting the child services
42+
public init(services: [Service], logger: Logger, _ preamble: @escaping @Sendable () async throws -> Void) {
43+
self.init(
44+
service: ServiceGroup(configuration: .init(services: services, logger: logger)),
45+
preamble: preamble
46+
)
47+
}
48+
49+
/// Initialize PreambleService with a child ServiceGroup
50+
/// - Parameters:
51+
/// - services: Array of service configurations to create ServiceGroup from
52+
/// - logger: Logger used by ServiceGroup
53+
/// - preamble: Preamble closure to run before starting the child services
54+
public init(
55+
services: [ServiceGroupConfiguration.ServiceConfiguration],
56+
logger: Logger,
57+
_ preamble: @escaping @Sendable () async throws -> Void
58+
) {
59+
self.init(
60+
service: ServiceGroup(configuration: .init(services: services, logger: logger)),
61+
preamble: preamble
62+
)
63+
}
64+
}

Tests/ServiceLifecycleTests/ServiceGroupTests.swift

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,6 +1476,85 @@ final class ServiceGroupTests: XCTestCase {
14761476
}
14771477
}
14781478

1479+
func testPreambleService() async throws {
1480+
struct TestService: Service {
1481+
let continuation: AsyncStream<Int>.Continuation
1482+
1483+
init(continuation: AsyncStream<Int>.Continuation) {
1484+
self.continuation = continuation
1485+
}
1486+
1487+
func run() async throws {
1488+
continuation.yield(1)
1489+
}
1490+
}
1491+
let (stream, continuation) = AsyncStream.makeStream(of: Int.self)
1492+
let preambleService = PreambleService(service: TestService(continuation: continuation)) {
1493+
continuation.yield(0)
1494+
}
1495+
var logger = Logger(label: "Tests")
1496+
logger.logLevel = .debug
1497+
1498+
let serviceGroup = ServiceGroup(
1499+
services: [preambleService],
1500+
logger: logger
1501+
)
1502+
1503+
await withThrowingTaskGroup(of: Void.self) { group in
1504+
group.addTask {
1505+
try await serviceGroup.run()
1506+
}
1507+
1508+
var eventIterator = stream.makeAsyncIterator()
1509+
await XCTAsyncAssertEqual(await eventIterator.next(), 0)
1510+
await XCTAsyncAssertEqual(await eventIterator.next(), 1)
1511+
1512+
group.cancelAll()
1513+
}
1514+
}
1515+
1516+
func testPreambleServices() async throws {
1517+
struct TestService: Service {
1518+
let continuation: AsyncStream<Int>.Continuation
1519+
1520+
init(continuation: AsyncStream<Int>.Continuation) {
1521+
self.continuation = continuation
1522+
}
1523+
1524+
func run() async throws {
1525+
continuation.yield(1)
1526+
}
1527+
}
1528+
let (stream, continuation) = AsyncStream.makeStream(of: Int.self)
1529+
var logger = Logger(label: "Tests")
1530+
logger.logLevel = .debug
1531+
let preambleService = PreambleService(
1532+
services: [
1533+
TestService(continuation: continuation),
1534+
TestService(continuation: continuation),
1535+
],
1536+
logger: logger
1537+
) { continuation.yield(0) }
1538+
1539+
let serviceGroup = ServiceGroup(
1540+
services: [preambleService],
1541+
logger: logger
1542+
)
1543+
1544+
await withThrowingTaskGroup(of: Void.self) { group in
1545+
group.addTask {
1546+
try await serviceGroup.run()
1547+
}
1548+
1549+
var eventIterator = stream.makeAsyncIterator()
1550+
await XCTAsyncAssertEqual(await eventIterator.next(), 0)
1551+
await XCTAsyncAssertEqual(await eventIterator.next(), 1)
1552+
await XCTAsyncAssertEqual(await eventIterator.next(), 1)
1553+
1554+
group.cancelAll()
1555+
}
1556+
}
1557+
14791558
// MARK: - Helpers
14801559

14811560
private func makeServiceGroup(

0 commit comments

Comments
 (0)