diff --git a/Sources/Configuration/ConfigSnapshotReader.swift b/Sources/Configuration/ConfigSnapshotReader.swift index 9b356ea..8fbb02c 100644 --- a/Sources/Configuration/ConfigSnapshotReader.swift +++ b/Sources/Configuration/ConfigSnapshotReader.swift @@ -22,20 +22,19 @@ import Synchronization /// /// ## Usage /// -/// Get a ``ConfigSnapshotReader`` from a ``ConfigReader`` by using ``ConfigReader/withSnapshot(_:)`` -/// to retrieve a snapshot. The values of the snapshot are guaranteed to be from the same point in time: +/// Get a ``ConfigSnapshotReader`` from a ``ConfigReader`` by using ``ConfigReader/snapshot()`` +/// to retrieve a snapshot. All values in the snapshot are guaranteed to be from the same point in time: /// ```swift /// // Get a snapshot from a ConfigReader /// let config = ConfigReader(provider: EnvironmentVariablesProvider()) -/// let result = config.withSnapshot { snapshot in -/// // Use snapshot to read config values -/// let cert = snapshot.string(forKey: "cert") -/// let privateKey = snapshot.string(forKey: "privateKey") -/// // Ensures that both values are coming from the same -/// // underlying snapshot and that a provider didn't change -/// // its internal state between the two `string(...)` calls. -/// return MyCert(cert: cert, privateKey: privateKey) -/// } +/// let snapshot = config.snapshot() +/// // Use snapshot to read config values +/// let cert = snapshot.string(forKey: "cert") +/// let privateKey = snapshot.string(forKey: "privateKey") +/// // Ensures that both values are coming from the same +/// // underlying snapshot and that a provider didn't change +/// // its internal state between the two `string(...)` calls. +/// let identity = MyIdentity(cert: cert, privateKey: privateKey) /// ``` /// /// Or you can watch for snapshot updates using the ``ConfigReader/watchSnapshot(fileID:line:updatesHandler:)``: @@ -226,29 +225,23 @@ public struct ConfigSnapshotReader: Sendable { @available(Configuration 1.0, *) extension ConfigReader { - /// Provides a snapshot of the current configuration state and passes it to the provided closure. + /// Returns a snapshot of the current configuration state. /// - /// This method creates a snapshot of the current configuration state and passes it to the - /// provided closure. The snapshot reader provides read-only access to the configuration's state + /// The snapshot reader provides read-only access to the configuration's state /// at the time the method was called. /// /// ```swift - /// let result = config.withSnapshot { snapshot in - /// // Use snapshot to read config values - /// let cert = snapshot.string(forKey: "cert") - /// let privateKey = snapshot.string(forKey: "privateKey") - /// // Ensures that both values are coming from the same underlying snapshot and that a provider - /// // didn't change its internal state between the two `string(...)` calls. - /// return MyCert(cert: cert, privateKey: privateKey) - /// } + /// let snapshot = config.snapshot() + /// // Use snapshot to read config values + /// let cert = snapshot.string(forKey: "cert") + /// let privateKey = snapshot.string(forKey: "privateKey") + /// // Ensures that both values are coming from the same underlying snapshot and that a provider + /// // didn't change its internal state between the two `string(...)` calls. + /// let identity = MyIdentity(cert: cert, privateKey: privateKey) /// ``` /// - /// - Parameter body: A closure that takes a `ConfigSnapshotReader` and returns a value. - /// - Returns: The value returned by the closure. - /// - Throws: Rethrows any errors thrown by the provided closure. - public func withSnapshot( - _ body: (ConfigSnapshotReader) throws(Failure) -> Return - ) throws(Failure) -> Return { + /// - Returns: The snapshot. + public func snapshot() -> ConfigSnapshotReader { let multiSnapshot = provider.snapshot() let snapshotReader = ConfigSnapshotReader( keyPrefix: keyPrefix, @@ -257,7 +250,7 @@ extension ConfigReader { accessReporter: accessReporter ) ) - return try body(snapshotReader) + return snapshotReader } /// Watches the configuration for changes. diff --git a/Sources/Configuration/Deprecations.swift b/Sources/Configuration/Deprecations.swift index f51902e..57b9a37 100644 --- a/Sources/Configuration/Deprecations.swift +++ b/Sources/Configuration/Deprecations.swift @@ -48,3 +48,41 @@ public typealias ReloadingYAMLProvider = ReloadingFileProvider @available(Configuration 1.0, *) @available(*, deprecated, renamed: "ConfigSnapshot") public typealias ConfigSnapshotProtocol = ConfigSnapshot + +@available(Configuration 1.0, *) +extension ConfigReader { + /// Provides a snapshot of the current configuration state and passes it to the provided closure. + /// + /// This method creates a snapshot of the current configuration state and passes it to the + /// provided closure. The snapshot reader provides read-only access to the configuration's state + /// at the time the method was called. + /// + /// ```swift + /// let result = config.withSnapshot { snapshot in + /// // Use snapshot to read config values + /// let cert = snapshot.string(forKey: "cert") + /// let privateKey = snapshot.string(forKey: "privateKey") + /// // Ensures that both values are coming from the same underlying snapshot and that a provider + /// // didn't change its internal state between the two `string(...)` calls. + /// return MyCert(cert: cert, privateKey: privateKey) + /// } + /// ``` + /// + /// - Parameter body: A closure that takes a `ConfigSnapshotReader` and returns a value. + /// - Returns: The value returned by the closure. + /// - Throws: Rethrows any errors thrown by the provided closure. + @available(*, deprecated, message: "Renamed to snapshot().") + public func withSnapshot( + _ body: (ConfigSnapshotReader) throws(Failure) -> Return + ) throws(Failure) -> Return { + let multiSnapshot = provider.snapshot() + let snapshotReader = ConfigSnapshotReader( + keyPrefix: keyPrefix, + storage: .init( + snapshot: multiSnapshot, + accessReporter: accessReporter + ) + ) + return try body(snapshotReader) + } +} diff --git a/Sources/Configuration/Documentation.docc/Documentation.md b/Sources/Configuration/Documentation.docc/Documentation.md index a4aa9bb..d84cfa9 100644 --- a/Sources/Configuration/Documentation.docc/Documentation.md +++ b/Sources/Configuration/Documentation.docc/Documentation.md @@ -358,7 +358,7 @@ Read for guidance on best practices for secrets #### Consistent snapshots Retrieve related values from a consistent snapshot using ``ConfigSnapshotReader``, which you -get from calling ``ConfigReader/withSnapshot(_:)``. +get by calling ``ConfigReader/snapshot()``. This ensures that multiple values are read from a single snapshot inside each provider, even when using providers that update their internal values. @@ -366,11 +366,10 @@ For example by downloading new data periodically: ```swift let config = /* a reader with one or more providers that change values over time */ -try config.withSnapshot { snapshot in - let certificate = try snapshot.requiredString(forKey: "mtls.certificate") - let privateKey = try snapshot.requiredString(forKey: "mtls.privateKey", isSecret: true) - // `certificate` and `privateKey` are guaranteed to come from the same snapshot in the provider -} +let snapshot = config.snapshot() +let certificate = try snapshot.requiredString(forKey: "mtls.certificate") +let privateKey = try snapshot.requiredString(forKey: "mtls.privateKey", isSecret: true) +// `certificate` and `privateKey` are guaranteed to come from the same snapshot in the provider ``` #### Extensible ecosystem diff --git a/Sources/Configuration/Documentation.docc/Reference/ConfigReader.md b/Sources/Configuration/Documentation.docc/Reference/ConfigReader.md index ff31a07..94ef5fd 100644 --- a/Sources/Configuration/Documentation.docc/Reference/ConfigReader.md +++ b/Sources/Configuration/Documentation.docc/Reference/ConfigReader.md @@ -11,7 +11,7 @@ - ``ConfigReader/scoped(to:)`` ### Reading from a snapshot -- ``ConfigReader/withSnapshot(_:)`` +- ``ConfigReader/snapshot()`` - ``ConfigReader/watchSnapshot(fileID:line:updatesHandler:)`` - diff --git a/Sources/Configuration/Documentation.docc/Reference/ConfigSnapshotReader.md b/Sources/Configuration/Documentation.docc/Reference/ConfigSnapshotReader.md index 4236575..0a203ac 100644 --- a/Sources/Configuration/Documentation.docc/Reference/ConfigSnapshotReader.md +++ b/Sources/Configuration/Documentation.docc/Reference/ConfigSnapshotReader.md @@ -3,7 +3,7 @@ ## Topics ### Creating a snapshot -- ``ConfigReader/withSnapshot(_:)`` +- ``ConfigReader/snapshot()`` - ``ConfigReader/watchSnapshot(fileID:line:updatesHandler:)`` ### Namespacing diff --git a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift index 84b5f20..7708a39 100644 --- a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift +++ b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift @@ -32,7 +32,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { @Test func get() async throws { let config = ConfigReaderTests.config - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.string(forKey: "string") == Defaults.string) @@ -61,7 +63,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { // Required - failing #expect(throws: TestProvider.TestError.self) { try snapshot.requiredString(forKey: "failure") } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.int(forKey: "int") == Defaults.int) @@ -90,7 +94,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { // Required - failing #expect(throws: TestProvider.TestError.self) { try snapshot.requiredInt(forKey: "failure") } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.double(forKey: "double") == Defaults.double) @@ -119,7 +125,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { // Required - failing #expect(throws: TestProvider.TestError.self) { try snapshot.requiredDouble(forKey: "failure") } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.bool(forKey: "bool") == Defaults.bool) @@ -148,7 +156,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { // Required - failing #expect(throws: TestProvider.TestError.self) { try snapshot.requiredBool(forKey: "failure") } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.bytes(forKey: "bytes") == Defaults.bytes) @@ -177,7 +187,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { // Required - failing #expect(throws: TestProvider.TestError.self) { try snapshot.requiredBytes(forKey: "failure") } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.stringArray(forKey: "stringArray") == Defaults.stringArray) @@ -215,7 +227,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { // Required - failing #expect(throws: TestProvider.TestError.self) { try snapshot.requiredStringArray(forKey: "failure") } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.intArray(forKey: "intArray") == Defaults.intArray) @@ -246,7 +260,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { // Required - failing #expect(throws: TestProvider.TestError.self) { try snapshot.requiredIntArray(forKey: "failure") } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.doubleArray(forKey: "doubleArray") == Defaults.doubleArray) @@ -284,7 +300,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { // Required - failing #expect(throws: TestProvider.TestError.self) { try snapshot.requiredDoubleArray(forKey: "failure") } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.boolArray(forKey: "boolArray") == Defaults.boolArray) @@ -316,7 +334,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { // Required - failing #expect(throws: TestProvider.TestError.self) { try snapshot.requiredBoolArray(forKey: "failure") } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.byteChunkArray(forKey: "byteChunkArray") == Defaults.byteChunkArray) diff --git a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift.gyb b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift.gyb index 6fe0431..010dab8 100644 --- a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift.gyb +++ b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet1.swift.gyb @@ -34,7 +34,9 @@ struct ConfigSnapshotReaderMethodTestsGet1 { % for primitive_type in primitive_types: % name = primitive_type["name"] % lower_name = lower_first(name) - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.${lower_name}(forKey: "${lower_name}") == Defaults.${lower_name}) diff --git a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift index a7b7e30..1ae2735 100644 --- a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift +++ b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift @@ -32,7 +32,9 @@ struct ConfigSnapshotReaderMethodTestsGet2 { @Test func get() async throws { let config = ConfigReaderTests.config - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect( snapshot.string(forKey: "stringConvertible", as: TestStringConvertible.self) @@ -89,7 +91,9 @@ struct ConfigSnapshotReaderMethodTestsGet2 { try snapshot.requiredString(forKey: "failure", as: TestStringConvertible.self) } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.string(forKey: "stringEnum", as: TestEnum.self) == Defaults.stringEnum) diff --git a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift.gyb b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift.gyb index 77f5ab5..fbd05d8 100644 --- a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift.gyb +++ b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet2.swift.gyb @@ -35,7 +35,9 @@ struct ConfigSnapshotReaderMethodTestsGet2 { % test_type = string_convertible_type["testType"] % test_suffix = string_convertible_type["testSuffix"] % lower_test_suffix = lower_first(test_suffix) - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.string(forKey: "${lower_test_suffix}", as: ${test_type}.self) == Defaults.${lower_test_suffix}) diff --git a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet3.swift b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet3.swift index f0d0a87..948679b 100644 --- a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet3.swift +++ b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet3.swift @@ -32,7 +32,9 @@ struct ConfigSnapshotReaderMethodTestsGet3 { @Test func get() async throws { let config = ConfigReaderTests.config - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect( snapshot.stringArray(forKey: "stringConvertibleArray", as: TestStringConvertible.self) @@ -89,7 +91,9 @@ struct ConfigSnapshotReaderMethodTestsGet3 { try snapshot.requiredStringArray(forKey: "failure", as: TestStringConvertible.self) } } - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.stringArray(forKey: "stringEnumArray", as: TestEnum.self) == Defaults.stringEnumArray) diff --git a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet3.swift.gyb b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet3.swift.gyb index c5f7a32..8333fb9 100644 --- a/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet3.swift.gyb +++ b/Tests/ConfigurationTests/ConfigReaderTests/ConfigSnapshotReaderMethodTestsGet3.swift.gyb @@ -35,7 +35,9 @@ struct ConfigSnapshotReaderMethodTestsGet3 { % test_type = string_convertible_type["testType"] % test_suffix = string_convertible_type["testSuffix"] + "Array" % lower_test_suffix = lower_first(test_suffix) - try config.withSnapshot { snapshot in + do { + let snapshot = config.snapshot() + // Optional - success #expect(snapshot.stringArray(forKey: "${lower_test_suffix}", as: ${test_type}.self) == Defaults.${lower_test_suffix}) diff --git a/Tests/ConfigurationTests/ConfigSnapshotReaderTests.swift b/Tests/ConfigurationTests/ConfigSnapshotReaderTests.swift index 720966a..e2211c9 100644 --- a/Tests/ConfigurationTests/ConfigSnapshotReaderTests.swift +++ b/Tests/ConfigurationTests/ConfigSnapshotReaderTests.swift @@ -27,10 +27,9 @@ struct ConfigSnapshotReaderTests { ] ) let config = ConfigReader(provider: provider) - try config.withSnapshot { snapshot in - try #require(snapshot.string(forKey: "http.stuff", default: "test") == "test") - try #require(snapshot.string(forKey: "http.client.user-agent") == "Config/1.0 (Test)") - } + let snapshot = config.snapshot() + try #require(snapshot.string(forKey: "http.stuff", default: "test") == "test") + try #require(snapshot.string(forKey: "http.client.user-agent") == "Config/1.0 (Test)") } @available(Configuration 1.0, *) @@ -60,10 +59,9 @@ struct ConfigSnapshotReaderTests { ] ) let config = ConfigReader(provider: provider) - config.withSnapshot { snapshot in - #expect(snapshot.string(forKey: "user-agent") == nil) - let scoped = snapshot.scoped(to: "http.client") - #expect(scoped.string(forKey: "user-agent") == "Config/1.0 (Test)") - } + let snapshot = config.snapshot() + #expect(snapshot.string(forKey: "user-agent") == nil) + let scoped = snapshot.scoped(to: "http.client") + #expect(scoped.string(forKey: "user-agent") == "Config/1.0 (Test)") } } diff --git a/Tests/ConfigurationTests/MutableInMemoryProviderTests.swift b/Tests/ConfigurationTests/MutableInMemoryProviderTests.swift index 6b75f8b..7e5b00f 100644 --- a/Tests/ConfigurationTests/MutableInMemoryProviderTests.swift +++ b/Tests/ConfigurationTests/MutableInMemoryProviderTests.swift @@ -132,11 +132,11 @@ struct MutableInMemoryProviderTests { let provider = makeProvider() let config = ConfigReader(provider: provider) - config.withSnapshot { snapshot in - #expect(snapshot.bool(forKey: "bool") == true) - provider.setValue(false, forKey: "bool") - #expect(snapshot.bool(forKey: "bool") == true) - } + let snapshot = config.snapshot() + #expect(snapshot.bool(forKey: "bool") == true) + provider.setValue(false, forKey: "bool") + #expect(snapshot.bool(forKey: "bool") == true) + #expect(config.bool(forKey: "bool") == false) }