Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@ public struct EnvironmentVariablesProvider: Sendable {

/// A decoder of arrays from a string.
var arrayDecoder: EnvironmentValueArrayDecoder

/// A decoder of bool values from a string
static func decodeBool(from string: String) -> Bool? {
let stringLowercased = string.lowercased()
return if ["true", "false"].contains(stringLowercased) {
stringLowercased == "true"
} else if ["yes", "no"].contains(stringLowercased) {
stringLowercased == "yes"
} else if ["1", "0"].contains(stringLowercased) {
stringLowercased == "1"
} else {
nil
}
Comment on lines +148 to +157
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let stringLowercased = string.lowercased()
return if ["true", "false"].contains(stringLowercased) {
stringLowercased == "true"
} else if ["yes", "no"].contains(stringLowercased) {
stringLowercased == "yes"
} else if ["1", "0"].contains(stringLowercased) {
stringLowercased == "1"
} else {
nil
}
switch string.lowercased() {
case "true", "yes", "1": true
case "false", "no", "0": false
default: nil
}

Just a bit shorter :)

}
}

/// The underlying snapshot of the provider.
Expand Down Expand Up @@ -393,7 +407,7 @@ extension EnvironmentVariablesProvider.Snapshot {
}
content = .double(doubleValue)
case .bool:
guard let boolValue = Bool(stringValue) else {
guard let boolValue = Self.decodeBool(from: stringValue) else {
try throwMismatch()
}
content = .bool(boolValue)
Expand Down Expand Up @@ -426,7 +440,7 @@ extension EnvironmentVariablesProvider.Snapshot {
case .boolArray:
let arrayValue = arrayDecoder.decode(stringValue)
let boolArray = try arrayValue.map { stringValue in
guard let boolValue = Bool(stringValue) else {
guard let boolValue = Self.decodeBool(from: stringValue) else {
try throwMismatch()
}
return boolValue
Expand Down
51 changes: 51 additions & 0 deletions Tests/ConfigurationTests/EnvironmentVariablesProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,57 @@ struct EnvironmentVariablesProviderTests {
#expect(provider.debugDescription == expectedDebugDescription)
}

@Test()
@available(Configuration 1.0, *)
func decodeBoolFromString() throws {
Comment on lines +71 to +73
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@Test()
@available(Configuration 1.0, *)
func decodeBoolFromString() throws {
@available(Configuration 1.0, *)
@Test func decodeBoolFromString() throws {

let sut = EnvironmentVariablesProvider.Snapshot.decodeBool
let cases: [(expected: Bool?, input: [String])] = [
(true, ["1"]),
(false, ["0"]),
(true, ["Yes", "yes", "YES", "yES"]),
(false, ["No", "no", "NO", "nO"]),
(true, ["true", "TRUE", "trUe"]),
(false, ["false", "FALSE", "faLse"]),
(nil, ["", "_true_", "_false_", "_yes_", "_no_", "_1_", "_0_", "11", "00"])
]
for (expected, inputs) in cases {
for input in inputs {
#expect(sut(input) == expected, "input: \(input)")
}
}
}

@available(Configuration 1.0, *)
@Test func valueForKeyOfBoolAndBoolArrayTypes() throws {
let sut = EnvironmentVariablesProvider(
environmentVariables: [
"BOOL_TRUE": "true",
"BOOL_FALSE": "false",
"BOOL_1": "1",
"BOOL_0": "0",
"BOOL_YES": "YES",
"BOOL_NO": "NO",
"BOOL_THROWS_ERROR_EMPTY": "",
"BOOL_THROWS_ERROR_NOT_BOOL_STRING": "2",
"BOOLY_ARRAY_TRUE": "true,1,,YES",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I'd expect this to throw an error due to the 3rd element being an empty string - can you take a look why it doesn't? Probably a pre-existing bug

"BOOLY_ARRAY_FALSE": "false,0,NO",
"BOOLY_ARRAY_THROWS_1": "true,1,YESS",
"BOOLY_ARRAY_THROWS_2": "false,00,no",
])
#expect(try sut.value(forKey: "BOOL_TRUE", type: .bool).value == true)
#expect(try sut.value(forKey: "BOOL_FALSE", type: .bool).value == false)
#expect(try sut.value(forKey: "BOOL_1", type: .bool).value == true)
#expect(try sut.value(forKey: "BOOL_0", type: .bool).value == false)
#expect(try sut.value(forKey: "BOOL_YES", type: .bool).value == true)
#expect(try sut.value(forKey: "BOOL_NO", type: .bool).value == false)
#expect(throws: ConfigError.self) { try sut.value(forKey: "BOOL_THROWS_ERROR_EMPTY", type: .bool) }
#expect(throws: ConfigError.self) { try sut.value(forKey: "BOOL_THROWS_ERROR_NOT_BOOL_STRING", type: .bool) }
#expect(try sut.value(forKey: "BOOLY_ARRAY_TRUE", type: .boolArray).value == .init([true, true, true], isSecret: false))
#expect(try sut.value(forKey: "BOOLY_ARRAY_FALSE", type: .boolArray).value == .init([false, false, false], isSecret: false))
#expect(throws: ConfigError.self) { try sut.value(forKey: "BOOLY_ARRAY_THROWS_1", type: .boolArray) }
#expect(throws: ConfigError.self) { try sut.value(forKey: "BOOLY_ARRAY_THROWS_2", type: .boolArray) }
}

@available(Configuration 1.0, *)
@Test func compat() async throws {
try await ProviderCompatTest(provider: provider).runTest()
Expand Down