From aa05b081b1d5649cc0f0c119c2d1191d778ff34e Mon Sep 17 00:00:00 2001 From: Finn Voorhees Date: Wed, 1 Oct 2025 13:05:48 +0100 Subject: [PATCH] Expose AbsoluteConfigKey prepending/appending --- .../hello-world-cli-example/Package.swift | 10 +- Sources/Configuration/ConfigKey.swift | 12 +- .../AbsoluteConfigKeyTests.swift | 113 ++++++++++++++++++ 3 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 Tests/ConfigurationTests/AbsoluteConfigKeyTests.swift diff --git a/Examples/hello-world-cli-example/Package.swift b/Examples/hello-world-cli-example/Package.swift index 8edc5b4..a70d582 100644 --- a/Examples/hello-world-cli-example/Package.swift +++ b/Examples/hello-world-cli-example/Package.swift @@ -8,14 +8,18 @@ let package = Package( .macOS(.v15) ], dependencies: [ - .package(url: "https://github.com/apple/swift-configuration", .upToNextMinor(from: "0.1.0"), traits: [.defaults, "CommandLineArgumentsSupport"]) + .package( + url: "https://github.com/apple/swift-configuration", + .upToNextMinor(from: "0.1.0"), + traits: [.defaults, "CommandLineArgumentsSupport"] + ) ], targets: [ .executableTarget( name: "CLI", dependencies: [ - .product(name: "Configuration", package: "swift-configuration"), + .product(name: "Configuration", package: "swift-configuration") ] - ), + ) ] ) diff --git a/Sources/Configuration/ConfigKey.swift b/Sources/Configuration/ConfigKey.swift index ca12780..a120410 100644 --- a/Sources/Configuration/ConfigKey.swift +++ b/Sources/Configuration/ConfigKey.swift @@ -182,13 +182,23 @@ extension AbsoluteConfigKey { /// Returns a new absolute configuration key by prepending the given relative key. /// - Parameter prefix: The relative configuration key to prepend to this key. /// - Returns: A new absolute configuration key with the prefix prepended. - internal func prepending(_ prefix: ConfigKey) -> AbsoluteConfigKey { + public func prepending(_ prefix: ConfigKey) -> AbsoluteConfigKey { var prefixedComponents = prefix.components prefixedComponents.append(contentsOf: self.components) var mergedContext = prefix.context mergedContext.merge(self.context) { $1 } return AbsoluteConfigKey(prefixedComponents, context: mergedContext) } + + /// Returns a new absolute configuration key by appending the given relative key. + /// - Parameter relative: The relative configuration key to append to this key. + /// - Returns: A new absolute configuration key with the relative key appended. + public func appending(_ relative: ConfigKey) -> AbsoluteConfigKey { + var appended = self + appended.components.append(contentsOf: relative.components) + appended.context.merge(relative.context) { $1 } + return appended + } } extension AbsoluteConfigKey: ExpressibleByArrayLiteral { diff --git a/Tests/ConfigurationTests/AbsoluteConfigKeyTests.swift b/Tests/ConfigurationTests/AbsoluteConfigKeyTests.swift new file mode 100644 index 0000000..0ade572 --- /dev/null +++ b/Tests/ConfigurationTests/AbsoluteConfigKeyTests.swift @@ -0,0 +1,113 @@ +//===----------------------------------------------------------------------===// +// +// 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 +@testable import Configuration + +struct AbsoluteConfigKeyTests { + @Test func prependingSimpleKey() { + let base = AbsoluteConfigKey(["bar", "baz"]) + let prefix = ConfigKey(["foo"]) + let result = base.prepending(prefix) + + #expect(result.components == ["foo", "bar", "baz"]) + #expect(result.context.isEmpty) + } + + @Test func prependingWithContext() { + let base = AbsoluteConfigKey(["bar"], context: ["env": .string("prod")]) + let prefix = ConfigKey(["foo"], context: ["region": .string("us-west")]) + let result = base.prepending(prefix) + + #expect(result.components == ["foo", "bar"]) + #expect(result.context["env"] == .string("prod")) + #expect(result.context["region"] == .string("us-west")) + } + + @Test func prependingWithConflictingContext() { + let base = AbsoluteConfigKey(["bar"], context: ["key": .string("base-value")]) + let prefix = ConfigKey(["foo"], context: ["key": .string("prefix-value")]) + let result = base.prepending(prefix) + + #expect(result.components == ["foo", "bar"]) + #expect(result.context["key"] == .string("base-value")) + } + + @Test func prependingEmptyKey() { + let base = AbsoluteConfigKey(["foo", "bar"]) + let prefix = ConfigKey([]) + let result = base.prepending(prefix) + + #expect(result.components == ["foo", "bar"]) + #expect(result.context.isEmpty) + } + + @Test func appendingSimpleKey() { + let base = AbsoluteConfigKey(["foo", "bar"]) + let suffix = ConfigKey(["baz"]) + let result = base.appending(suffix) + + #expect(result.components == ["foo", "bar", "baz"]) + #expect(result.context.isEmpty) + } + + @Test func appendingWithContext() { + let base = AbsoluteConfigKey(["foo"], context: ["env": .string("prod")]) + let suffix = ConfigKey(["bar"], context: ["region": .string("us-west")]) + let result = base.appending(suffix) + + #expect(result.components == ["foo", "bar"]) + #expect(result.context["env"] == .string("prod")) + #expect(result.context["region"] == .string("us-west")) + } + + @Test func appendingWithConflictingContext() { + let base = AbsoluteConfigKey(["foo"], context: ["key": .string("base-value")]) + let suffix = ConfigKey(["bar"], context: ["key": .string("suffix-value")]) + let result = base.appending(suffix) + + #expect(result.components == ["foo", "bar"]) + #expect(result.context["key"] == .string("suffix-value")) + } + + @Test func appendingEmptyKey() { + let base = AbsoluteConfigKey(["foo", "bar"]) + let suffix = ConfigKey([]) + let result = base.appending(suffix) + + #expect(result.components == ["foo", "bar"]) + #expect(result.context.isEmpty) + } + + @Test func appendingMultipleKeys() { + let base = AbsoluteConfigKey(["foo"]) + let result = base.appending(ConfigKey(["bar"])).appending(ConfigKey(["baz"])) + + #expect(result.components == ["foo", "bar", "baz"]) + } + + @Test func prependingMultipleKeys() { + let base = AbsoluteConfigKey(["baz"]) + let result = base.prepending(ConfigKey(["bar"])).prepending(ConfigKey(["foo"])) + + #expect(result.components == ["foo", "bar", "baz"]) + } + + @Test func prependingAndAppending() { + let base = AbsoluteConfigKey(["middle"]) + let result = base.prepending(ConfigKey(["start"])).appending(ConfigKey(["end"])) + + #expect(result.components == ["start", "middle", "end"]) + } +}