diff --git a/Sources/Configuration/Documentation.docc/Documentation.md b/Sources/Configuration/Documentation.docc/Documentation.md index d0de187..93af6eb 100644 --- a/Sources/Configuration/Documentation.docc/Documentation.md +++ b/Sources/Configuration/Documentation.docc/Documentation.md @@ -456,8 +456,8 @@ Any package can implement a ``ConfigProvider``, making the ecosystem extensible ### Value conversion - ``ExpressibleByConfigString`` - ``ConfigBytesFromStringDecoder`` -- ``Base64BytesFromStringDecoder`` -- ``HexByteFromStringDecoder`` +- ``ConfigBytesFromBase64StringDecoder`` +- ``ConfigBytesFromHexStringDecoder`` ### Contributing - diff --git a/Sources/Configuration/Documentation.docc/Reference/Base64BytesFromStringDecoder.md b/Sources/Configuration/Documentation.docc/Reference/Base64BytesFromStringDecoder.md deleted file mode 100644 index e065bdc..0000000 --- a/Sources/Configuration/Documentation.docc/Reference/Base64BytesFromStringDecoder.md +++ /dev/null @@ -1,7 +0,0 @@ -# ``Configuration/Base64BytesFromStringDecoder`` - -## Topics - -### Creating bytes from a base-64 string - -- ``init()`` diff --git a/Sources/Configuration/Documentation.docc/Reference/ConfigBytesFromBase64StringDecoder.md b/Sources/Configuration/Documentation.docc/Reference/ConfigBytesFromBase64StringDecoder.md new file mode 100644 index 0000000..fdda42a --- /dev/null +++ b/Sources/Configuration/Documentation.docc/Reference/ConfigBytesFromBase64StringDecoder.md @@ -0,0 +1,7 @@ +# ``Configuration/ConfigBytesFromBase64StringDecoder`` + +## Topics + +### Creating bytes from a base64 string + +- ``init()`` diff --git a/Sources/Configuration/Documentation.docc/Reference/ConfigBytesFromHexStringDecoder.md b/Sources/Configuration/Documentation.docc/Reference/ConfigBytesFromHexStringDecoder.md new file mode 100644 index 0000000..e8712cd --- /dev/null +++ b/Sources/Configuration/Documentation.docc/Reference/ConfigBytesFromHexStringDecoder.md @@ -0,0 +1,7 @@ +# ``Configuration/ConfigBytesFromHexStringDecoder`` + +## Topics + +### Creating bytes from a hex string decoder + +- ``init()`` diff --git a/Sources/Configuration/Documentation.docc/Reference/HexByteFromStringDecoder.md b/Sources/Configuration/Documentation.docc/Reference/HexByteFromStringDecoder.md deleted file mode 100644 index ef51a4d..0000000 --- a/Sources/Configuration/Documentation.docc/Reference/HexByteFromStringDecoder.md +++ /dev/null @@ -1,7 +0,0 @@ -# ``Configuration/HexByteFromStringDecoder`` - -## Topics - -### Creating byte from hex string decoder - -- ``init()`` diff --git a/Sources/Configuration/ValueCoders/ConfigBytesFromStringDecoder.swift b/Sources/Configuration/ValueCoders/ConfigBytesFromStringDecoder.swift index 6c7c5a2..010ac64 100644 --- a/Sources/Configuration/ValueCoders/ConfigBytesFromStringDecoder.swift +++ b/Sources/Configuration/ValueCoders/ConfigBytesFromStringDecoder.swift @@ -22,7 +22,7 @@ import Foundation /// /// This protocol defines the interface for converting string-based configuration /// values into binary data. Different implementations can support various encoding -/// formats such as Base64, hexadecimal, or other custom encodings. +/// formats such as base64, hexadecimal, or other custom encodings. /// /// ## Usage /// @@ -32,7 +32,7 @@ import Foundation /// /// ```swift /// let decoder: ConfigBytesFromStringDecoder = .base64 -/// let bytes = decoder.decode("SGVsbG8gV29ybGQ=") // "Hello World" in Base64 +/// let bytes = decoder.decode("SGVsbG8gV29ybGQ=") // "Hello World" in base64 /// ``` public protocol ConfigBytesFromStringDecoder: Sendable { @@ -48,17 +48,17 @@ public protocol ConfigBytesFromStringDecoder: Sendable { func decode(_ value: String) -> [UInt8]? } -/// A decoder that converts Base64-encoded strings into byte arrays. +/// A decoder that converts base64-encoded strings into byte arrays. /// -/// This decoder interprets string configuration values as Base64-encoded data +/// This decoder interprets string configuration values as base64-encoded data /// and converts them to their binary representation. -public struct Base64BytesFromStringDecoder: Sendable { +public struct ConfigBytesFromBase64StringDecoder: Sendable { - /// Creates a new Base64 decoder. + /// Creates a new base64 decoder. public init() {} } -extension Base64BytesFromStringDecoder: ConfigBytesFromStringDecoder { +extension ConfigBytesFromBase64StringDecoder: ConfigBytesFromStringDecoder { // swift-format-ignore: AllPublicDeclarationsHaveDocumentation public func decode(_ value: String) -> [UInt8]? { guard let data = Data(base64Encoded: value) else { @@ -68,9 +68,9 @@ extension Base64BytesFromStringDecoder: ConfigBytesFromStringDecoder { } } -extension ConfigBytesFromStringDecoder where Self == Base64BytesFromStringDecoder { +extension ConfigBytesFromStringDecoder where Self == ConfigBytesFromBase64StringDecoder { - /// A decoder that interprets string values as Base64-encoded data. + /// A decoder that interprets string values as base64-encoded data. public static var base64: Self { .init() } } @@ -85,13 +85,13 @@ extension ConfigBytesFromStringDecoder where Self == Base64BytesFromStringDecode /// The decoder expects strings with an even number of characters, where each /// pair of characters represents one byte. For example, "48656C6C6F" represents /// the bytes for "Hello". -public struct HexByteFromStringDecoder: Sendable { +public struct ConfigBytesFromHexStringDecoder: Sendable { /// Creates a new hexadecimal decoder. public init() {} } -extension HexByteFromStringDecoder: ConfigBytesFromStringDecoder { +extension ConfigBytesFromHexStringDecoder: ConfigBytesFromStringDecoder { // swift-format-ignore: AllPublicDeclarationsHaveDocumentation public func decode(_ value: String) -> [UInt8]? { if value.count % 2 != 0 { @@ -113,7 +113,7 @@ extension HexByteFromStringDecoder: ConfigBytesFromStringDecoder { } } -extension ConfigBytesFromStringDecoder where Self == HexByteFromStringDecoder { +extension ConfigBytesFromStringDecoder where Self == ConfigBytesFromHexStringDecoder { /// A decoder that interprets string values as hexadecimal-encoded data. public static var hex: Self { .init() } diff --git a/Tests/ConfigurationTests/ConfigBytesFromStringDecoderTests.swift b/Tests/ConfigurationTests/ConfigBytesFromStringDecoderTests.swift new file mode 100644 index 0000000..ef436dd --- /dev/null +++ b/Tests/ConfigurationTests/ConfigBytesFromStringDecoderTests.swift @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftConfiguration open source project +// +// Copyright (c) 2025 Apple Inc. and the SwiftConfiguration project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftConfiguration project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Testing +import Foundation +@testable import Configuration + +struct ConfigBytesFromStringDecoderTests { + + @available(Configuration 1.0, *) + @Test func base64DecoderValidStrings() { + let decoder = ConfigBytesFromBase64StringDecoder() + + // Test "Hello World" in base64 + let helloWorldB64 = "SGVsbG8gV29ybGQ=" + let expectedHelloWorld: [UInt8] = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100] + #expect(decoder.decode(helloWorldB64) == expectedHelloWorld) + + // Test empty string + #expect(decoder.decode("") == []) + } + + @available(Configuration 1.0, *) + @Test func base64StaticConvenienceMethod() { + let decoder: any ConfigBytesFromStringDecoder = .base64 + // "Hello" in base64 + let testString = "SGVsbG8=" + let expected: [UInt8] = [72, 101, 108, 108, 111] + #expect(decoder.decode(testString) == expected) + } + + @available(Configuration 1.0, *) + @Test func hexDecoderValidStrings() { + let decoder = ConfigBytesFromHexStringDecoder() + + // Test "Hello" in hex (uppercase) + let helloHexUpper = "48656C6C6F" + let expectedHello: [UInt8] = [72, 101, 108, 108, 111] + #expect(decoder.decode(helloHexUpper) == expectedHello) + + // Test "Hello" in hex (lowercase) + let helloHexLower = "48656c6c6f" + #expect(decoder.decode(helloHexLower) == expectedHello) + + // Test mixed case + let helloHexMixed = "48656C6c6F" + #expect(decoder.decode(helloHexMixed) == expectedHello) + + // Test empty string + #expect(decoder.decode("") == []) + + // Test single byte values + #expect(decoder.decode("00") == [0]) + #expect(decoder.decode("FF") == [255]) + #expect(decoder.decode("ff") == [255]) + } + + @available(Configuration 1.0, *) + @Test func hexStaticConvenienceMethod() { + let decoder: any ConfigBytesFromStringDecoder = .hex + // "Hello" in hex + let testString = "48656C6C6F" + let expected: [UInt8] = [72, 101, 108, 108, 111] + #expect(decoder.decode(testString) == expected) + } +}