Skip to content

Commit cba8052

Browse files
committed
[SCO-0002] Remove custom key decoders
1 parent 5a82370 commit cba8052

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ If you have any questions, ask in an issue on GitHub.
3737

3838
- <doc:SCO-NNNN>
3939
- <doc:SCO-0001>
40+
- <doc:SCO-0002>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# SCO-0002: Remove custom key decoders
2+
3+
Remove the custom key decoder feature to fix a flaw and simplify the project
4+
5+
## Overview
6+
7+
- Proposal: SCO-0002
8+
- Author(s): [Honza Dvorsky](https://github.com/czechboy0)
9+
- Status: **In Review**
10+
- Issue: [apple/swift-configuration#70](https://github.com/apple/swift-configuration/issues/70)
11+
- Implementation:
12+
- [apple/swift-configuration#71](https://github.com/apple/swift-configuration/pull/71)
13+
14+
### Introduction
15+
16+
Remove the custom key decoder feature to fix a flaw and simplify the project.
17+
18+
### Motivation
19+
20+
The custom key decoder [feature](https://swiftpackageindex.com/apple/swift-configuration/0.2.0/documentation/configuration#Custom-key-syntax) allowed using custom syntax in multi-component keys.
21+
22+
Let's consider a key with the components `["http", "client", "read", "timeout"]`, read by an HTTPClient library, used by the root App executable.
23+
24+
#### Default key decoder
25+
26+
When both targets use and assume the default key decoder is used, all is well, as both rely on a single period as the delimiter in string-based keys:
27+
28+
```swift
29+
// App/main.swift
30+
import HTTPClient
31+
32+
let config = ConfigReader(
33+
provider: EnvironmentVariablesProvider()
34+
// ✅ Uses the default dot-based key decoder.
35+
)
36+
37+
// ✅ Uses scoping correctly
38+
let client = Client(config: config.scoped(to: "http.client"))
39+
40+
```
41+
42+
```swift
43+
// HTTPClient/Config.swift
44+
45+
extension Client {
46+
public init(config: ConfigReader) {
47+
// ✅ reads ["http", "client", "read", "timeout"]
48+
self.timeout = config.int(forKey: "read.timeout", default: 30)
49+
}
50+
}
51+
```
52+
53+
#### Called-customized key decoder
54+
55+
However, if the App target customizes the key decoder to use a colon as the delimiter, it breaks the library that receives the config reader and reads an incorrect key.
56+
57+
```swift
58+
// App/main.swift
59+
import HTTPClient
60+
61+
let config = ConfigReader(
62+
provider: EnvironmentVariablesProvider()
63+
// ⚠️ Customizes the key decoder
64+
keyDecoder: SeparatorKeyDecoder(separator: ":")
65+
)
66+
67+
// ✅ Uses scoping correctly with the custom delimiter
68+
let client = Client(config: config.scoped(to: "http:client"))
69+
70+
```
71+
72+
```swift
73+
// HTTPClient/Config.swift
74+
75+
extension Client {
76+
public init(config: ConfigReader) {
77+
// ❌ incorrectly reads ["http", "client", "read.timeout"] (last component is incorrect)
78+
self.timeout = config.int(forKey: "read.timeout", default: 30)
79+
}
80+
}
81+
```
82+
83+
### Proposed solution
84+
85+
Remove the custom key decoder feature altogether and simplify the project.
86+
87+
This is enabled by making `ConfigKey` and `AbsoluteConfigKey` conform to `ExpressibleByStringLiteral`, which is only possible now because we can hardcode the string splitting to use the dot delimiter. This allows us to remove the explicit method overloads that took `String` for the key parameter, and we only take `ConfigKey` now. However, since `ConfigKey` is `ExpressibleByStringLiteral` now, the user experience is mostly unchanged.
88+
89+
Not only does this change address a major flaw, it also removes over 10k lines of code.
90+
91+
### Detailed design
92+
93+
- Make `ConfigKey` and `AbsoluteConfigKey` conform to `ExpressibleByStringLiteral` and always use a dot as the delimiter when splitting components.
94+
- Remove any public APIs related to key decoding, such as the `ConfigKeyDecoder` protocol and all concrete implementations.
95+
- Remove all public overloads that specialized for a String-based key.
96+
97+
For details, check out the [draft PR](https://github.com/apple/swift-configuration/pull/71).
98+
99+
### API stability
100+
101+
Most adopters do not need to change any code.
102+
103+
Only if you used a method that takes both a string key and a context parameter, you now need to construct a `ConfigKey` manually:
104+
105+
```diff
106+
config.string(
107+
- forKey: "server.timeout",
108+
- context: ["upstream": "example.com]
109+
+ forKey: ConfigKey("server.timeout", context: ["upstream": "example.com])
110+
)
111+
```
112+
113+
### Future directions
114+
115+
The feature might be added back in the future, if there's enough value in it and we can find a design that avoids the flaw.
116+
117+
### Alternatives considered
118+
119+
- Keep as API and document that config readers with customized key decoders must not be passed to other libraries: seemed like a suboptimal solution and would weaken the ability of the ecosystem to seamlessly integrate different libraries.

0 commit comments

Comments
 (0)