Skip to content

Commit 183d866

Browse files
authored
Generic file providers (Implementation of SCO-0001) (#60)
### Motivation While JSON and YAML support comes built-in in Swift Configuration, it should be easier to implement additional formats in external extension packages. Right now it [required copying](#37 (comment)) a lot of boilerplate code (thank you @finnvoor for pointing that out). This PR is ready to land, the API was accepted in https://forums.swift.org/t/proposal-sco-0001-generic-file-providers/82824/9 ~~Note that this PR isn't meant to land just yet - I'll first write a [proposal](https://swiftpackageindex.com/apple/swift-configuration/0.2.0/documentation/configuration/proposals) to solicit community feedback before moving forward.~~ However, you're encouraged to test it out by depending on my fork's branch with this feature, to ensure it works well - if you're considering extending Swift Configuration with a new format in your own package. ```swift .package( url: "https://github.com/czechboy0/swift-configuration", branch: "hd-generic-file-providers", traits: [.defaults, "ReloadingSupport"] ) ``` This PR also bumps the minimum Swift version to 6.2, since that's required by RawSpan. ### Modifications Refactors JSON and YAML providers to factor out reusable `FileProvider` and `ReloadingFileProvider` types that are generic over their snapshot type. This reduces the amount of code an adopter has to write to add a new format only to the parsing logic, but they get everything else related to file reading and reloading for free. ### Result An API that allows adding more file formats much more easily. ### Test Plan Adapted tests.
1 parent 9d5e407 commit 183d866

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1216
-1493
lines changed

.github/workflows/main.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ jobs:
1717
linux_env_vars: '{"ENABLE_ALL_TRAITS":"1"}'
1818
linux_5_10_enabled: false
1919
linux_6_0_enabled: false
20-
linux_6_1_arguments_override: "--explicit-target-dependency-import-check error"
20+
linux_6_1_enabled: false
21+
linux_6_2_arguments_override: "--explicit-target-dependency-import-check error"
22+
linux_nightly_6_1_enabled: false
23+
linux_nightly_next_enabled: false
2124
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error"
2225
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error"
2326
# Windows is disabled, blocked on Swift Service Lifecycle Windows support: https://github.com/swift-server/swift-service-lifecycle/issues/213
@@ -45,6 +48,8 @@ jobs:
4548
runner_pool: nightly
4649
build_scheme: swift-configuration-Package
4750
xcode_16_2_enabled: false
51+
xcode_16_3_enabled: false
52+
xcode_16_4_enabled: false
4853

4954
release-builds:
5055
name: Release builds
@@ -53,6 +58,8 @@ jobs:
5358
linux_env_vars: '{"ENABLE_ALL_TRAITS":"1"}'
5459
linux_5_10_enabled: false
5560
linux_6_0_enabled: false
61+
linux_6_1_enabled: false
62+
linux_nightly_next_enabled: false
5663
windows_6_0_enabled: false
5764
windows_6_1_enabled: false
5865
windows_nightly_next_enabled: false
@@ -75,6 +82,8 @@ jobs:
7582
MATRIX_LINUX_COMMAND: ./Scripts/run-linkage-test.sh
7683
MATRIX_LINUX_5_10_ENABLED: false
7784
MATRIX_LINUX_6_0_ENABLED: false
85+
MATRIX_LINUX_6_1_ENABLED: false
86+
MATRIX_LINUX_NIGHTLY_NEXT_ENABLED: false
7887
MATRIX_LINUX_NIGHTLY_MAIN_ENABLED: false
7988

8089
linkage-test:
@@ -102,6 +111,8 @@ jobs:
102111
MATRIX_LINUX_COMMAND: ./Scripts/test-examples.sh
103112
MATRIX_LINUX_5_10_ENABLED: false
104113
MATRIX_LINUX_6_0_ENABLED: false
114+
MATRIX_LINUX_6_1_ENABLED: false
115+
MATRIX_LINUX_NIGHTLY_NEXT_ENABLED: false
105116
MATRIX_LINUX_NIGHTLY_MAIN_ENABLED: false
106117

107118
example-packages:

.github/workflows/pull_request.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ jobs:
2424
linux_env_vars: '{"ENABLE_ALL_TRAITS":"1"}'
2525
linux_5_10_enabled: false
2626
linux_6_0_enabled: false
27-
linux_6_1_arguments_override: "--explicit-target-dependency-import-check error"
27+
linux_6_1_enabled: false
28+
linux_6_2_arguments_override: "--explicit-target-dependency-import-check error"
29+
linux_nightly_6_1_enabled: false
30+
linux_nightly_next_enabled: false
2831
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error"
2932
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error"
3033
# Windows is disabled, blocked on Swift Service Lifecycle Windows support: https://github.com/swift-server/swift-service-lifecycle/issues/213
@@ -52,6 +55,8 @@ jobs:
5255
runner_pool: general
5356
build_scheme: swift-configuration-Package
5457
xcode_16_2_enabled: false
58+
xcode_16_3_enabled: false
59+
xcode_16_4_enabled: false
5560

5661
release-builds:
5762
name: Release builds
@@ -60,6 +65,8 @@ jobs:
6065
linux_env_vars: '{"ENABLE_ALL_TRAITS":"1"}'
6166
linux_5_10_enabled: false
6267
linux_6_0_enabled: false
68+
linux_6_1_enabled: false
69+
linux_nightly_next_enabled: false
6370
windows_6_0_enabled: false
6471
windows_6_1_enabled: false
6572
windows_nightly_next_enabled: false
@@ -82,6 +89,8 @@ jobs:
8289
MATRIX_LINUX_COMMAND: ./Scripts/run-linkage-test.sh
8390
MATRIX_LINUX_5_10_ENABLED: false
8491
MATRIX_LINUX_6_0_ENABLED: false
92+
MATRIX_LINUX_6_1_ENABLED: false
93+
MATRIX_LINUX_NIGHTLY_NEXT_ENABLED: false
8594
MATRIX_LINUX_NIGHTLY_MAIN_ENABLED: false
8695

8796
linkage-test:
@@ -109,6 +118,8 @@ jobs:
109118
MATRIX_LINUX_COMMAND: ./Scripts/test-examples.sh
110119
MATRIX_LINUX_5_10_ENABLED: false
111120
MATRIX_LINUX_6_0_ENABLED: false
121+
MATRIX_LINUX_6_1_ENABLED: false
122+
MATRIX_LINUX_NIGHTLY_NEXT_ENABLED: false
112123
MATRIX_LINUX_NIGHTLY_MAIN_ENABLED: false
113124

114125
example-packages:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Package.resolved
88
*.pyc
99
.docc-build
1010
.vscode
11+
.devcontainer

.spi.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: 1
22
builder:
33
configs:
4-
- swift_version: '6.1'
4+
- swift_version: '6.2'
55
documentation_targets:
66
- Configuration
77
- ConfigurationTesting

Examples/hello-world-cli-example/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 6.1
1+
// swift-tools-version: 6.2
22

33
import PackageDescription
44

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 6.1
1+
// swift-tools-version: 6.2
22

33
import PackageDescription
44
#if canImport(FoundationEssentials)
@@ -29,7 +29,7 @@ var traits: Set<Trait> = [
2929
.trait(
3030
name: "ReloadingSupport",
3131
description:
32-
"Adds support for reloading built-in provider variants, such as ReloadingJSONProvider and ReloadingYAMLProvider (when their respective traits are enabled).",
32+
"Adds support for reloading built-in provider variants, such as ReloadingFileProvider.",
3333
enabledTraits: [
3434
"LoggingSupport"
3535
]

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ The example code below creates the two relevant providers, and resolves them in
4343
```swift
4444
let config = ConfigReader(providers: [
4545
EnvironmentVariablesProvider(),
46-
try await JSONProvider(filePath: "/etc/config.json")
46+
try await FileProvider<JSONSnapshot>(filePath: "/etc/config.json")
4747
])
4848
let httpTimeout = config.int(forKey: "http.timeout", default: 60)
4949
print(httpTimeout) // prints 15
@@ -100,11 +100,11 @@ To enable an additional trait on the package, update the package dependency:
100100
```
101101

102102
Available traits:
103-
- **`JSONSupport`** (default): Adds support for `JSONProvider`, a `ConfigProvider` for reading JSON files.
103+
- **`JSONSupport`** (default): Adds support for `FileProvider<JSONSnapshot>`, a `ConfigProvider` for reading JSON files.
104104
- **`LoggingSupport`** (opt-in): Adds support for `AccessLogger`, a way to emit access events into a `SwiftLog.Logger`.
105-
- **`ReloadingSupport`** (opt-in): Adds support for auto-reloading variants of file providers, such as `ReloadingJSONProvider` (when `JSONSupport` is enabled) and `ReloadingYAMLProvider` (when `YAMLSupport` is enabled).
105+
- **`ReloadingSupport`** (opt-in): Adds support for auto-reloading variants of file providers, such as `ReloadingFileProvider<JSONSnapshot>` (when `JSONSupport` is enabled) and `ReloadingFileProvider<YAMLSnapshot>` (when `YAMLSupport` is enabled).
106106
- **`CommandLineArgumentsSupport`** (opt-in): Adds support for `CommandLineArgumentsProvider` for parsing command line arguments.
107-
- **`YAMLSupport`** (opt-in): Adds support for `YAMLProvider`, a `ConfigProvider` for reading YAML files.
107+
- **`YAMLSupport`** (opt-in): Adds support for `FileProvider<YAMLSnapshot>`, a `ConfigProvider` for reading YAML files.
108108

109109
## Supported platforms and minimum versions
110110

@@ -120,8 +120,8 @@ The library includes comprehensive built-in provider support:
120120

121121
- Environment variables: [`EnvironmentVariablesProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/environmentvariablesprovider)
122122
- Command-line arguments: [`CommandLineArgumentsProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/commandlineargumentsprovider)
123-
- JSON file: [`JSONProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/jsonprovider) and [`ReloadingJSONProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/reloadingjsonprovider)
124-
- YAML file: [`YAMLProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/yamlprovider) and [`ReloadingYAMLProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/reloadingyamlprovider)
123+
- JSON file: [`FileProvider<JSONSnapshot>`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/fileprovider) and [`ReloadingFileProvider<JSONSnapshot>`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/reloadingfileprovider)
124+
- YAML file: [`FileProvider<YAMLSnapshot>`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/fileprovider) and [`ReloadingFileProvider<YAMLSnapshot>`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/reloadingfileprovider)
125125
- Directory of files: [`DirectoryFilesProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/directoryfilesprovider)
126126
- In-memory: [`InMemoryProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/inmemoryprovider) and [`MutableInMemoryProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/mutableinmemoryprovider)
127127
- Key transforming: [`KeyMappingProvider`](https://swiftpackageindex.com/apple/swift-configuration/documentation/configuration/keymappingprovider)

Sources/Configuration/ConfigReader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import Foundation
4444
/// // First, check environment variables
4545
/// EnvironmentVariablesProvider(),
4646
/// // Then, check a JSON config file
47-
/// try await JSONProvider(filePath: "/etc/config.json"),
47+
/// try await FileProvider<JSONSnapshot>(filePath: "/etc/config.json"),
4848
/// // Finally, fall back to in-memory defaults
4949
/// InMemoryProvider(values: [
5050
/// "http.timeout": 60,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftConfiguration open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftConfiguration project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftConfiguration project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#if JSONSupport
16+
/// A provider of configuration in JSON files.
17+
@available(Configuration 1.0, *)
18+
@available(*, deprecated, message: "Renamed to FileProvider<JSONSnapshot>")
19+
public typealias JSONProvider = FileProvider<JSONSnapshot>
20+
#endif
21+
22+
#if YAMLSupport
23+
/// A provider of configuration in YAML files.
24+
@available(Configuration 1.0, *)
25+
@available(*, deprecated, message: "Renamed to FileProvider<YAMLSnapshot>")
26+
public typealias YAMLProvider = FileProvider<YAMLSnapshot>
27+
#endif
28+
29+
#if ReloadingSupport
30+
31+
#if JSONSupport
32+
/// A reloading provider of configuration in JSON files.
33+
@available(Configuration 1.0, *)
34+
@available(*, deprecated, message: "Renamed to ReloadingFileProvider<JSONSnapshot>")
35+
public typealias ReloadingJSONProvider = ReloadingFileProvider<JSONSnapshot>
36+
#endif
37+
38+
#if YAMLSupport
39+
/// A reloading provider of configuration in JSON files.
40+
@available(Configuration 1.0, *)
41+
@available(*, deprecated, message: "Renamed to ReloadingFileProvider<YAMLSnapshot>")
42+
public typealias ReloadingYAMLProvider = ReloadingFileProvider<YAMLSnapshot>
43+
#endif
44+
45+
#endif

Sources/Configuration/Documentation.docc/Documentation.md

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ For example, to read the timeout configuration value for an HTTP client, check o
4444
}
4545
```
4646
```swift
47-
let provider = try await JSONProvider(
47+
let provider = try await FileProvider<JSONSnapshot>(
4848
filePath: "/etc/config.json"
4949
)
5050
let config = ConfigReader(provider: provider)
@@ -61,7 +61,7 @@ For example, to read the timeout configuration value for an HTTP client, check o
6161
}
6262
```
6363
```swift
64-
let provider = try await ReloadingJSONProvider(
64+
let provider = try await ReloadingFileProvider<JSONSnapshot>(
6565
filePath: "/etc/config.json"
6666
)
6767
// Omitted: Add `provider` to a ServiceGroup
@@ -76,7 +76,7 @@ For example, to read the timeout configuration value for an HTTP client, check o
7676
timeout: 30
7777
```
7878
```swift
79-
let provider = try await YAMLProvider(
79+
let provider = try await FileProvider<YAMLSnapshot>(
8080
filePath: "/etc/config.yaml"
8181
)
8282
let config = ConfigReader(provider: provider)
@@ -90,7 +90,7 @@ For example, to read the timeout configuration value for an HTTP client, check o
9090
timeout: 30
9191
```
9292
```swift
93-
let provider = try await ReloadingYAMLProvider(
93+
let provider = try await ReloadingFileProvider<YAMLSnapshot>(
9494
filePath: "/etc/config.yaml"
9595
)
9696
// Omitted: Add `provider` to a ServiceGroup
@@ -187,11 +187,11 @@ To enable an additional trait on the package, update the package dependency:
187187
```
188188

189189
Available traits:
190-
- **`JSONSupport`** (default): Adds support for ``JSONProvider``, a ``ConfigProvider`` for reading JSON files.
190+
- **`JSONSupport`** (default): Adds support for ``JSONSnapshot``, which enables using ``FileProvider`` and ``ReloadingFileProvider`` with JSON files.
191191
- **`LoggingSupport`** (opt-in): Adds support for ``AccessLogger``, a way to emit access events into a `SwiftLog.Logger`.
192-
- **`ReloadingSupport`** (opt-in): Adds support for auto-reloading variants of file providers, such as ``ReloadingJSONProvider`` (when `JSONSupport` is enabled) and ``ReloadingYAMLProvider`` (when `YAMLSupport` is enabled).
192+
- **`ReloadingSupport`** (opt-in): Adds support for ``ReloadingFileProvider``, which provides auto-reloading capability for file-based configuration.
193193
- **`CommandLineArgumentsSupport`** (opt-in): Adds support for ``CommandLineArgumentsProvider`` for parsing command line arguments.
194-
- **`YAMLSupport`** (opt-in): Adds support for ``YAMLProvider``, a ``ConfigProvider`` for reading YAML files.
194+
- **`YAMLSupport`** (opt-in): Adds support for ``YAMLSnapshot``, which enables using ``FileProvider`` and ``ReloadingFileProvider`` with YAML files.
195195

196196
### Supported platforms and minimum versions
197197

@@ -235,8 +235,8 @@ The library includes comprehensive built-in provider support:
235235

236236
- Environment variables: ``EnvironmentVariablesProvider``
237237
- Command-line arguments: ``CommandLineArgumentsProvider``
238-
- JSON file: ``JSONProvider`` and ``ReloadingJSONProvider``
239-
- YAML file: ``YAMLProvider`` and ``ReloadingYAMLProvider``
238+
- JSON file: ``FileProvider`` and ``ReloadingFileProvider`` with ``JSONSnapshot``
239+
- YAML file: ``FileProvider`` and ``ReloadingFileProvider`` with ``YAMLSnapshot``
240240
- Directory of files: ``DirectoryFilesProvider``
241241
- In-memory: ``InMemoryProvider`` and ``MutableInMemoryProvider``
242242
- Key transforming: ``KeyMappingProvider``
@@ -259,7 +259,7 @@ let config = ConfigReader(providers: [
259259
// Then, check command-line options.
260260
CommandLineArgumentsProvider(),
261261
// Then, check a JSON config file.
262-
try await JSONProvider(filePath: "/etc/config.json"),
262+
try await FileProvider<JSONSnapshot>(filePath: "/etc/config.json"),
263263
// Finally, fall back to in-memory defaults.
264264
InMemoryProvider(values: [
265265
"http.timeout": 60,
@@ -272,10 +272,10 @@ let timeout = config.int(forKey: "http.timeout", default: 15)
272272

273273
#### Hot reloading
274274

275-
Long-running services can periodically reload configuration with ``ReloadingJSONProvider`` and ``ReloadingYAMLProvider``:
275+
Long-running services can periodically reload configuration with ``ReloadingFileProvider``:
276276

277277
```swift
278-
let provider = try await ReloadingJSONProvider(filePath: "/etc/config.json")
278+
let provider = try await ReloadingFileProvider<JSONSnapshot>(filePath: "/etc/config.json")
279279
// Omitted: add provider to a ServiceGroup
280280
let config = ConfigReader(provider: provider)
281281

@@ -407,18 +407,17 @@ Any package can implement a ``ConfigProvider``, making the ecosystem extensible
407407
- ``ConfigReader``
408408
- ``ConfigProvider``
409409
- ``ConfigSnapshotReader``
410-
- ``ConfigSnapshotProtocol``
411410
- <doc:Choosing-access-patterns>
412411
- <doc:Choosing-reader-methods>
413412
- <doc:Handling-secrets-correctly>
414413

415414
### Built-in providers
416415
- ``EnvironmentVariablesProvider``
417416
- ``CommandLineArgumentsProvider``
418-
- ``JSONProvider``
419-
- ``YAMLProvider``
420-
- ``ReloadingJSONProvider``
421-
- ``ReloadingYAMLProvider``
417+
- ``FileProvider``
418+
- ``ReloadingFileProvider``
419+
- ``JSONSnapshot``
420+
- ``YAMLSnapshot``
422421
- <doc:Using-reloading-providers>
423422
- ``DirectoryFilesProvider``
424423
- <doc:Using-in-memory-providers>
@@ -427,6 +426,8 @@ Any package can implement a ``ConfigProvider``, making the ecosystem extensible
427426
- ``KeyMappingProvider``
428427

429428
### Creating a custom provider
429+
- ``ConfigSnapshotProtocol``
430+
- ``FileParsingOptions``
430431
- ``ConfigProvider``
431432
- ``ConfigContent``
432433
- ``ConfigValue``

0 commit comments

Comments
 (0)