Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ If you have any questions, ask in an issue on GitHub.

- <doc:SCO-NNNN>
- <doc:SCO-0001>
- <doc:SCO-0003>
136 changes: 136 additions & 0 deletions Sources/Configuration/Documentation.docc/Proposals/SCO-0003.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# SCO-0003: Allow missing files in file providers

Add an `allowMissing` parameter to file-based providers to handle missing configuration files gracefully.

## Overview

- Proposal: SCO-0003
- Author(s): [Honza Dvorsky](https://github.com/czechboy0)
- Status: **In Review**
- Issue: [apple/swift-configuration#66](https://github.com/apple/swift-configuration/issues/66)
- Implementation:
- [apple/swift-configuration#73](https://github.com/apple/swift-configuration/pull/73)
- Revisions:
- v1 - Nov 12, 2025 - Initial proposal.

### Introduction

Add an `allowMissing` Boolean parameter to file-based configuration providers to enable graceful handling of missing configuration files.

### Motivation

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:

- Applications fail to start when optional configuration files are missing, even when they could operate with sensible defaults specified in code.
- In containerized environments or cloud deployments, configuration files may be mounted dynamically or created by other services, making their availability timing unpredictable.
- Developers must create placeholder configuration files even when working on features that don't require external configuration.

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.

### Proposed solution

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.

Key behavioral changes:

```swift
// Current behavior - throws if config.json doesn't exist
let provider = try await FileProvider<JSONSnapshot>(filePath: "config.json")

// New behavior - succeeds even if config.json doesn't exist
let provider = try await FileProvider<JSONSnapshot>(
filePath: "config.json",
allowMissing: true
)
```

The `allowMissing` parameter defaults to `false`, preserving existing behavior for backward compatibility and keeping the strict variant as the default. When `true`:

- Missing files are treated as empty configuration (no key-value pairs).
- Reloading providers continue to work - providers detect when missing files are created, updated, and deleted.
- Malformed files still throw parsing errors regardless of the `allowMissing` setting.
- Directory provider treats missing directories as empty.

Example usage patterns:

```swift
// Multi-layered configuration with optional overrides
let config = ConfigReader(provider: [
EnvironmentVariablesProvider(),
try await FileProvider<JSONSnapshot>(
filePath: "optional-config.json",
allowMissing: true // Won't fail if missing
),
InMemoryProvider(data: ["fallback": "values"])
])

// Reloading provider that handles dynamic file creation, updates, and deletion
let dynamicConfig = try await ReloadingFileProvider<YAMLSnapshot>(
filePath: "/etc/dynamic/config.yaml",
allowMissing: true,
pollInterval: .seconds(5)
)
```

### Detailed design

#### API additions

All affected initializers will gain an `allowMissing` parameter:

```swift
// FileProvider.swift
public init(
snapshotType: Snapshot.Type = Snapshot.self,
parsingOptions: Snapshot.ParsingOptions = .default,
filePath: FilePath,
allowMissing: Bool = false // <<< new
) async throws

// ReloadingFileProvider
public convenience init(
snapshotType: Snapshot.Type = Snapshot.self,
parsingOptions: Snapshot.ParsingOptions = .default,
filePath: FilePath,
allowMissing: Bool = false, // <<< new
pollInterval: Duration = .seconds(15),
logger: Logger = Logger(label: "ReloadingFileProvider"),
metrics: any MetricsFactory = MetricsSystem.factory
) async throws

// DirectoryFilesProvider
public init(
directoryPath: FilePath,
allowMissing: Bool = false, // <<< new
secretsSpecifier: SecretsSpecifier<String, Data> = .all,
arraySeparator: Character = ",",
keyEncoder: some ConfigKeyEncoder = .directoryFiles
) async throws

// EnvironmentVariablesProvider
public init(
environmentFilePath: FilePath,
allowMissing: Bool = false, // <<< new
secretsSpecifier: SecretsSpecifier<String, String> = .none,
bytesDecoder: some ConfigBytesFromStringDecoder = .base64,
arraySeparator: Character = ","
) async throws
```

#### Configuration keys

When using `ConfigReader`-based initialization, a new key is supported:

- `allowMissing` (boolean, optional, default: false): Whether to allow missing files/directories.

### API stability

This change is purely additive, so no existing adopters are affected.

### Future directions

Nothing comes to mind at the moment.

### Alternatives considered

Status quo - we could have kept the file-reading behavior strict, which would require adopters to write conditional logic when setting up their `ConfigReader`.
Loading