//===----------------------------------------------------------------------===// // Copyright © 2026 Apple Inc. and the container project authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //===----------------------------------------------------------------------===// import Configuration /// A decoder that decodes `Decodable` types from a ``ConfigSnapshotReader``. /// /// `ConfigSnapshotDecoder` bridges Swift's `Decodable` protocol with the configuration /// provider system, allowing you to decode structured configuration into typed /// Swift values. /// /// ```swift /// struct AppConfig: Decodable { /// var host: String /// var port: Int /// var database: DatabaseConfig /// } /// /// struct DatabaseConfig: Decodable { /// var connectionString: String /// var maxConnections: Int /// } /// /// let reader = ConfigReader(providers: [envProvider, jsonProvider]) /// let snapshot = reader.snapshot() /// let config = try ConfigSnapshotDecoder().decode(AppConfig.self, from: snapshot) /// ``` /// /// Nested structs map to dot-separated key paths. In the example above, /// `database.connectionString` and `database.maxConnections` are looked up /// from the snapshot. public struct ConfigSnapshotDecoder: Sendable { public var userInfo: [CodingUserInfoKey: any Sendable] private let typeDecodingStrategies: [ObjectIdentifier: AnyConfigDecodingStrategy] public init(decodingStrategies: [any ConfigDecodingStrategy] = [URLConfigDecodingStrategy()]) { self.userInfo = [:] var strategies: [ObjectIdentifier: AnyConfigDecodingStrategy] = [:] for strategy in decodingStrategies { let erased = AnyConfigDecodingStrategy(strategy) strategies[erased.valueTypeID] = erased } self.typeDecodingStrategies = strategies } public func decode( _ type: T.Type, from snapshot: ConfigSnapshotReader ) throws -> T { if type is any UnsupportedDictionaryDecoding.Type { throw DecodingError.typeMismatch( T.self, DecodingError.Context( codingPath: [], debugDescription: "ConfigSnapshotDecoder does not support decoding dictionaries (got \(T.self)). Represent dynamic keys as nested structs with known property names." ) ) } let decoder = ConfigSnapshotDecoderImpl( snapshot: snapshot, codingPath: [], userInfo: userInfo.mapValues { $0 as Any }, typeDecodingStrategies: typeDecodingStrategies ) return try T(from: decoder) } } struct ConfigSnapshotDecoderImpl: Decoder { let snapshot: ConfigSnapshotReader let codingPath: [any CodingKey] let userInfo: [CodingUserInfoKey: Any] let typeDecodingStrategies: [ObjectIdentifier: AnyConfigDecodingStrategy] func container( keyedBy type: Key.Type ) throws -> KeyedDecodingContainer { KeyedDecodingContainer( KeyedContainer( snapshot: snapshot, codingPath: codingPath, userInfo: userInfo, typeDecodingStrategies: typeDecodingStrategies ) ) } func unkeyedContainer() throws -> any UnkeyedDecodingContainer { UnkeyedContainer( snapshot: snapshot, codingPath: codingPath, userInfo: userInfo, typeDecodingStrategies: typeDecodingStrategies ) } func singleValueContainer() throws -> any SingleValueDecodingContainer { SingleValueContainer( snapshot: snapshot, codingPath: codingPath, userInfo: userInfo, typeDecodingStrategies: typeDecodingStrategies ) } }