-
Notifications
You must be signed in to change notification settings - Fork 117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make HTTPClientResponse.init
public
#632
Conversation
84dde48
to
1f146a4
Compare
Sources/AsyncHTTPClient/AsyncAwait/AsyncSequenceFromSyncSequence.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/AsyncAwait/AsyncSequenceFromSyncSequence.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/AsyncAwait/AsyncSequenceFromSyncSequence.swift
Outdated
Show resolved
Hide resolved
public func makeAsyncIterator() -> AsyncIterator { | ||
AsyncIterator(stream: IteratorStream(bag: self.bag)) | ||
|
||
@inlinable public init(){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@inlinable public init(){ | |
@inlinable public init() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason not to make this a memberwise initializer with these defaults already provided?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We eventually want to add support for trailing headers and this would require us to add another init
for it if we want to allow trailing headers in the init too.
However, I'm fine with adding a member wise init already today if you are too. We aren't likely to add any more properties to this struct (famous last words) and one additional init in the future isn't too bad.
/// are entirely synthetic and have no semantic meaning. | ||
public struct Body: AsyncSequence, Sendable { | ||
public typealias Element = ByteBuffer | ||
@usableFromInline typealias Storage = Either<TransactionBody, AnyAsyncSequence<ByteBuffer>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, I think the use of Either
is excessively clever, and it leads to some unclear code (case a
and case b
). I'd rather see a specific-case enum for this use-case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, this looks like a fine approach to me. However, I would like to discuss the similarities and differences between the approach we took in gRPC and here. In gRPC we are just providing a testing interface which you can use to drive the sequence. Here we allow the user to provider their own AsyncSequence
.
The former makes it slightly easier to get started testing since you can just use that interface to yield
and finish
. The latter provides a bit more customisation because you can drive more than just the next
method, i.e. also the makeAsyncIterator
.
Sources/AsyncHTTPClient/AsyncAwait/AsyncSequenceFromSyncSequence.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/AsyncAwait/AsyncSequenceFromSyncSequence.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/AsyncAwait/AsyncSequenceFromSyncSequence.swift
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
@inlinable func makeAsyncIterator() -> AsyncIterator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a different note and from our other learnings, we might want to ensure that only a single iterator is ever created here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do something similar already here:
async-http-client/Sources/AsyncHTTPClient/AsyncAwait/Transaction+StateMachine.swift
Lines 641 to 653 in f17a47e
private func verifyStreamIDIsEqual( | |
registered: HTTPClientResponse.Body.IteratorStream.ID, | |
this: HTTPClientResponse.Body.IteratorStream.ID, | |
file: StaticString = #file, | |
line: UInt = #line | |
) { | |
if registered != this { | |
preconditionFailure( | |
"Tried to use a second iterator on response body stream. Multiple iterators are not supported.", | |
file: file, line: line | |
) | |
} | |
} |
This implementation is a bit more clever. It will allow the creation of multiple iterators and will only fail slightly delayed if someone tries to iterate over more than one of them. I think this is actually too clever and we should remove this eventually and just fail early during the creation of a second iterator. I'm not aware of any use case for creating unused iterators but maybe @fabianfett has some in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, it was the best thing I came up with at the time. I def. like the atomic approach that we can see here the most so far. SingleIteratorPrecondition
. However I'm not sure if we should change this behavior now. Because this would IMO be a breaking change. I just went through this in GRPC...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm inclined to agree with Fabian: there's no good sense in breaking the use-case that already exists, even though it'd be better defined if we did.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed it back to the old behaviour. However, we now only enforce this on user provided AsyncSequence
s. Can also change that to use the more relaxed precondition if you want.
I think that in AHC we usually do not want to write individual We may also want to think about aligning the Request and Response API in AHC. extension HTTPClientRequest.Body {
public static func bytes(_ byteBuffer: ByteBuffer) -> Self
public static func bytes<Bytes: RandomAccessCollection & Sendable>(
_ bytes: Bytes
) -> Self where Bytes.Element == UInt8 {
Self._bytes(bytes)
}
public static func bytes<Bytes: Sequence & Sendable>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8
public static func bytes<Bytes: Collection & Sendable>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8
public static func stream<SequenceOfBytes: AsyncSequence & Sendable>(
_ sequenceOfBytes: SequenceOfBytes,
length: Length
) -> Self where SequenceOfBytes.Element == ByteBuffer
public static func stream<Bytes: AsyncSequence & Sendable>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8
} We could provide a similar interface for |
d5da21d
to
7dfed60
Compare
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) | ||
extension Sequence { | ||
/// Turns `self` into an `AsyncSequence` by vending each element of `self` asynchronously. | ||
@inlinable var async: AsyncLazySequence<Self> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This modifier already exists, doesn't it? I see it is local here, but I assume that we want to make it public eventually...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to make it public. It exists in swift-async-algorithms
. However, it doesn't yet have a 1.x release.
|
||
import Atomics | ||
|
||
/// Makes sure that a consumer of this `AsyncSequence` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure they what?
} | ||
} | ||
|
||
@inlinable func makeAsyncIterator() -> AsyncIterator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm inclined to agree with Fabian: there's no good sense in breaking the use-case that already exists, even though it'd be better defined if we did.
Motivation
We want to allow user to initialise
HTTPClientResponse
for mocking purposes in tests.Modifications
AnyAsyncSequence<Element>
to erase the type of the user providedAsyncSequence
s. We need this because we do not want to makeHTTPClientResponse.Body
generic over the specificAsyncSequence
.HTTPClientResponse.Body
toTransactionBody
and make it internal.TransactionBody
as it is internal.Either<A, B>
and conditionally conform it toAsyncSequence
ifA
andB
are areAsyncSequence
s with the sameElement
type. We use it to still allow the compiler to specialize theHTTPClientResponse.Body
default case where theAsyncSequence
is of typeTransactionBody
Either<TransactionBody, AnyAsyncSeqence<ByteBuffer>
as the new storage type forHTTPClientReponse.Body
. We still hide the concrete storage type behind a struct.AsyncSequenceFromSyncSequence
fromAsyncHTTPClientTests
toAsyncHTTPClient
and make it inlinable. (we could replace the implementation withAsyncLazySequence
fromswift-async-algorithms
)HTTPClientResponse.Body
inits:HTTPClientResponse
:Result
Users can now freely initialise and modify
HTTPClientReponse
s: