diff --git a/Sources/Configuration/Documentation.docc/Proposals/Proposals.md b/Sources/Configuration/Documentation.docc/Proposals/Proposals.md index c3c270a..dbf1d7a 100644 --- a/Sources/Configuration/Documentation.docc/Proposals/Proposals.md +++ b/Sources/Configuration/Documentation.docc/Proposals/Proposals.md @@ -37,3 +37,4 @@ If you have any questions, ask in an issue on GitHub. - - +- diff --git a/Sources/Configuration/Documentation.docc/Proposals/SCO-0002.md b/Sources/Configuration/Documentation.docc/Proposals/SCO-0002.md new file mode 100644 index 0000000..56387aa --- /dev/null +++ b/Sources/Configuration/Documentation.docc/Proposals/SCO-0002.md @@ -0,0 +1,119 @@ +# SCO-0002: Remove custom key decoders + +Remove the custom key decoder feature to fix a flaw and simplify the project + +## Overview + +- Proposal: SCO-0002 +- Author(s): [Honza Dvorsky](https://github.com/czechboy0) +- Status: **In Review** +- Issue: [apple/swift-configuration#70](https://github.com/apple/swift-configuration/issues/70) +- Implementation: + - [apple/swift-configuration#71](https://github.com/apple/swift-configuration/pull/71) + +### Introduction + +Remove the custom key decoder feature to fix a flaw and simplify the project. + +### Motivation + +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. + +Let's consider a key with the components `["http", "client", "read", "timeout"]`, read by an HTTPClient library, used by the root App executable. + +#### Default key decoder + +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: + +```swift +// App/main.swift +import HTTPClient + +let config = ConfigReader( + provider: EnvironmentVariablesProvider() + // ✅ Uses the default dot-based key decoder. +) + +// ✅ Uses scoping correctly +let client = Client(config: config.scoped(to: "http.client")) + +``` + +```swift +// HTTPClient/Config.swift + +extension Client { + public init(config: ConfigReader) { + // ✅ reads ["http", "client", "read", "timeout"] + self.timeout = config.int(forKey: "read.timeout", default: 30) + } +} +``` + +#### Caller-customized key decoder + +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. + +```swift +// App/main.swift +import HTTPClient + +let config = ConfigReader( + provider: EnvironmentVariablesProvider() + // ⚠️ Customizes the key decoder + keyDecoder: SeparatorKeyDecoder(separator: ":") +) + +// ✅ Uses scoping correctly with the custom delimiter +let client = Client(config: config.scoped(to: "http:client")) + +``` + +```swift +// HTTPClient/Config.swift + +extension Client { + public init(config: ConfigReader) { + // ❌ incorrectly reads ["http", "client", "read.timeout"] (last component is incorrect) + self.timeout = config.int(forKey: "read.timeout", default: 30) + } +} +``` + +### Proposed solution + +Remove the custom key decoder feature altogether and simplify the project. + +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. + +Not only does this change address a major flaw, it also removes over 10k lines of code. + +### Detailed design + +- Make `ConfigKey` and `AbsoluteConfigKey` conform to `ExpressibleByStringLiteral` and always use a dot as the delimiter when splitting components. +- Remove any public APIs related to key decoding, such as the `ConfigKeyDecoder` protocol and all concrete implementations. +- Remove all public overloads that specialized for a String-based key. + +For details, check out the [draft PR](https://github.com/apple/swift-configuration/pull/71). + +### API stability + +Most adopters do not need to change any code. + +Only if you used a method that takes both a string key and a context parameter, you now need to construct a `ConfigKey` manually: + +```diff +config.string( +- forKey: "server.timeout", +- context: ["upstream": "example.com] ++ forKey: ConfigKey("server.timeout", context: ["upstream": "example.com]) +) +``` + +### Future directions + +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. + +### Alternatives considered + +- 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.