Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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``
- ``BytesFromBase64StringDecoder``
- ``BytesFromHexStringDecoder``

### 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