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
9 changes: 6 additions & 3 deletions Sources/Configuration/ConfigReader+internalHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,8 @@ extension ConfigReader {
) async throws -> Return {
let absoluteKey = keyPrefix.appending(key)
return try await provider.watchValue(forKey: absoluteKey, type: type) { updates in
let mappedUpdates = ConcreteAsyncSequence(updates)
let mappedUpdates =
updates
.map { updateTuple in
let (providerResults, value) = mergingIsSecret(
isSecret,
Expand Down Expand Up @@ -715,7 +716,8 @@ extension ConfigReader {
) async throws -> Return {
let absoluteKey = keyPrefix.appending(key)
return try await provider.watchValue(forKey: absoluteKey, type: type) { updates in
let mappedUpdates = ConcreteAsyncSequence(updates)
let mappedUpdates =
updates
.map { updateTuple in
let (providerResults, value) = mergingIsSecret(
isSecret,
Expand Down Expand Up @@ -790,7 +792,8 @@ extension ConfigReader {
) async throws -> Return {
let absoluteKey = keyPrefix.appending(key)
return try await provider.watchValue(forKey: absoluteKey, type: type) { updates in
let mappedUpdates = ConcreteAsyncSequence(updates)
let mappedUpdates =
updates
.mapThrowing { updateTuple in
let (providerResults, value) = mergingIsSecret(
isSecret,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Configuration/ConfigSnapshotReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ extension ConfigReader {
try await provider.watchSnapshot { updates in
try await updatesHandler(
ConfigUpdatesAsyncSequence(
ConcreteAsyncSequence(updates)
updates
.map { multiSnapshot in
ConfigSnapshotReader(
keyPrefix: keyPrefix,
Expand Down
4 changes: 2 additions & 2 deletions Sources/Configuration/MultiProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ extension MultiProvider {
updatesHandler: { updateArrays in
try await body(
ConfigUpdatesAsyncSequence(
ConcreteAsyncSequence(updateArrays)
updateArrays
.map { array in
MultiSnapshot(snapshots: array)
}
Expand Down Expand Up @@ -302,7 +302,7 @@ extension MultiProvider {
updatesHandler: { updateArrays in
try await updatesHandler(
ConfigUpdatesAsyncSequence(
ConcreteAsyncSequence(updateArrays)
updateArrays
.map { array in
var results: [AccessEvent.ProviderResult] = []
for (providerIndex, lookupResult) in array.enumerated() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ extension KeyMappingProvider: ConfigProvider {
try await upstream.watchSnapshot { sequence in
try await updatesHandler(
ConfigUpdatesAsyncSequence(
ConcreteAsyncSequence(sequence)
sequence
.map { snapshot in
MappedKeySnapshot(mapKey: self.mapKey, upstream: self.upstream.snapshot())
}
Expand Down
65 changes: 0 additions & 65 deletions Sources/Configuration/Utilities/AsyncSequences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,71 +55,6 @@ extension ConfigUpdatesAsyncSequence: AsyncSequence {
}
}

/// A concrete async sequence that wraps an existential async sequence.
///
/// This type provides a concrete implementation of `AsyncSequence` that wraps
/// an existential `any AsyncSequence`. It serves as a workaround for limitations
/// in Swift's type system where certain operations like `map` and other sequence
/// transformations don't work directly with existential async sequences.
///
/// ## Purpose
///
/// The Swift standard library's async sequence operations require concrete types
/// to function properly. When working with `any AsyncSequence`, these operations
/// are not available. This wrapper provides a concrete type that enables the use
/// of standard async sequence operations.
///
/// ## Usage
///
/// ```swift
/// let existentialSequence: any AsyncSequence<Int, Never> = someAsyncSequence
/// let concreteSequence = ConcreteAsyncSequence(existentialSequence)
///
/// // Now you can use standard async sequence operations
/// let mappedSequence = concreteSequence.map { $0 * 2 }
/// ```
package struct ConcreteAsyncSequence<Element: Sendable, Failure: Error> {

/// The upstream async sequence that this concrete sequence wraps.
///
/// This property holds the async sequence that provides the actual elements.
/// All operations on this concrete sequence are delegated to this upstream sequence.
var upstream: any AsyncSequence<Element, Failure>

/// Creates a new concrete async sequence wrapping the provided existential sequence.
///
/// - Parameter upstream: The async sequence to wrap.
package init(_ upstream: any AsyncSequence<Element, Failure>) {
self.upstream = upstream
}
}

extension ConcreteAsyncSequence: AsyncSequence {

/// An async iterator that wraps an existential async iterator.
///
/// This iterator provides the concrete implementation for iterating over
/// the wrapped existential async sequence. It delegates all operations
/// to the upstream iterator while maintaining type safety.
package struct Iterator: AsyncIteratorProtocol {

/// The upstream async iterator that provides the actual iteration logic.
var upstream: any AsyncIteratorProtocol<Element, Failure>

// swift-format-ignore: AllPublicDeclarationsHaveDocumentation
package mutating func next(
isolation actor: isolated (any Actor)?
) async throws(Failure) -> Element? {
try await upstream.next(isolation: actor)
}
}

// swift-format-ignore: AllPublicDeclarationsHaveDocumentation
package func makeAsyncIterator() -> Iterator {
Iterator(upstream: upstream.makeAsyncIterator())
}
}

// MARK: - AsyncSequence extensions

extension AsyncSequence where Failure == Never {
Expand Down
33 changes: 0 additions & 33 deletions Tests/ConfigurationTests/AsyncSequencesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,39 +57,6 @@ struct AsyncSequencesTests {
#expect(results == [1, 2])
}

// MARK: - ConcreteAsyncSequence tests

@Test func concreteAsyncSequenceWrapsExistentialSequence() async throws {
let values = [1, 2, 3, 4, 5]
let concreteSequence = ConcreteAsyncSequence(values.async)
let results = await concreteSequence.collect()
#expect(results == values)
}

@Test func concreteAsyncSequenceWithEmptySequence() async throws {
let concreteSequence = ConcreteAsyncSequence(([] as [Int]).async)
let results = await concreteSequence.collect()
#expect(results.isEmpty)
}

@Test func concreteAsyncSequencePropagatesErrors() async throws {
let throwingSequence = AsyncThrowingStream<Int, any Error> { continuation in
continuation.yield(1)
continuation.yield(2)
continuation.finish(throwing: TestError.upstreamFailed)
}
let concreteSequence = ConcreteAsyncSequence(throwingSequence)

var results: [Int] = []
let error = await #expect(throws: TestError.self) {
for try await value in concreteSequence {
results.append(value)
}
}
#expect(error == .upstreamFailed)
#expect(results == [1, 2])
}

// MARK: - mapThrowing Tests

@Test func mapThrowingSuccessfulTransformation() async throws {
Expand Down
4 changes: 2 additions & 2 deletions Tests/ConfigurationTests/MultiProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ struct MultiProviderTests {
try await multiProvider.watchValue(forKey: key, type: type) { updates in
try await handler(
ConfigUpdatesAsyncSequence(
ConcreteAsyncSequence(updates)
updates
.map { update in
update.1.map { .init(encodedKey: "<not tested>", value: $0) }
}
Expand All @@ -116,7 +116,7 @@ struct MultiProviderTests {
try await multiProvider.watchSnapshot { updates in
try await updatesHandler(
ConfigUpdatesAsyncSequence(
ConcreteAsyncSequence(updates).map { $0 }
updates.map { $0 }
)
)
}
Expand Down
Loading