Skip to content

Commit 7819bf9

Browse files
authored
[SCO-0003] Allow missing files in file providers (#75)
### Motivation Fixes #66. Read the proposal [here](https://github.com/czechboy0/swift-configuration/blob/hd-sco-0003/Sources/Configuration/Documentation.docc/Proposals/SCO-0003.md). ### Modifications Adds the proposal. ### Result N/A ### Test Plan N/A
1 parent 44f8639 commit 7819bf9

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

Sources/Configuration/Documentation.docc/Proposals/Proposals.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ If you have any questions, ask in an issue on GitHub.
3838
- <doc:SCO-NNNN>
3939
- <doc:SCO-0001>
4040
- <doc:SCO-0002>
41+
- <doc:SCO-0003>
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# SCO-0003: Allow missing files in file providers
2+
3+
Add an `allowMissing` parameter to file-based providers to handle missing configuration files gracefully.
4+
5+
## Overview
6+
7+
- Proposal: SCO-0003
8+
- Author(s): [Honza Dvorsky](https://github.com/czechboy0)
9+
- Status: **Implemented (1.0.0-alpha.1)**
10+
- Issue: [apple/swift-configuration#66](https://github.com/apple/swift-configuration/issues/66)
11+
- Implementation:
12+
- [apple/swift-configuration#73](https://github.com/apple/swift-configuration/pull/73)
13+
- Revisions:
14+
- v1 - Nov 12, 2025 - Initial proposal.
15+
16+
### Introduction
17+
18+
Add an `allowMissing` Boolean parameter to file-based configuration providers to enable graceful handling of missing configuration files.
19+
20+
### Motivation
21+
22+
Applications often need to handle optional configuration files that may not exist at startup or during runtime. Currently, all file-based providers (`FileProvider`, `ReloadingFileProvider`, `DirectoryFilesProvider`, and `EnvironmentVariablesProvider` when initialized from an `.env` file) throw errors when the specified configuration file is missing, which creates several challenges:
23+
24+
- Applications fail to start when optional configuration files are missing, even when they could operate with sensible defaults specified in code.
25+
- In containerized environments or cloud deployments, configuration files may be mounted dynamically or created by other services, making their availability timing unpredictable.
26+
- Developers must create placeholder configuration files even when working on features that don't require external configuration.
27+
28+
Currently, adopters must implement workarounds such as manually checking for a file's presence before creating a file-based provider, which requires writing needless boilerplate code.
29+
30+
### Proposed solution
31+
32+
We propose adding an `allowMissing` parameter to the initializers of `FileProvider`, `ReloadingFileProvider`, `DirectoryFilesProvider`, and `EnvironmentVariablesProvider`. When set to `true`, missing files are treated as empty configuration sources instead of causing initialization failures.
33+
34+
Key behavioral changes:
35+
36+
```swift
37+
// Current behavior - throws if config.json doesn't exist
38+
let provider = try await FileProvider<JSONSnapshot>(filePath: "config.json")
39+
40+
// New behavior - succeeds even if config.json doesn't exist
41+
let provider = try await FileProvider<JSONSnapshot>(
42+
filePath: "config.json",
43+
allowMissing: true
44+
)
45+
```
46+
47+
The `allowMissing` parameter defaults to `false`, preserving existing behavior for backward compatibility and keeping the strict variant as the default. When `true`:
48+
49+
- Missing files are treated as empty configuration (no key-value pairs).
50+
- Reloading providers continue to work - providers detect when missing files are created, updated, and deleted.
51+
- Malformed files still throw parsing errors regardless of the `allowMissing` setting.
52+
- Directory provider treats missing directories as empty.
53+
54+
Example usage patterns:
55+
56+
```swift
57+
// Multi-layered configuration with optional overrides
58+
let config = ConfigReader(provider: [
59+
EnvironmentVariablesProvider(),
60+
try await FileProvider<JSONSnapshot>(
61+
filePath: "optional-config.json",
62+
allowMissing: true // Won't fail if missing
63+
),
64+
InMemoryProvider(data: ["fallback": "values"])
65+
])
66+
67+
// Reloading provider that handles dynamic file creation, updates, and deletion
68+
let dynamicConfig = try await ReloadingFileProvider<YAMLSnapshot>(
69+
filePath: "/etc/dynamic/config.yaml",
70+
allowMissing: true,
71+
pollInterval: .seconds(5)
72+
)
73+
```
74+
75+
### Detailed design
76+
77+
#### API additions
78+
79+
All affected initializers will gain an `allowMissing` parameter:
80+
81+
```swift
82+
// FileProvider.swift
83+
public init(
84+
snapshotType: Snapshot.Type = Snapshot.self,
85+
parsingOptions: Snapshot.ParsingOptions = .default,
86+
filePath: FilePath,
87+
allowMissing: Bool = false // <<< new
88+
) async throws
89+
90+
// ReloadingFileProvider
91+
public convenience init(
92+
snapshotType: Snapshot.Type = Snapshot.self,
93+
parsingOptions: Snapshot.ParsingOptions = .default,
94+
filePath: FilePath,
95+
allowMissing: Bool = false, // <<< new
96+
pollInterval: Duration = .seconds(15),
97+
logger: Logger = Logger(label: "ReloadingFileProvider"),
98+
metrics: any MetricsFactory = MetricsSystem.factory
99+
) async throws
100+
101+
// DirectoryFilesProvider
102+
public init(
103+
directoryPath: FilePath,
104+
allowMissing: Bool = false, // <<< new
105+
secretsSpecifier: SecretsSpecifier<String, Data> = .all,
106+
arraySeparator: Character = ",",
107+
keyEncoder: some ConfigKeyEncoder = .directoryFiles
108+
) async throws
109+
110+
// EnvironmentVariablesProvider
111+
public init(
112+
environmentFilePath: FilePath,
113+
allowMissing: Bool = false, // <<< new
114+
secretsSpecifier: SecretsSpecifier<String, String> = .none,
115+
bytesDecoder: some ConfigBytesFromStringDecoder = .base64,
116+
arraySeparator: Character = ","
117+
) async throws
118+
```
119+
120+
#### Configuration keys
121+
122+
When using `ConfigReader`-based initialization, a new key is supported:
123+
124+
- `allowMissing` (boolean, optional, default: false): Whether to allow missing files/directories.
125+
126+
### API stability
127+
128+
This change is purely additive, so no existing adopters are affected.
129+
130+
### Future directions
131+
132+
Nothing comes to mind at the moment.
133+
134+
### Alternatives considered
135+
136+
Status quo - we could have kept the file-reading behavior strict, which would require adopters to write conditional logic when setting up their `ConfigReader`.

0 commit comments

Comments
 (0)