Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/Configuration/Documentation.docc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,8 @@ Any package can implement a ``ConfigProvider``, making the ecosystem extensible
### Value conversion
- ``ExpressibleByConfigString``
- ``ConfigBytesFromStringDecoder``
- ``Base64BytesFromStringDecoder``
- ``HexByteFromStringDecoder``
- ``ConfigBytesFromBase64StringDecoder``
- ``ConfigBytesFromHexStringDecoder``

### Contributing
- <doc:Development>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ``Configuration/ConfigBytesFromBase64StringDecoder``

## Topics

### Creating bytes from a base64 string

- ``init()``
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ``Configuration/ConfigBytesFromHexStringDecoder``

## Topics

### Creating bytes from a hex string decoder

- ``init()``

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand All @@ -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 {

Expand All @@ -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 {
Expand All @@ -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() }
}

Expand All @@ -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 {
Expand All @@ -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() }
Expand Down
77 changes: 77 additions & 0 deletions Tests/ConfigurationTests/ConfigBytesFromStringDecoderTests.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading