mirror of
https://github.com/apple/container.git
synced 2026-07-12 20:47:04 +00:00
beb1592c04
This adds a swift Decoder that decodes from a swift-configuration ConfigSnapshot, and adds the ability to modify how select types are decoded within the ConfigSnapshotDecoder. This is something that JSONEncoder already does with URLs specifically, however this enables users to configure this behavior for any arbitrary type regardless of existing Codable conformance. The decoder will help us expand on top of (pr: https://github.com/apple/container/pull/1425), which switches user configuration to TOML. With this PR we will be able to provide a multi layered TOML if desired, allowing container to ship a default TOML instead of encoding them in code constants.
111 lines
3.8 KiB
Swift
111 lines
3.8 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// 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<T: Decodable>(
|
|
_ type: T.Type,
|
|
from snapshot: ConfigSnapshotReader
|
|
) throws -> T {
|
|
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<Key: CodingKey>(
|
|
keyedBy type: Key.Type
|
|
) throws -> KeyedDecodingContainer<Key> {
|
|
KeyedDecodingContainer(
|
|
KeyedContainer<Key>(
|
|
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
|
|
)
|
|
}
|
|
}
|