From c55ffa3d06788b5527a4d7a7d9e0188f8e0073fb Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Thu, 23 Oct 2025 14:22:24 +0200 Subject: [PATCH 1/5] [Proposal] SCO-0001: Generic file providers --- .../Documentation.docc/Proposals/SCO-0001.md | 543 ++++++++++++++++++ 1 file changed, 543 insertions(+) create mode 100644 Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md diff --git a/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md b/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md new file mode 100644 index 0000000..8a15ee6 --- /dev/null +++ b/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md @@ -0,0 +1,543 @@ +# SCO-0001: Generic file providers + +Introduce format-agnostic providers to simplify implementing additional file formats beyond JSON and YAML. + +## Overview + +- Proposal: SCO-0001 +- Author(s): [Honza Dvorsky](https://github.com/czechboy0) +- Status: **In Review** +- Issue: [apple/swift-configuration#61](https://github.com/apple/swift-configuration/issues/61) +- Implementation: + - [apple/swift-configuration#60](https://github.com/apple/swift-configuration/pull/60) +- Related links: + - [Feedback](https://github.com/apple/swift-configuration/issues/37#issuecomment-3360375383) on having to copy boilerplate code to implement a TOML provider. + +### Introduction + +Introduce generic file providers that integrate with user-provided format parsers. + +### Motivation + +While Swift Configuration 0.1.0 shipped with JSON and YAML support built in, there are many other file formats used for configuration files. The package does not strive to support every format out of the box, and should instead offer customization points allowing the community to plug in additional format parsers without reimplementing the shared file-handling and file-reloading logic. + +### Proposed solution + +We propose to remove the existing JSON and YAML providers (namely, `JSONProvider`, `ReloadingJSONProvider`, `YAMLProvider`, `ReloadingYAMLProvider`) and replace them with two generic types: +- `FileProvider`, a stateless provider that loads the file at initialization time and never reloads it again. +- `ReloadingFileProvider`, a stateful provider that loads the file at initialization time and then reloads it based on a customizable interval. This provider must be run in a dedicated task (for example, using Swift Service Lifecycle). + +The provider types are generic over a type conforming to a new protocol called `FileConfigSnapshotProtocol`. This protocol refines the existing protocols `ConfigSnapshotProtocol`, `CustomStringConvertible`, and `CustomDebugStringConvertible`. It adds a requirement for a throwing initializer that takes the raw file contents as input. + +The requirements for `CustomStringConvertible` and `CustomDebugStringConvertible` get used by each of the file provider's own `description` and `debugDescription` conformance, and should take care to avoid printing configuration values marked as secret. + +The protocol `FileConfigSnapshotProtocol` also defines an associated type conforming to `FileParsingOptionsProtocol`, which can be used to provide additional inputs needed to parse the file contents and create the snapshot. Some examples of values that can be propagated to the parsing initializer using `ParsingOptions` include a format-specific decoder, prebuilt mappings, and a secrets specifier. + +The snapshot type, implemented by each supported format, either in the Swift Configuration package or in community packages, allows implementing just the logic to parse the file contents and retrieve configuration values for a provided key. Swift Configuration provides two new types in its API, `JSONSnapshot` and `YAMLSnapshot`, preserving built-in support for these two formats. + +Existing adopters would change their usage of the existing JSON and YAML provider types as follows: + +```diff +-JSONProvider ++FileProvider + +-YAMLProvider ++FileProvider + +-ReloadingJSONProvider ++ReloadingFileProvider + +-ReloadingYAMLProvider ++ReloadingFileProvider +``` + +Adopters who need to use a format not supported out of the box by Swift Configuration can implement a type that conforms to `FileConfigSnapshotProtocol` and share it as a standalone package. Any such type can then be used with `FileProvider` and `ReloadingFileProvider`. + +### Detailed design + +#### `FileConfigSnapshotProtocol` + +```swift +/// A type that provides parsing options for file configuration snapshots. +/// +/// This protocol defines the requirements for parsing options types used when creating +/// file-based configuration snapshots. Types conforming to this protocol provide +/// additional configuration or processing parameters that affect how file data is +/// interpreted and parsed. +/// +/// ## Usage +/// +/// Implement this protocol to provide parsing options: +/// +/// ```swift +/// struct MyParsingOptions: FileParsingOptionsProtocol { +/// let encoding: String.Encoding +/// let dateFormat: String? +/// +/// static let `default` = MyParsingOptions( +/// encoding: .utf8, +/// dateFormat: nil +/// ) +/// } +/// ``` +public protocol FileParsingOptionsProtocol: Sendable { + /// The default instance of this options type. + /// + /// This property provides a default configuration that can be used when + /// no parsing options are specified. + static var `default`: Self { get } +} + +/// A protocol for configuration snapshots created from file data. +/// +/// This protocol extends ``ConfigSnapshotProtocol`` to provide file-specific functionality +/// for creating configuration snapshots from raw file data. Types conforming to this protocol +/// can parse various file formats (such as JSON and YAML) and convert them into configuration values. +/// +/// Commonly used with ``FileProvider`` and ``ReloadingFileProvider``. +/// +/// ## Implementation +/// +/// To create a custom file configuration snapshot: +/// +/// ```swift +/// struct MyFormatSnapshot: FileConfigSnapshotProtocol { +/// typealias ParsingOptions = MyParsingOptions +/// +/// let values: [String: ConfigValue] +/// let providerName: String +/// +/// init(data: Data, providerName: String, parsingOptions: MyParsingOptions) throws { +/// self.providerName = providerName +/// // Parse the data according to your format +/// self.values = try parseMyFormat(data, using: parsingOptions) +/// } +/// } +/// ``` +/// +/// The snapshot is responsible for parsing the file data and converting it into a +/// representation of configuration values that can be queried by the configuration system. +public protocol FileConfigSnapshotProtocol: + ConfigSnapshotProtocol, CustomStringConvertible, CustomDebugStringConvertible +{ + /// The parsing options type used for parsing this snapshot. + associatedtype ParsingOptions: FileParsingOptionsProtocol + + /// Creates a new snapshot from file data. + /// + /// This initializer parses the provided file data and creates a snapshot + /// containing the configuration values found in the file. + /// + /// - Parameters: + /// - data: The raw file data to parse. + /// - providerName: The name of the provider creating this snapshot. + /// - parsingOptions: Parsing options that affect parsing behavior. + /// - Throws: If the file data cannot be parsed or contains invalid configuration. + init(data: Data, providerName: String, parsingOptions: ParsingOptions) throws +} +``` + +#### `FileProvider` + +```swift +/// A configuration provider that reads from a file on disk using a configurable snapshot type. +/// +/// `FileProvider` is a generic file-based configuration provider that works with different +/// file formats by using different snapshot types that conform to ``FileConfigSnapshotProtocol``. +/// This allows for a unified interface for reading JSON, YAML, or other structured configuration files. +/// +/// ## Usage +/// +/// Create a provider by specifying the snapshot type and file path: +/// +/// ```swift +/// // Using with JSON snapshot +/// let jsonProvider = try await FileProvider( +/// filePath: "/etc/config.json" +/// ) +/// +/// // Using with YAML snapshot +/// let yamlProvider = try await FileProvider( +/// filePath: "/etc/config.yaml" +/// ) +/// ``` +/// +/// The provider reads the file once during initialization and creates an immutable snapshot +/// of the configuration values. For auto-reloading behavior, use ``ReloadingFileProvider``. +/// +/// ## Configuration from a reader +/// +/// You can also initialize the provider using a configuration reader that specifies +/// the file path through environment variables or other configuration sources: +/// +/// ```swift +/// let envConfig = ConfigReader(provider: EnvironmentVariablesProvider()) +/// let provider = try await FileProvider(config: envConfig) +/// ``` +/// +/// This expects a `filePath` key in the configuration that specifies the path to the file. +/// For a full list of configuration keys, check out ``FileProvider/init(snapshotType:parsingOptions:config:)``. +public struct FileProvider< + SnapshotType: FileConfigSnapshotProtocol +> : Sendable { + + /// Creates a file provider that reads from the specified file path. + /// + /// This initializer reads the file at the given path and creates a snapshot using the + /// specified snapshot type. The file is read once during initialization. + /// + /// - Parameters: + /// - snapshotType: The type of snapshot to create from the file contents. + /// - parsingOptions: Options used by the snapshot to parse the file data. + /// - filePath: The path to the configuration file to read. + /// - Throws: If the file cannot be read or if snapshot creation fails. + public init( + snapshotType: SnapshotType.Type = SnapshotType.self, + parsingOptions: SnapshotType.ParsingOptions = .default, + filePath: FilePath + ) async throws + + /// Creates a file provider using a file path from a configuration reader. + /// + /// This initializer reads the file path from the provided configuration reader + /// and creates a snapshot from that file. + /// + /// ## Configuration keys + /// - `filePath` (string, required): The path to the configuration file to read. + /// + /// - Parameters: + /// - snapshotType: The type of snapshot to create from the file contents. + /// - parsingOptions: Options used by the snapshot to parse the file data. + /// - config: A configuration reader that contains the required configuration keys. + /// - Throws: If the `filePath` key is missing, if the file cannot be read, or if snapshot creation fails. + public init( + snapshotType: SnapshotType.Type = SnapshotType.self, + parsingOptions: SnapshotType.ParsingOptions = .default, + config: ConfigReader + ) async throws +} + +extension FileProvider : CustomStringConvertible { + public var description: String { get } +} + +extension FileProvider : CustomDebugStringConvertible { + public var debugDescription: String { get } +} + +extension FileProvider : ConfigProvider { + public var providerName: String { get } + public func value( + forKey key: AbsoluteConfigKey, + type: ConfigType + ) throws -> LookupResult + public func fetchValue( + forKey key: AbsoluteConfigKey, + type: ConfigType + ) async throws -> LookupResult + public func watchSnapshot( + updatesHandler: ( + ConfigUpdatesAsyncSequence + ) async throws -> Return + ) async throws -> Return + public func watchValue( + forKey key: AbsoluteConfigKey, + type: ConfigType, + updatesHandler: ( + ConfigUpdatesAsyncSequence, Never> + ) async throws -> Return + ) async throws -> Return + public func snapshot() -> any ConfigSnapshotProtocol +} +``` + +#### `ReloadingFileProvider` + +```swift +/// A configuration provider that reads configuration from a file on disk with automatic reloading capability. +/// +/// `ReloadingFileProvider` is a generic file-based configuration provider that monitors +/// a configuration file for changes and automatically reloads the data when +/// the file is modified. This provider works with different file formats by using +/// different snapshot types that conform to ``FileConfigSnapshotProtocol``. +/// +/// ## Usage +/// +/// Create a reloading provider by specifying the snapshot type and file path: +/// +/// ```swift +/// // Using with a JSON snapshot and a custom poll interval +/// let jsonProvider = try await ReloadingFileProvider( +/// filePath: "/etc/config.json", +/// pollInterval: .seconds(30) +/// ) +/// +/// // Using with a YAML snapshot +/// let yamlProvider = try await ReloadingFileProvider( +/// filePath: "/etc/config.yaml" +/// ) +/// ``` +/// +/// ## Service integration +/// +/// This provider implements the `Service` protocol and must be run within a `ServiceGroup` +/// to enable automatic reloading: +/// +/// ```swift +/// let provider = try await ReloadingFileProvider(filePath: "/etc/config.json") +/// let serviceGroup = ServiceGroup(services: [provider], logger: logger) +/// try await serviceGroup.run() +/// ``` +/// +/// The provider monitors the file by polling at the specified interval (default: 15 seconds) +/// and notifies any active watchers when changes are detected. +/// +/// ## Configuration from a reader +/// +/// You can also initialize the provider using a configuration reader: +/// +/// ```swift +/// let envConfig = ConfigReader(provider: EnvironmentVariablesProvider()) +/// let provider = try await ReloadingFileProvider(config: envConfig) +/// ``` +/// +/// This expects a `filePath` key in the configuration that specifies the path to the file. +/// For a full list of configuration keys, check out ``ReloadingFileProvider/init(snapshotType:parsingOptions:config:logger:metrics:)``. +/// +/// ## File monitoring +/// +/// The provider detects changes by monitoring both file timestamps and symlink target changes. +/// When a change is detected, it reloads the file and notifies all active watchers of the +/// updated configuration values. +public final class ReloadingFileProvider< + SnapshotType: FileConfigSnapshotProtocol +>: Sendable { + + /// Creates a reloading file provider that monitors the specified file path. + /// + /// - Parameters: + /// - snapshotType: The type of snapshot to create from the file contents. + /// - parsingOptions: Options used by the snapshot to parse the file data. + /// - filePath: The path to the configuration file to monitor. + /// - pollInterval: How often to check for file changes. + /// - logger: The logger instance to use for this provider. + /// - metrics: The metrics factory to use for monitoring provider performance. + /// - Throws: If the file cannot be read or if snapshot creation fails. + public convenience init( + snapshotType: SnapshotType.Type = SnapshotType.self, + parsingOptions: SnapshotType.ParsingOptions = .default, + filePath: FilePath, + pollInterval: Duration = .seconds(15), + logger: Logger = Logger(label: "ReloadingFileProvider"), + metrics: any MetricsFactory = MetricsSystem.factory + ) async throws + + /// Creates a reloading file provider using configuration from a reader. + /// + /// ## Configuration keys + /// - `filePath` (string, required): The path to the configuration file to monitor. + /// - `pollIntervalSeconds` (int, optional, default: 15): How often to check for file changes in seconds. + /// + /// - Parameters: + /// - snapshotType: The type of snapshot to create from the file contents. + /// - parsingOptions: Options used by the snapshot to parse the file data. + /// - config: A configuration reader that contains the required configuration keys. + /// - logger: The logger instance to use for this provider. + /// - metrics: The metrics factory to use for monitoring provider performance. + /// - Throws: If required configuration keys are missing, if the file cannot be read, or if snapshot creation fails. + public convenience init( + snapshotType: SnapshotType.Type = SnapshotType.self, + parsingOptions: SnapshotType.ParsingOptions = .default, + config: ConfigReader, + logger: Logger = Logger(label: "ReloadingFileProvider"), + metrics: any MetricsFactory = MetricsSystem.factory + ) async throws +} + +extension ReloadingFileProvider : CustomStringConvertible { + public var description: String { get } +} + +extension ReloadingFileProvider : CustomDebugStringConvertible { + public var debugDescription: String { get } +} + +extension ReloadingFileProvider : ConfigProvider { + public var providerName: String { get } + public func value( + forKey key: AbsoluteConfigKey, + type: ConfigType + ) throws -> LookupResult + public func fetchValue( + forKey key: AbsoluteConfigKey, + type: ConfigType + ) async throws -> LookupResult + public func watchSnapshot( + updatesHandler: ( + ConfigUpdatesAsyncSequence + ) async throws -> Return + ) async throws -> Return + public func watchValue( + forKey key: AbsoluteConfigKey, + type: ConfigType, + updatesHandler: ( + ConfigUpdatesAsyncSequence, Never> + ) async throws -> Return + ) async throws -> Return + public func snapshot() -> any ConfigSnapshotProtocol +} + +extension ReloadingFileProvider: Service { + public func run() async throws +} +``` + +#### `JSONSnapshot` + +```swift +/// A snapshot of configuration values parsed from JSON data. +/// +/// This structure represents a point-in-time view of configuration values. It handles +/// the conversion from JSON types to configuration value types. +/// +/// Commonly used with ``FileProvider`` and ``ReloadingFileProvider``. +public struct JSONSnapshot { + /// Parsing options for JSON snapshot creation. + /// + /// This struct provides configuration options for parsing JSON data into configuration snapshots, + /// including byte decoding and secrets specification. + public struct ParsingOptions: FileParsingOptionsProtocol { + + /// A decoder of bytes from a string. + public var bytesDecoder: any ConfigBytesFromStringDecoder + + /// A specifier for determining which configuration values should be treated as secrets. + public var secretsSpecifier: SecretsSpecifier + + /// Creates parsing options for JSON snapshots. + /// + /// - Parameters: + /// - bytesDecoder: The decoder to use for converting string values to byte arrays. + /// - secretsSpecifier: The specifier for identifying secret values. + public init( + bytesDecoder: some ConfigBytesFromStringDecoder = .base64, + secretsSpecifier: SecretsSpecifier = .none + ) + + /// The default parsing options. + /// + /// Uses base64 byte decoding and treats no values as secrets. + public static var `default`: Self + } + + /// The name of the provider that created this snapshot. + public let providerName: String +} + +extension JSONSnapshot: FileConfigSnapshotProtocol { + public init( + data: Data, + providerName: String, + parsingOptions: ParsingOptions + ) throws +} + +extension JSONSnapshot: ConfigSnapshotProtocol { + public func value( + forKey key: AbsoluteConfigKey, + type: ConfigType + ) throws -> LookupResult +} + +extension JSONSnapshot: CustomStringConvertible { + public var description: String +} + +extension JSONSnapshot: CustomDebugStringConvertible { + public var debugDescription: String +} +``` + +#### `YAMLSnapshot` + +```swift +/// A snapshot of configuration values parsed from YAML data. +/// +/// This structure represents a point-in-time view of configuration values. It handles +/// the conversion from YAML types to configuration value types. +/// +/// Commonly used with ``FileProvider`` and ``ReloadingFileProvider``. +public struct YAMLSnapshot { + /// Parsing options for YAML snapshot creation. + /// + /// This struct provides configuration options for parsing YAML data into configuration snapshots, + /// including byte decoding and secrets specification. + public struct ParsingOptions: FileParsingOptionsProtocol { + + /// A decoder of bytes from a string. + public var bytesDecoder: any ConfigBytesFromStringDecoder + + /// A specifier for determining which configuration values should be treated as secrets. + public var secretsSpecifier: SecretsSpecifier + + /// Creates parsing options for YAML snapshots. + /// + /// - Parameters: + /// - bytesDecoder: The decoder to use for converting string values to byte arrays. + /// - secretsSpecifier: The specifier for identifying secret values. + public init( + bytesDecoder: some ConfigBytesFromStringDecoder = .base64, + secretsSpecifier: SecretsSpecifier = .none + ) + + /// The default parsing options. + /// + /// Uses base64 byte decoding and treats no values as secrets. + public static var `default`: Self + } + + /// The name of the provider that created this snapshot. + public let providerName: String +} + +extension YAMLSnapshot: FileConfigSnapshotProtocol { + public init( + data: Data, + providerName: String, + parsingOptions: ParsingOptions + ) throws +} + +extension YAMLSnapshot: ConfigSnapshotProtocol { + public func value( + forKey key: AbsoluteConfigKey, + type: ConfigType + ) throws -> LookupResult +} + +extension YAMLSnapshot: CustomStringConvertible { + public var description: String +} + +extension YAMLSnapshot: CustomDebugStringConvertible { + public var debugDescription: String +} +``` + +### API stability + +The existing 4 APIs will be removed: +- `JSONProvider` +- `ReloadingJSONProvider` +- `YAMLProvider` +- `ReloadingYAMLProvider` + +All other APIs mentioned above are new and won't affect existing adopters. + +### Future directions + +The current file providers described in this proposal handle a single file. In the future, we could provide multi-file variants of these providers to handle formats that break up configuration into dependent files. + +### Alternatives considered + +We only shipped the concrete JSON and YAML providers in 0.1.0. However, early feedback has shown that there's demand for additional formats, and there's no reason to require those adopters to reimplement the common file logic from scratch. From 75bf8f97fef70128887618eba5c0ae6604f81313 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Thu, 23 Oct 2025 14:26:15 +0200 Subject: [PATCH 2/5] Add SCO-0001 --- Sources/Configuration/Documentation.docc/Proposals/Proposals.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Configuration/Documentation.docc/Proposals/Proposals.md b/Sources/Configuration/Documentation.docc/Proposals/Proposals.md index 86861db..c3c270a 100644 --- a/Sources/Configuration/Documentation.docc/Proposals/Proposals.md +++ b/Sources/Configuration/Documentation.docc/Proposals/Proposals.md @@ -36,3 +36,4 @@ If you have any questions, ask in an issue on GitHub. ## Topics - +- From 484ec5b3d9503aba7dfdb1f3116533619e3c689e Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 27 Oct 2025 18:52:28 +0100 Subject: [PATCH 3/5] Proposal updates based on Franz's feedback --- .../Documentation.docc/Proposals/SCO-0001.md | 64 ++++++++++--------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md b/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md index 8a15ee6..6d72933 100644 --- a/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md +++ b/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md @@ -12,6 +12,12 @@ Introduce format-agnostic providers to simplify implementing additional file for - [apple/swift-configuration#60](https://github.com/apple/swift-configuration/pull/60) - Related links: - [Feedback](https://github.com/apple/swift-configuration/issues/37#issuecomment-3360375383) on having to copy boilerplate code to implement a TOML provider. +- Revisions: + - v1 - Oct 23, 2025 - Initial proposal. + - v2 - Oct 27, 2025 - Updates based on feedback from Franz Busch: + - 1. Dropped the `Protocol` suffix in `FileConfigSnapshotProtocol` and `FileParsingOptionsProtocol`. + - 2. Renamed generic parameter `SnapshotType` to `Snapshot` in `FileProvider` and `ReloadingFileProvider`. + - 3. Changed the parameter type from `Data` to `RawSpan` in the `FileConfigSnapshot` initializer. ### Introduction @@ -27,11 +33,11 @@ We propose to remove the existing JSON and YAML providers (namely, `JSONProvider - `FileProvider`, a stateless provider that loads the file at initialization time and never reloads it again. - `ReloadingFileProvider`, a stateful provider that loads the file at initialization time and then reloads it based on a customizable interval. This provider must be run in a dedicated task (for example, using Swift Service Lifecycle). -The provider types are generic over a type conforming to a new protocol called `FileConfigSnapshotProtocol`. This protocol refines the existing protocols `ConfigSnapshotProtocol`, `CustomStringConvertible`, and `CustomDebugStringConvertible`. It adds a requirement for a throwing initializer that takes the raw file contents as input. +The provider types are generic over a type conforming to a new protocol called `FileConfigSnapshot`. This protocol refines the existing protocols `ConfigSnapshotProtocol`, `CustomStringConvertible`, and `CustomDebugStringConvertible`. It adds a requirement for a throwing initializer that takes the raw file contents as input. The requirements for `CustomStringConvertible` and `CustomDebugStringConvertible` get used by each of the file provider's own `description` and `debugDescription` conformance, and should take care to avoid printing configuration values marked as secret. -The protocol `FileConfigSnapshotProtocol` also defines an associated type conforming to `FileParsingOptionsProtocol`, which can be used to provide additional inputs needed to parse the file contents and create the snapshot. Some examples of values that can be propagated to the parsing initializer using `ParsingOptions` include a format-specific decoder, prebuilt mappings, and a secrets specifier. +The protocol `FileConfigSnapshot` also defines an associated type conforming to `FileParsingOptions`, which can be used to provide additional inputs needed to parse the file contents and create the snapshot. Some examples of values that can be propagated to the parsing initializer using `ParsingOptions` include a format-specific decoder, prebuilt mappings, and a secrets specifier. The snapshot type, implemented by each supported format, either in the Swift Configuration package or in community packages, allows implementing just the logic to parse the file contents and retrieve configuration values for a provided key. Swift Configuration provides two new types in its API, `JSONSnapshot` and `YAMLSnapshot`, preserving built-in support for these two formats. @@ -51,11 +57,11 @@ Existing adopters would change their usage of the existing JSON and YAML provide +ReloadingFileProvider ``` -Adopters who need to use a format not supported out of the box by Swift Configuration can implement a type that conforms to `FileConfigSnapshotProtocol` and share it as a standalone package. Any such type can then be used with `FileProvider` and `ReloadingFileProvider`. +Adopters who need to use a format not supported out of the box by Swift Configuration can implement a type that conforms to `FileConfigSnapshot` and share it as a standalone package. Any such type can then be used with `FileProvider` and `ReloadingFileProvider`. ### Detailed design -#### `FileConfigSnapshotProtocol` +#### `FileConfigSnapshot` ```swift /// A type that provides parsing options for file configuration snapshots. @@ -70,7 +76,7 @@ Adopters who need to use a format not supported out of the box by Swift Configur /// Implement this protocol to provide parsing options: /// /// ```swift -/// struct MyParsingOptions: FileParsingOptionsProtocol { +/// struct MyParsingOptions: FileParsingOptions { /// let encoding: String.Encoding /// let dateFormat: String? /// @@ -80,7 +86,7 @@ Adopters who need to use a format not supported out of the box by Swift Configur /// ) /// } /// ``` -public protocol FileParsingOptionsProtocol: Sendable { +public protocol FileParsingOptions: Sendable { /// The default instance of this options type. /// /// This property provides a default configuration that can be used when @@ -101,13 +107,13 @@ public protocol FileParsingOptionsProtocol: Sendable { /// To create a custom file configuration snapshot: /// /// ```swift -/// struct MyFormatSnapshot: FileConfigSnapshotProtocol { +/// struct MyFormatSnapshot: FileConfigSnapshot { /// typealias ParsingOptions = MyParsingOptions /// /// let values: [String: ConfigValue] /// let providerName: String /// -/// init(data: Data, providerName: String, parsingOptions: MyParsingOptions) throws { +/// init(data: RawSpan, providerName: String, parsingOptions: MyParsingOptions) throws { /// self.providerName = providerName /// // Parse the data according to your format /// self.values = try parseMyFormat(data, using: parsingOptions) @@ -117,11 +123,11 @@ public protocol FileParsingOptionsProtocol: Sendable { /// /// The snapshot is responsible for parsing the file data and converting it into a /// representation of configuration values that can be queried by the configuration system. -public protocol FileConfigSnapshotProtocol: +public protocol FileConfigSnapshot: ConfigSnapshotProtocol, CustomStringConvertible, CustomDebugStringConvertible { /// The parsing options type used for parsing this snapshot. - associatedtype ParsingOptions: FileParsingOptionsProtocol + associatedtype ParsingOptions: FileParsingOptions /// Creates a new snapshot from file data. /// @@ -133,7 +139,7 @@ public protocol FileConfigSnapshotProtocol: /// - providerName: The name of the provider creating this snapshot. /// - parsingOptions: Parsing options that affect parsing behavior. /// - Throws: If the file data cannot be parsed or contains invalid configuration. - init(data: Data, providerName: String, parsingOptions: ParsingOptions) throws + init(data: RawSpan, providerName: String, parsingOptions: ParsingOptions) throws } ``` @@ -143,7 +149,7 @@ public protocol FileConfigSnapshotProtocol: /// A configuration provider that reads from a file on disk using a configurable snapshot type. /// /// `FileProvider` is a generic file-based configuration provider that works with different -/// file formats by using different snapshot types that conform to ``FileConfigSnapshotProtocol``. +/// file formats by using different snapshot types that conform to ``FileConfigSnapshot``. /// This allows for a unified interface for reading JSON, YAML, or other structured configuration files. /// /// ## Usage @@ -178,7 +184,7 @@ public protocol FileConfigSnapshotProtocol: /// This expects a `filePath` key in the configuration that specifies the path to the file. /// For a full list of configuration keys, check out ``FileProvider/init(snapshotType:parsingOptions:config:)``. public struct FileProvider< - SnapshotType: FileConfigSnapshotProtocol + Snapshot: FileConfigSnapshot > : Sendable { /// Creates a file provider that reads from the specified file path. @@ -192,8 +198,8 @@ public struct FileProvider< /// - filePath: The path to the configuration file to read. /// - Throws: If the file cannot be read or if snapshot creation fails. public init( - snapshotType: SnapshotType.Type = SnapshotType.self, - parsingOptions: SnapshotType.ParsingOptions = .default, + snapshotType: Snapshot.Type = Snapshot.self, + parsingOptions: Snapshot.ParsingOptions = .default, filePath: FilePath ) async throws @@ -211,8 +217,8 @@ public struct FileProvider< /// - config: A configuration reader that contains the required configuration keys. /// - Throws: If the `filePath` key is missing, if the file cannot be read, or if snapshot creation fails. public init( - snapshotType: SnapshotType.Type = SnapshotType.self, - parsingOptions: SnapshotType.ParsingOptions = .default, + snapshotType: Snapshot.Type = Snapshot.self, + parsingOptions: Snapshot.ParsingOptions = .default, config: ConfigReader ) async throws } @@ -259,7 +265,7 @@ extension FileProvider : ConfigProvider { /// `ReloadingFileProvider` is a generic file-based configuration provider that monitors /// a configuration file for changes and automatically reloads the data when /// the file is modified. This provider works with different file formats by using -/// different snapshot types that conform to ``FileConfigSnapshotProtocol``. +/// different snapshot types that conform to ``FileConfigSnapshot``. /// /// ## Usage /// @@ -310,7 +316,7 @@ extension FileProvider : ConfigProvider { /// When a change is detected, it reloads the file and notifies all active watchers of the /// updated configuration values. public final class ReloadingFileProvider< - SnapshotType: FileConfigSnapshotProtocol + Snapshot: FileConfigSnapshot >: Sendable { /// Creates a reloading file provider that monitors the specified file path. @@ -324,8 +330,8 @@ public final class ReloadingFileProvider< /// - metrics: The metrics factory to use for monitoring provider performance. /// - Throws: If the file cannot be read or if snapshot creation fails. public convenience init( - snapshotType: SnapshotType.Type = SnapshotType.self, - parsingOptions: SnapshotType.ParsingOptions = .default, + snapshotType: Snapshot.Type = Snapshot.self, + parsingOptions: Snapshot.ParsingOptions = .default, filePath: FilePath, pollInterval: Duration = .seconds(15), logger: Logger = Logger(label: "ReloadingFileProvider"), @@ -346,8 +352,8 @@ public final class ReloadingFileProvider< /// - metrics: The metrics factory to use for monitoring provider performance. /// - Throws: If required configuration keys are missing, if the file cannot be read, or if snapshot creation fails. public convenience init( - snapshotType: SnapshotType.Type = SnapshotType.self, - parsingOptions: SnapshotType.ParsingOptions = .default, + snapshotType: Snapshot.Type = Snapshot.self, + parsingOptions: Snapshot.ParsingOptions = .default, config: ConfigReader, logger: Logger = Logger(label: "ReloadingFileProvider"), metrics: any MetricsFactory = MetricsSystem.factory @@ -406,7 +412,7 @@ public struct JSONSnapshot { /// /// This struct provides configuration options for parsing JSON data into configuration snapshots, /// including byte decoding and secrets specification. - public struct ParsingOptions: FileParsingOptionsProtocol { + public struct ParsingOptions: FileParsingOptions { /// A decoder of bytes from a string. public var bytesDecoder: any ConfigBytesFromStringDecoder @@ -434,9 +440,9 @@ public struct JSONSnapshot { public let providerName: String } -extension JSONSnapshot: FileConfigSnapshotProtocol { +extension JSONSnapshot: FileConfigSnapshot { public init( - data: Data, + data: RawSpan, providerName: String, parsingOptions: ParsingOptions ) throws @@ -472,7 +478,7 @@ public struct YAMLSnapshot { /// /// This struct provides configuration options for parsing YAML data into configuration snapshots, /// including byte decoding and secrets specification. - public struct ParsingOptions: FileParsingOptionsProtocol { + public struct ParsingOptions: FileParsingOptions { /// A decoder of bytes from a string. public var bytesDecoder: any ConfigBytesFromStringDecoder @@ -500,9 +506,9 @@ public struct YAMLSnapshot { public let providerName: String } -extension YAMLSnapshot: FileConfigSnapshotProtocol { +extension YAMLSnapshot: FileConfigSnapshot { public init( - data: Data, + data: RawSpan, providerName: String, parsingOptions: ParsingOptions ) throws From 8f97afed21d39712ebbe60ccb0bb7e3bf1d44ca3 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 3 Nov 2025 10:35:03 +0100 Subject: [PATCH 4/5] Update status --- Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md b/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md index 6d72933..d4634bc 100644 --- a/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md +++ b/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md @@ -6,7 +6,7 @@ Introduce format-agnostic providers to simplify implementing additional file for - Proposal: SCO-0001 - Author(s): [Honza Dvorsky](https://github.com/czechboy0) -- Status: **In Review** +- Status: **Ready for Implementation** - Issue: [apple/swift-configuration#61](https://github.com/apple/swift-configuration/issues/61) - Implementation: - [apple/swift-configuration#60](https://github.com/apple/swift-configuration/pull/60) From 3a4c2c1015104ca1fe6461a68168d1b9ef2ebf86 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 3 Nov 2025 16:24:46 +0100 Subject: [PATCH 5/5] Update proposal --- Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md b/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md index d4634bc..d834f45 100644 --- a/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md +++ b/Sources/Configuration/Documentation.docc/Proposals/SCO-0001.md @@ -6,7 +6,7 @@ Introduce format-agnostic providers to simplify implementing additional file for - Proposal: SCO-0001 - Author(s): [Honza Dvorsky](https://github.com/czechboy0) -- Status: **Ready for Implementation** +- Status: **Implemented (0.3.0)** - Issue: [apple/swift-configuration#61](https://github.com/apple/swift-configuration/issues/61) - Implementation: - [apple/swift-configuration#60](https://github.com/apple/swift-configuration/pull/60)