diff --git a/Sources/Configuration/Providers/EnvironmentVariables/EnvironmentVariablesProvider.swift b/Sources/Configuration/Providers/EnvironmentVariables/EnvironmentVariablesProvider.swift index 1608823..829b98e 100644 --- a/Sources/Configuration/Providers/EnvironmentVariables/EnvironmentVariablesProvider.swift +++ b/Sources/Configuration/Providers/EnvironmentVariables/EnvironmentVariablesProvider.swift @@ -142,6 +142,15 @@ 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? { + switch string.lowercased() { + case "true", "yes", "1": true + case "false", "no", "0": false + default: nil + } + } } /// The underlying snapshot of the provider. @@ -393,7 +402,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) @@ -426,7 +435,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 diff --git a/Tests/ConfigurationTests/EnvironmentVariablesProviderTests.swift b/Tests/ConfigurationTests/EnvironmentVariablesProviderTests.swift index ab72485..fb15ff8 100644 --- a/Tests/ConfigurationTests/EnvironmentVariablesProviderTests.swift +++ b/Tests/ConfigurationTests/EnvironmentVariablesProviderTests.swift @@ -68,6 +68,56 @@ struct EnvironmentVariablesProviderTests { #expect(provider.debugDescription == expectedDebugDescription) } + @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", + "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()