Skip to content

Commit 23c6b9b

Browse files
authored
Rename ConfigReader.withSnapshot(_:) to ConfigReader.snapshot() (#79)
### Motivation Fixes #78. Since ConfigSnapshotReader is being sent through async sequences, we can't take advantage of any non-copyable/non-escapable optimizations on it, which brings the ConfigReader.withSnapshot method into question - why is it a with-style method, when simply returning a snapshot reader would work just fine? ConfigProvider.snapshot() already has this spelling. This benefits consistency, because there isn't any reason the ConfigSnapshotReader needs to be scoped, this API shape came from an earlier experiment where we thought we could use a non-copyable/non-escapable type. But right now it's not providing any benefits and is just providing a slightly less ergonomic API for users. ### Modifications Renamed `ConfigReader.withSnapshot(_:)` to `ConfigReader.snapshot()`. ### Result More consistent, simpler API. ### Test Plan All tests pass.
1 parent 309df0a commit 23c6b9b

13 files changed

+130
-68
lines changed

Sources/Configuration/ConfigSnapshotReader.swift

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,19 @@ import Synchronization
2222
///
2323
/// ## Usage
2424
///
25-
/// Get a ``ConfigSnapshotReader`` from a ``ConfigReader`` by using ``ConfigReader/withSnapshot(_:)``
26-
/// to retrieve a snapshot. The values of the snapshot are guaranteed to be from the same point in time:
25+
/// Get a ``ConfigSnapshotReader`` from a ``ConfigReader`` by using ``ConfigReader/snapshot()``
26+
/// to retrieve a snapshot. All values in the snapshot are guaranteed to be from the same point in time:
2727
/// ```swift
2828
/// // Get a snapshot from a ConfigReader
2929
/// let config = ConfigReader(provider: EnvironmentVariablesProvider())
30-
/// let result = config.withSnapshot { snapshot in
31-
/// // Use snapshot to read config values
32-
/// let cert = snapshot.string(forKey: "cert")
33-
/// let privateKey = snapshot.string(forKey: "privateKey")
34-
/// // Ensures that both values are coming from the same
35-
/// // underlying snapshot and that a provider didn't change
36-
/// // its internal state between the two `string(...)` calls.
37-
/// return MyCert(cert: cert, privateKey: privateKey)
38-
/// }
30+
/// let snapshot = config.snapshot()
31+
/// // Use snapshot to read config values
32+
/// let cert = snapshot.string(forKey: "cert")
33+
/// let privateKey = snapshot.string(forKey: "privateKey")
34+
/// // Ensures that both values are coming from the same
35+
/// // underlying snapshot and that a provider didn't change
36+
/// // its internal state between the two `string(...)` calls.
37+
/// let identity = MyIdentity(cert: cert, privateKey: privateKey)
3938
/// ```
4039
///
4140
/// Or you can watch for snapshot updates using the ``ConfigReader/watchSnapshot(fileID:line:updatesHandler:)``:
@@ -226,29 +225,23 @@ public struct ConfigSnapshotReader: Sendable {
226225

227226
@available(Configuration 1.0, *)
228227
extension ConfigReader {
229-
/// Provides a snapshot of the current configuration state and passes it to the provided closure.
228+
/// Returns a snapshot of the current configuration state.
230229
///
231-
/// This method creates a snapshot of the current configuration state and passes it to the
232-
/// provided closure. The snapshot reader provides read-only access to the configuration's state
230+
/// The snapshot reader provides read-only access to the configuration's state
233231
/// at the time the method was called.
234232
///
235233
/// ```swift
236-
/// let result = config.withSnapshot { snapshot in
237-
/// // Use snapshot to read config values
238-
/// let cert = snapshot.string(forKey: "cert")
239-
/// let privateKey = snapshot.string(forKey: "privateKey")
240-
/// // Ensures that both values are coming from the same underlying snapshot and that a provider
241-
/// // didn't change its internal state between the two `string(...)` calls.
242-
/// return MyCert(cert: cert, privateKey: privateKey)
243-
/// }
234+
/// let snapshot = config.snapshot()
235+
/// // Use snapshot to read config values
236+
/// let cert = snapshot.string(forKey: "cert")
237+
/// let privateKey = snapshot.string(forKey: "privateKey")
238+
/// // Ensures that both values are coming from the same underlying snapshot and that a provider
239+
/// // didn't change its internal state between the two `string(...)` calls.
240+
/// let identity = MyIdentity(cert: cert, privateKey: privateKey)
244241
/// ```
245242
///
246-
/// - Parameter body: A closure that takes a `ConfigSnapshotReader` and returns a value.
247-
/// - Returns: The value returned by the closure.
248-
/// - Throws: Rethrows any errors thrown by the provided closure.
249-
public func withSnapshot<Failure: Error, Return>(
250-
_ body: (ConfigSnapshotReader) throws(Failure) -> Return
251-
) throws(Failure) -> Return {
243+
/// - Returns: The snapshot.
244+
public func snapshot() -> ConfigSnapshotReader {
252245
let multiSnapshot = provider.snapshot()
253246
let snapshotReader = ConfigSnapshotReader(
254247
keyPrefix: keyPrefix,
@@ -257,7 +250,7 @@ extension ConfigReader {
257250
accessReporter: accessReporter
258251
)
259252
)
260-
return try body(snapshotReader)
253+
return snapshotReader
261254
}
262255

263256
/// Watches the configuration for changes.

Sources/Configuration/Deprecations.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,41 @@ public typealias ReloadingYAMLProvider = ReloadingFileProvider<YAMLSnapshot>
4848
@available(Configuration 1.0, *)
4949
@available(*, deprecated, renamed: "ConfigSnapshot")
5050
public typealias ConfigSnapshotProtocol = ConfigSnapshot
51+
52+
@available(Configuration 1.0, *)
53+
extension ConfigReader {
54+
/// Provides a snapshot of the current configuration state and passes it to the provided closure.
55+
///
56+
/// This method creates a snapshot of the current configuration state and passes it to the
57+
/// provided closure. The snapshot reader provides read-only access to the configuration's state
58+
/// at the time the method was called.
59+
///
60+
/// ```swift
61+
/// let result = config.withSnapshot { snapshot in
62+
/// // Use snapshot to read config values
63+
/// let cert = snapshot.string(forKey: "cert")
64+
/// let privateKey = snapshot.string(forKey: "privateKey")
65+
/// // Ensures that both values are coming from the same underlying snapshot and that a provider
66+
/// // didn't change its internal state between the two `string(...)` calls.
67+
/// return MyCert(cert: cert, privateKey: privateKey)
68+
/// }
69+
/// ```
70+
///
71+
/// - Parameter body: A closure that takes a `ConfigSnapshotReader` and returns a value.
72+
/// - Returns: The value returned by the closure.
73+
/// - Throws: Rethrows any errors thrown by the provided closure.
74+
@available(*, deprecated, message: "Renamed to snapshot().")
75+
public func withSnapshot<Failure: Error, Return>(
76+
_ body: (ConfigSnapshotReader) throws(Failure) -> Return
77+
) throws(Failure) -> Return {
78+
let multiSnapshot = provider.snapshot()
79+
let snapshotReader = ConfigSnapshotReader(
80+
keyPrefix: keyPrefix,
81+
storage: .init(
82+
snapshot: multiSnapshot,
83+
accessReporter: accessReporter
84+
)
85+
)
86+
return try body(snapshotReader)
87+
}
88+
}

Sources/Configuration/Documentation.docc/Documentation.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -358,19 +358,18 @@ Read <doc:Handling-secrets-correctly> for guidance on best practices for secrets
358358
#### Consistent snapshots
359359

360360
Retrieve related values from a consistent snapshot using ``ConfigSnapshotReader``, which you
361-
get from calling ``ConfigReader/withSnapshot(_:)``.
361+
get by calling ``ConfigReader/snapshot()``.
362362

363363
This ensures that multiple values are read from a single snapshot inside each provider, even when using
364364
providers that update their internal values.
365365
For example by downloading new data periodically:
366366

367367
```swift
368368
let config = /* a reader with one or more providers that change values over time */
369-
try config.withSnapshot { snapshot in
370-
let certificate = try snapshot.requiredString(forKey: "mtls.certificate")
371-
let privateKey = try snapshot.requiredString(forKey: "mtls.privateKey", isSecret: true)
372-
// `certificate` and `privateKey` are guaranteed to come from the same snapshot in the provider
373-
}
369+
let snapshot = config.snapshot()
370+
let certificate = try snapshot.requiredString(forKey: "mtls.certificate")
371+
let privateKey = try snapshot.requiredString(forKey: "mtls.privateKey", isSecret: true)
372+
// `certificate` and `privateKey` are guaranteed to come from the same snapshot in the provider
374373
```
375374

376375
#### Extensible ecosystem

Sources/Configuration/Documentation.docc/Reference/ConfigReader.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
- ``ConfigReader/scoped(to:)``
1212

1313
### Reading from a snapshot
14-
- ``ConfigReader/withSnapshot(_:)``
14+
- ``ConfigReader/snapshot()``
1515
- ``ConfigReader/watchSnapshot(fileID:line:updatesHandler:)``
1616

1717
- <doc:ConfigReader-Get>

Sources/Configuration/Documentation.docc/Reference/ConfigSnapshotReader.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Topics
44

55
### Creating a snapshot
6-
- ``ConfigReader/withSnapshot(_:)``
6+
- ``ConfigReader/snapshot()``
77
- ``ConfigReader/watchSnapshot(fileID:line:updatesHandler:)``
88

99
### Namespacing

Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
3232
@Test func get() async throws {
3333
let config = ConfigReaderTests.config
3434

35-
try config.withSnapshot { snapshot in
35+
do {
36+
let snapshot = config.snapshot()
37+
3638
// Optional - success
3739
#expect(snapshot.string(forKey: "string") == Defaults.string)
3840

@@ -61,7 +63,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
6163
// Required - failing
6264
#expect(throws: TestProvider.TestError.self) { try snapshot.requiredString(forKey: "failure") }
6365
}
64-
try config.withSnapshot { snapshot in
66+
do {
67+
let snapshot = config.snapshot()
68+
6569
// Optional - success
6670
#expect(snapshot.int(forKey: "int") == Defaults.int)
6771

@@ -90,7 +94,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
9094
// Required - failing
9195
#expect(throws: TestProvider.TestError.self) { try snapshot.requiredInt(forKey: "failure") }
9296
}
93-
try config.withSnapshot { snapshot in
97+
do {
98+
let snapshot = config.snapshot()
99+
94100
// Optional - success
95101
#expect(snapshot.double(forKey: "double") == Defaults.double)
96102

@@ -119,7 +125,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
119125
// Required - failing
120126
#expect(throws: TestProvider.TestError.self) { try snapshot.requiredDouble(forKey: "failure") }
121127
}
122-
try config.withSnapshot { snapshot in
128+
do {
129+
let snapshot = config.snapshot()
130+
123131
// Optional - success
124132
#expect(snapshot.bool(forKey: "bool") == Defaults.bool)
125133

@@ -148,7 +156,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
148156
// Required - failing
149157
#expect(throws: TestProvider.TestError.self) { try snapshot.requiredBool(forKey: "failure") }
150158
}
151-
try config.withSnapshot { snapshot in
159+
do {
160+
let snapshot = config.snapshot()
161+
152162
// Optional - success
153163
#expect(snapshot.bytes(forKey: "bytes") == Defaults.bytes)
154164

@@ -177,7 +187,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
177187
// Required - failing
178188
#expect(throws: TestProvider.TestError.self) { try snapshot.requiredBytes(forKey: "failure") }
179189
}
180-
try config.withSnapshot { snapshot in
190+
do {
191+
let snapshot = config.snapshot()
192+
181193
// Optional - success
182194
#expect(snapshot.stringArray(forKey: "stringArray") == Defaults.stringArray)
183195

@@ -215,7 +227,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
215227
// Required - failing
216228
#expect(throws: TestProvider.TestError.self) { try snapshot.requiredStringArray(forKey: "failure") }
217229
}
218-
try config.withSnapshot { snapshot in
230+
do {
231+
let snapshot = config.snapshot()
232+
219233
// Optional - success
220234
#expect(snapshot.intArray(forKey: "intArray") == Defaults.intArray)
221235

@@ -246,7 +260,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
246260
// Required - failing
247261
#expect(throws: TestProvider.TestError.self) { try snapshot.requiredIntArray(forKey: "failure") }
248262
}
249-
try config.withSnapshot { snapshot in
263+
do {
264+
let snapshot = config.snapshot()
265+
250266
// Optional - success
251267
#expect(snapshot.doubleArray(forKey: "doubleArray") == Defaults.doubleArray)
252268

@@ -284,7 +300,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
284300
// Required - failing
285301
#expect(throws: TestProvider.TestError.self) { try snapshot.requiredDoubleArray(forKey: "failure") }
286302
}
287-
try config.withSnapshot { snapshot in
303+
do {
304+
let snapshot = config.snapshot()
305+
288306
// Optional - success
289307
#expect(snapshot.boolArray(forKey: "boolArray") == Defaults.boolArray)
290308

@@ -316,7 +334,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
316334
// Required - failing
317335
#expect(throws: TestProvider.TestError.self) { try snapshot.requiredBoolArray(forKey: "failure") }
318336
}
319-
try config.withSnapshot { snapshot in
337+
do {
338+
let snapshot = config.snapshot()
339+
320340
// Optional - success
321341
#expect(snapshot.byteChunkArray(forKey: "byteChunkArray") == Defaults.byteChunkArray)
322342

Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift.gyb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 {
3434
% for primitive_type in primitive_types:
3535
% name = primitive_type["name"]
3636
% lower_name = lower_first(name)
37-
try config.withSnapshot { snapshot in
37+
do {
38+
let snapshot = config.snapshot()
39+
3840
// Optional - success
3941
#expect(snapshot.${lower_name}(forKey: "${lower_name}") == Defaults.${lower_name})
4042

Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ struct ConfigSnapshotReaderMethodTestsGet2 {
3232
@Test func get() async throws {
3333
let config = ConfigReaderTests.config
3434

35-
try config.withSnapshot { snapshot in
35+
do {
36+
let snapshot = config.snapshot()
37+
3638
// Optional - success
3739
#expect(
3840
snapshot.string(forKey: "stringConvertible", as: TestStringConvertible.self)
@@ -89,7 +91,9 @@ struct ConfigSnapshotReaderMethodTestsGet2 {
8991
try snapshot.requiredString(forKey: "failure", as: TestStringConvertible.self)
9092
}
9193
}
92-
try config.withSnapshot { snapshot in
94+
do {
95+
let snapshot = config.snapshot()
96+
9397
// Optional - success
9498
#expect(snapshot.string(forKey: "stringEnum", as: TestEnum.self) == Defaults.stringEnum)
9599

Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift.gyb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ struct ConfigSnapshotReaderMethodTestsGet2 {
3535
% test_type = string_convertible_type["testType"]
3636
% test_suffix = string_convertible_type["testSuffix"]
3737
% lower_test_suffix = lower_first(test_suffix)
38-
try config.withSnapshot { snapshot in
38+
do {
39+
let snapshot = config.snapshot()
40+
3941
// Optional - success
4042
#expect(snapshot.string(forKey: "${lower_test_suffix}", as: ${test_type}.self) == Defaults.${lower_test_suffix})
4143

Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet3.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ struct ConfigSnapshotReaderMethodTestsGet3 {
3232
@Test func get() async throws {
3333
let config = ConfigReaderTests.config
3434

35-
try config.withSnapshot { snapshot in
35+
do {
36+
let snapshot = config.snapshot()
37+
3638
// Optional - success
3739
#expect(
3840
snapshot.stringArray(forKey: "stringConvertibleArray", as: TestStringConvertible.self)
@@ -89,7 +91,9 @@ struct ConfigSnapshotReaderMethodTestsGet3 {
8991
try snapshot.requiredStringArray(forKey: "failure", as: TestStringConvertible.self)
9092
}
9193
}
92-
try config.withSnapshot { snapshot in
94+
do {
95+
let snapshot = config.snapshot()
96+
9397
// Optional - success
9498
#expect(snapshot.stringArray(forKey: "stringEnumArray", as: TestEnum.self) == Defaults.stringEnumArray)
9599

0 commit comments

Comments
 (0)