Skip to content

Commit

Permalink
feat: Separate out resolve request (#36)
Browse files Browse the repository at this point in the history
Co-authored-by: danthorpe <[email protected]>
  • Loading branch information
danthorpe and danthorpe authored Oct 29, 2023
1 parent c7f2300 commit d0b0d09
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 57 deletions.
6 changes: 2 additions & 4 deletions Sources/Networking/Components/Server/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ extension NetworkingComponent {

struct MutateRequest: NetworkingModifier {
let mutate: (inout HTTPRequestData) -> Void
func send(upstream: NetworkingComponent, request: HTTPRequestData) -> ResponseStream<
HTTPResponseData
> {
func resolve(upstream: NetworkingComponent, request: HTTPRequestData) -> HTTPRequestData {
var copy = request
mutate(&copy)
return upstream.send(copy)
return copy
}
}
3 changes: 2 additions & 1 deletion Sources/Networking/Components/URLSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import os.log

extension URLSession: NetworkingComponent {
public func send(_ request: HTTPRequestData) -> ResponseStream<HTTPResponseData> {
ResponseStream<HTTPResponseData> { continuation in
let request = resolve(request)
return ResponseStream<HTTPResponseData> { continuation in
Task {
@NetworkEnvironment(\.instrument) var instrument
guard let urlRequest = URLRequest(http: request) else {
Expand Down
49 changes: 49 additions & 0 deletions Sources/Networking/Core/NetworkingComponent+Data.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Dependencies
import Foundation
import Helpers

extension NetworkingComponent {

@discardableResult
public func data(
_ request: HTTPRequestData,
progress updateProgress: @escaping @Sendable (BytesReceived) async -> Void = { _ in },
timeout duration: Duration,
using clock: @autoclosure () -> any Clock<Duration>
) async throws -> HTTPResponseData {
do {
try Task.checkCancellation()
return try await send(request)
.compactMap { element in
await updateProgress(element.progress)
return element.value
}
.first(beforeTimeout: duration, using: clock())
} catch is TimeoutError {
throw StackError.timeout(request)
}
}

@discardableResult
public func data(
_ request: HTTPRequestData,
progress updateProgress: @escaping @Sendable (BytesReceived) async -> Void = { _ in },
timeout duration: Duration
) async throws -> HTTPResponseData {
try await data(
request,
progress: updateProgress,
timeout: duration,
using: Dependency(\.continuousClock).wrappedValue
)
}

@discardableResult
public func data(
_ request: HTTPRequestData,
progress updateProgress: @escaping @Sendable (BytesReceived) async -> Void = { _ in }
) async throws -> HTTPResponseData {
try await data(
request, progress: updateProgress, timeout: .seconds(request.requestTimeoutInSeconds))
}
}
8 changes: 8 additions & 0 deletions Sources/Networking/Core/NetworkingComponent+Metadata.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

extension NetworkingComponent {

public var authority: String {
resolve(HTTPRequestData()).authority
}
}
54 changes: 10 additions & 44 deletions Sources/Networking/Core/NetworkingComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,25 @@ import Helpers

/// `NetworkingComponent` is a protocol to enable a chain-of-responsibility style networking stack. The
/// stack is comprised of multiple elements, each of which conforms to this protocol.
///
/// It has a single requirement for sending a networking request, and receiving a stream of events back.
public protocol NetworkingComponent {

/// Send the networking request and receive a stream of events back.
func send(_ request: HTTPRequestData) -> ResponseStream<HTTPResponseData>

/// Get the final resolved request before it would be sent. This is very useful to query the overall networking stack
func resolve(_ request: HTTPRequestData) -> HTTPRequestData
}

public typealias ResponseStream<Value> = AsyncThrowingStream<Partial<Value, BytesReceived>, Error>
// MARK: - Default Implementations

extension NetworkingComponent {

@discardableResult
public func data(
_ request: HTTPRequestData,
progress updateProgress: @escaping @Sendable (BytesReceived) async -> Void = { _ in },
timeout duration: Duration,
using clock: @autoclosure () -> any Clock<Duration>
) async throws -> HTTPResponseData {
do {
try Task.checkCancellation()
return try await send(request)
.compactMap { element in
await updateProgress(element.progress)
return element.value
}
.first(beforeTimeout: duration, using: clock())
} catch is TimeoutError {
throw StackError.timeout(request)
}
public func resolve(_ request: HTTPRequestData) -> HTTPRequestData {
request
}
}

@discardableResult
public func data(
_ request: HTTPRequestData,
progress updateProgress: @escaping @Sendable (BytesReceived) async -> Void = { _ in },
timeout duration: Duration
) async throws -> HTTPResponseData {
try await data(
request,
progress: updateProgress,
timeout: duration,
using: Dependency(\.continuousClock).wrappedValue
)
}
public typealias ResponseStream<Value> = AsyncThrowingStream<Partial<Value, BytesReceived>, Error>

@discardableResult
public func data(
_ request: HTTPRequestData,
progress updateProgress: @escaping @Sendable (BytesReceived) async -> Void = { _ in }
) async throws -> HTTPResponseData {
try await data(
request, progress: updateProgress, timeout: .seconds(request.requestTimeoutInSeconds))
}
}

// MARK: - Codable Support

Expand Down
38 changes: 31 additions & 7 deletions Sources/Networking/Core/NetworkingModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@ import Dependencies
import Foundation
import Helpers

/// Chain networking components by implementing `NetworkingModifier`
///
/// Provide a public interface via an extension on `NetworkingComponent` which calls
/// through to `modified(_ : some NetworkingModifier)`.
public protocol NetworkingModifier {
func send(upstream: NetworkingComponent, request: HTTPRequestData) -> ResponseStream<
HTTPResponseData
>

/// Perform modifications to the input request
func resolve(upstream: NetworkingComponent, request: HTTPRequestData) -> HTTPRequestData

/// Perform some modification, before sending the request onto the upstread component, and
/// doing any post-processing to the resultant stream
func send(upstream: NetworkingComponent, request: HTTPRequestData) -> ResponseStream<HTTPResponseData>
}

extension NetworkingModifier {
public func resolve(upstream: NetworkingComponent, request: HTTPRequestData) -> HTTPRequestData {
upstream.resolve(request)
}

public func send(upstream: NetworkingComponent, request: HTTPRequestData) -> ResponseStream<HTTPResponseData> {
upstream.send(request)
}
}

extension NetworkingComponent {
Expand All @@ -14,16 +32,22 @@ extension NetworkingComponent {
}
}

private struct Modified<Upstream: NetworkingComponent, Modifier: NetworkingModifier>:
NetworkingComponent
{
private struct Modified<Upstream: NetworkingComponent, Modifier: NetworkingModifier> {
let upstream: Upstream
let modifier: Modifier
init(upstream: Upstream, modifier: Modifier) {
self.upstream = upstream
self.modifier = modifier
}
}

extension Modified: NetworkingComponent {

func resolve(_ request: HTTPRequestData) -> HTTPRequestData {
modifier.resolve(upstream: upstream, request: request)
}

func send(_ request: HTTPRequestData) -> ResponseStream<HTTPResponseData> {
modifier.send(upstream: upstream, request: request)
modifier.send(upstream: upstream, request: resolve(request))
}
}
3 changes: 2 additions & 1 deletion Sources/TestSupport/TerminalNetworkingComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public struct TerminalNetworkingComponent: NetworkingComponent {
self.isFailingTerminal = isFailingTerminal
}
public func send(_ request: HTTPRequestData) -> ResponseStream<HTTPResponseData> {
ResponseStream { continuation in
let request = resolve(request)
return ResponseStream { continuation in
if isFailingTerminal {
continuation.finish(throwing: TestFailure(request: request))
} else {
Expand Down
2 changes: 2 additions & 0 deletions Tests/NetworkingTests/Components/ServerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ final class ServerTests: NetworkingTestCase {

let sentRequests = await reporter.requests
XCTAssertEqual(sentRequests.map(\.authority), ["sample.com"])

XCTAssertEqual(network.authority, "sample.com")
}

func test__set_path() async throws {
Expand Down

0 comments on commit d0b0d09

Please sign in to comment.