Skip to content
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

Get 3.0 #75

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021-2022 Alexander Grebenyuk
Copyright (c) 2021-2023 Alexander Grebenyuk

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
31 changes: 29 additions & 2 deletions Sources/Get/APIClient.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

#warning("add Response to Error.responseValidationFailed")

/// Performs network requests constructed using ``Request``.
public actor APIClient {
/// The configuration with which the client was initialized with.
Expand Down Expand Up @@ -89,6 +91,31 @@ public actor APIClient {

// MARK: Sending Requests

#warning("should we keep basic send?")
#warning("more way to create DataTask?, e.g. with URL/URLRequest?")

public func dataTask<T>(with request: Request<T>) -> DataTask<T> {
let task = DataTask<T>()
task.task = Task<DataTask<T>.Response, Error> {
defer { task.task = nil }
let request = try await makeURLRequest(for: request, task.configure)
return try await performRequest {
var request = request
try await self.delegate.client(self, willSendRequest: &request)
let dataTask = session.dataTask(with: request)
do {
let response = try await dataLoader.startDataTask(dataTask, session: session, delegate: Box(task.delegate))
try validate(response)
#warning("remove this")
return DataTask<T>.Response(data: response.data, response: response.response, task: dataTask, metrics: response.metrics)
} catch {
throw DataLoaderError(task: dataTask, error: error)
}
}
}
return task
}

/// Sends the given request and returns a decoded response.
///
/// - parameters:
Expand Down Expand Up @@ -146,7 +173,7 @@ public actor APIClient {
try await self.delegate.client(self, willSendRequest: &request)
let task = session.dataTask(with: request)
do {
let response = try await dataLoader.startDataTask(task, session: session, delegate: delegate)
let response = try await dataLoader.startDataTask(task, session: session, delegate: Box(delegate))
try validate(response)
return response
} catch {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Get/APIClientDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import Foundation
#if canImport(FoundationNetworking)
Expand Down
6 changes: 3 additions & 3 deletions Sources/Get/DataLoader.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import Foundation
#if canImport(FoundationNetworking)
Expand Down Expand Up @@ -28,10 +28,10 @@ final class DataLoader: NSObject, URLSessionDataDelegate, URLSessionDownloadDele
return url
}()

func startDataTask(_ task: URLSessionDataTask, session: URLSession, delegate: URLSessionDataDelegate?) async throws -> Response<Data> {
func startDataTask(_ task: URLSessionDataTask, session: URLSession, delegate: Box<URLSessionDataDelegate?>) async throws -> Response<Data> {
try await withTaskCancellationHandler(operation: {
try await withUnsafeThrowingContinuation { continuation in
let handler = DataTaskHandler(delegate: delegate)
let handler = DataTaskHandler(delegate: delegate.value)
handler.completion = continuation.resume(with:)
self.handlers[task] = handler

Expand Down
90 changes: 90 additions & 0 deletions Sources/Get/DataTask.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import Foundation
import Combine

public final class DataTask<T>: @unchecked Sendable {
var task: Task<Response, Error>!

public var delegate: URLSessionDataDelegate?

#warning("implement progress")
public var progress: some Publisher<Float, Never> { _progress }
var _progress = CurrentValueSubject<Float, Never>(0.0)

public func cancel() {
task.cancel()
}

public var response: Response {
get async throws { try await result.get() }
}

public var result: Result<Response, Error> {
get async {
await withTaskCancellationHandler(operation: {
await task.result
}, onCancel: {
cancel()
})
}
}

#warning("add publisher in addition to Async/Await?")

#warning("this isn't thread-safe")
public var configure: (@Sendable (inout URLRequest) -> Void)?

/// A response with an associated value and metadata.
public struct Response: TaskDesponse {
/// Original response.
public let response: URLResponse
/// Original response data.
public let data: Data
/// Completed task.
public let task: URLSessionTask
/// Task metrics collected for the request.
public let metrics: URLSessionTaskMetrics?

/// Initializes the response.
public init(data: Data, response: URLResponse, task: URLSessionTask, metrics: URLSessionTaskMetrics? = nil) {
self.data = data
self.response = response
self.task = task
self.metrics = metrics
}
}
}

// Pros: this approach will allow users to extend the task with custom decoders

extension DataTask where T: Decodable {
public var value: T {
get async throws { try await response.decode(T.self) }
}
}

extension DataTask.Response where T: Decodable {
public var value: T {
get async throws { try await decode(T.self) }
}
}

extension DataTask.Response {
public func decode<T: Decodable>(_ type: T.Type, using decoder: JSONDecoder = JSONDecoder()) async throws -> T {
try await Get.decode(data, using: decoder)
}
}

// Silences Sendable warnings in some Foundation APIs.
struct Box<T>: @unchecked Sendable {
let value: T

init(_ value: T) {
self.value = value
}
}

#warning("add in docs that you can easily add custom decoders to the requests withot modifying the client iself")
4 changes: 3 additions & 1 deletion Sources/Get/Request.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import Foundation

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

#warning("does it need to be generic at all?")
/// An HTTP network request.
public struct Request<Response>: @unchecked Sendable {
/// HTTP method, e.g. "GET".
Expand Down Expand Up @@ -62,6 +63,7 @@ public struct Request<Response>: @unchecked Sendable {
self.method = method
}

#warning("we no longer need to change the reponse type?")
/// Changes the response type keeping the rest of the request parameters.
public func withResponse<T>(_ type: T.Type) -> Request<T> {
var copy = Request<T>(optionalUrl: url, method: method)
Expand Down
23 changes: 22 additions & 1 deletion Sources/Get/Response.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import Foundation

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

#warning("adding error to reponse it not a good idea because its inconvenient")

public protocol TaskDesponse: Sendable {
/// Original response.
var response: URLResponse { get }
/// Completed task.
var task: URLSessionTask { get }
/// Task metrics collected for the request.
var metrics: URLSessionTaskMetrics? { get }
}

extension TaskDesponse {
/// Response HTTP status code.
public var statusCode: Int? { (response as? HTTPURLResponse)?.statusCode }
/// Original request.
public var originalRequest: URLRequest? { task.originalRequest }
/// The URL request object currently being handled by the task. May be
/// different from the original request.
public var currentRequest: URLRequest? { task.currentRequest }
}

/// A response with an associated value and metadata.
public struct Response<T> {
/// Decoded response value.
Expand Down
2 changes: 1 addition & 1 deletion Tests/GetTests/ClientAuthorizationTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import XCTest
#if canImport(FoundationNetworking)
Expand Down
2 changes: 1 addition & 1 deletion Tests/GetTests/ClientDelegateTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import XCTest
@testable import Get
Expand Down
2 changes: 1 addition & 1 deletion Tests/GetTests/ClientIIntegrationTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import XCTest
@testable import Get
Expand Down
2 changes: 1 addition & 1 deletion Tests/GetTests/ClientMakeRequestsTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import XCTest
@testable import Get
Expand Down
2 changes: 1 addition & 1 deletion Tests/GetTests/ClientMiscTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import XCTest
@testable import Get
Expand Down
2 changes: 1 addition & 1 deletion Tests/GetTests/ClientSendingRequestsTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import XCTest
@testable import Get
Expand Down
2 changes: 1 addition & 1 deletion Tests/GetTests/ClientSessionDelegateTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import XCTest
@testable import Get
Expand Down
2 changes: 1 addition & 1 deletion Tests/GetTests/CodeSamplesTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).
// Copyright (c) 2021-2023 Alexander Grebenyuk (github.com/kean).

import XCTest
import Get
Expand Down
Loading