Skip to content

Commit 0cca7b8

Browse files
Adobelsczechboy0
andauthored
Add support for one zero and yes no boolean strings in environement variables provider (#120)
### Motivation Add support for 1, 0, and YES, NO boolean strings in EnvironmentVariablesProvider. Support for YES and NO boolean strings was not discussed in the original issue. From personal experience, I found it interesting to include it. If rejected, I will remove it. I tried to remain consitent with the existing decoders and the way they are used by the provider. Closes #110 ### Modifications The EnvironmentVariablesProvider's bool decoder is configurable and can be initialized with support for true/false, 1/0, yes/no, or any combination of them. ### Result Usage of EnvironmentVariablesProvider has not changed, but by default all pairs of boolean strings are accepted when retrieving values as Bool or BoolArray. Client code can configure the provider to use a bool decoder with support only for true/false strings. If it is more important to keep the behavior as it was, meaning default support for true/false only in the provider, then I will modify the default set for the EnvironmentVariablesProvider BoolDecoder. ### Test Plan The decoder is tested with dedicated tests, and additional tests for receiving a value from a provider were added to test the integration of BoolDecoder with EnvironmentVariablesProvider. Code is commented, and tests are green locally. --------- Co-authored-by: Honza Dvorsky <honza@apple.com>
1 parent 08206f5 commit 0cca7b8

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

Sources/Configuration/Providers/EnvironmentVariables/EnvironmentVariablesProvider.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,16 @@ public struct EnvironmentVariablesProvider: Sendable {
142142

143143
/// A decoder of arrays from a string.
144144
var arrayDecoder: EnvironmentValueArrayDecoder
145+
146+
/// A decoder of bool values from a string.
147+
static func decodeBool(from string: String) -> Bool? {
148+
let stringLowercased = string.lowercased()
149+
return switch stringLowercased {
150+
case "yes", "1": true
151+
case "no", "0": false
152+
default: Bool(stringLowercased)
153+
}
154+
}
145155
}
146156

147157
/// The underlying snapshot of the provider.
@@ -393,7 +403,7 @@ extension EnvironmentVariablesProvider.Snapshot {
393403
}
394404
content = .double(doubleValue)
395405
case .bool:
396-
guard let boolValue = Bool(stringValue) else {
406+
guard let boolValue = Self.decodeBool(from: stringValue) else {
397407
try throwMismatch()
398408
}
399409
content = .bool(boolValue)
@@ -426,7 +436,7 @@ extension EnvironmentVariablesProvider.Snapshot {
426436
case .boolArray:
427437
let arrayValue = arrayDecoder.decode(stringValue)
428438
let boolArray = try arrayValue.map { stringValue in
429-
guard let boolValue = Bool(stringValue) else {
439+
guard let boolValue = Self.decodeBool(from: stringValue) else {
430440
try throwMismatch()
431441
}
432442
return boolValue

Tests/ConfigurationTests/EnvironmentVariablesProviderTests.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,64 @@ struct EnvironmentVariablesProviderTests {
6868
#expect(provider.debugDescription == expectedDebugDescription)
6969
}
7070

71+
@available(Configuration 1.0, *)
72+
@Test func decodeBoolFromString() throws {
73+
let sut = EnvironmentVariablesProvider.Snapshot.decodeBool
74+
let cases: [(expected: Bool?, input: [String])] = [
75+
(true, ["1"]),
76+
(false, ["0"]),
77+
(true, ["Yes", "yes", "YES", "yES"]),
78+
(false, ["No", "no", "NO", "nO"]),
79+
(true, ["true", "TRUE", "trUe"]),
80+
(false, ["false", "FALSE", "faLse"]),
81+
(nil, ["", " ", "_true_", "_false_", "_yes_", "_no_", "_1_", "_0_", "11", "00"]),
82+
]
83+
for (expected, inputs) in cases {
84+
for input in inputs {
85+
#expect(sut(input) == expected, "input: \(input)")
86+
}
87+
}
88+
}
89+
90+
@available(Configuration 1.0, *)
91+
@Test func valueForKeyOfBoolAndBoolArrayTypes() throws {
92+
let sut = EnvironmentVariablesProvider(
93+
environmentVariables: [
94+
"BOOL_TRUE": "true",
95+
"BOOL_FALSE": "false",
96+
"BOOL_1": "1",
97+
"BOOL_0": "0",
98+
"BOOL_YES": "YES",
99+
"BOOL_NO": "NO",
100+
"BOOL_THROWS_ERROR_EMPTY": "",
101+
"BOOL_THROWS_ERROR_NOT_BOOL_STRING": "2",
102+
"BOOLY_ARRAY_TRUE": "true,1,,YES",
103+
"BOOLY_ARRAY_FALSE": "false,0,NO",
104+
"BOOLY_ARRAY_THROWS_1": "true,1,YESS",
105+
"BOOLY_ARRAY_THROWS_2": "false,00,no",
106+
"BOOLY_ARRAY_THROWS_3": "false, ,no",
107+
])
108+
#expect(try sut.value(forKey: "BOOL_TRUE", type: .bool).value == true)
109+
#expect(try sut.value(forKey: "BOOL_FALSE", type: .bool).value == false)
110+
#expect(try sut.value(forKey: "BOOL_1", type: .bool).value == true)
111+
#expect(try sut.value(forKey: "BOOL_0", type: .bool).value == false)
112+
#expect(try sut.value(forKey: "BOOL_YES", type: .bool).value == true)
113+
#expect(try sut.value(forKey: "BOOL_NO", type: .bool).value == false)
114+
#expect(throws: ConfigError.self) { try sut.value(forKey: "BOOL_THROWS_ERROR_EMPTY", type: .bool) }
115+
#expect(throws: ConfigError.self) { try sut.value(forKey: "BOOL_THROWS_ERROR_NOT_BOOL_STRING", type: .bool) }
116+
#expect(
117+
try sut.value(forKey: "BOOLY_ARRAY_TRUE", type: .boolArray).value
118+
== .init([true, true, true], isSecret: false)
119+
)
120+
#expect(
121+
try sut.value(forKey: "BOOLY_ARRAY_FALSE", type: .boolArray).value
122+
== .init([false, false, false], isSecret: false)
123+
)
124+
#expect(throws: ConfigError.self) { try sut.value(forKey: "BOOLY_ARRAY_THROWS_1", type: .boolArray) }
125+
#expect(throws: ConfigError.self) { try sut.value(forKey: "BOOLY_ARRAY_THROWS_2", type: .boolArray) }
126+
#expect(throws: ConfigError.self) { try sut.value(forKey: "BOOLY_ARRAY_THROWS_3", type: .boolArray) }
127+
}
128+
71129
@available(Configuration 1.0, *)
72130
@Test func compat() async throws {
73131
try await ProviderCompatTest(provider: provider).runTest()

0 commit comments

Comments
 (0)