Skip to content

Commit

Permalink
chore(merge): merge #45 into main
Browse files Browse the repository at this point in the history
  • Loading branch information
sbertix authored Aug 27, 2021
2 parents 485bd9b + dd36fae commit 0fa17ce
Show file tree
Hide file tree
Showing 21 changed files with 138 additions and 123 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.DS_Store
*.xcodeproj
.swiftpm/
.swiftpm/*
*.xcodeproj
!Followers.xcodeproj
/.build
/Packages
Expand Down
22 changes: 12 additions & 10 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,25 @@ let package = Package(
// Exposed libraries.
products: [.library(name: "Requests",
targets: ["Requests"]),
.library(name: "Storage",
targets: ["Storage"]),
.library(name: "StorageCrypto",
targets: ["StorageCrypto"])],
.library(name: "Storages",
targets: ["Storages"]),
.library(name: "EncryptedStorages",
targets: ["EncryptedStorages"])],
// Package dependencies.
dependencies: [.package(url: "https://github.com/kishikawakatsumi/KeychainAccess",
.upToNextMinor(from: "4.2.2"))],
.upToNextMinor(from: "4.2.2")),
.package(url: "https://github.com/kean/Future",
.upToNextMinor(from: "1.4.0"))],
// All targets.
targets: [.target(name: "Core"),
.target(name: "Requests",
dependencies: ["Core"]),
.target(name: "Storage",
dependencies: ["Core", "Future"]),
.target(name: "Storages",
dependencies: []),
.target(name: "StorageCrypto",
dependencies: ["Storage", "KeychainAccess"]),
.target(name: "EncryptedStorages",
dependencies: ["Storages", "KeychainAccess"]),
.testTarget(name: "ComposableRequestTests",
dependencies: ["Requests", "Storage", "StorageCrypto"])]
dependencies: ["Requests", "Storages", "EncryptedStorages"])]
)

if ProcessInfo.processInfo.environment["TARGETING_WATCHOS"] == "true" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import Foundation

import KeychainAccess
import protocol Storage.Storable
import protocol Storage.ThrowingStorage
import protocol Storages.Storable
import protocol Storages.ThrowingStorage

/// A `typealias` for `KeychainAccess.Keychain`.
///
Expand Down
40 changes: 40 additions & 0 deletions Sources/Requests/Receivable/Receivables+Once.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Receivables+Once.swift
// ComposableRequest
//
// Created by Stefano Bertagno on 27/08/21.
//

import Foundation

public extension Receivables {
/// A `struct` defining a `Requester`-based receivable
/// receiving immediately either a success or failure.
struct Once<Requester: Requests.Requester, Success>: Receivable {
/// The underlying result.
public let result: Result<Success, Error>

/// Init.
///
/// - parameters:
/// - success: A valid `Success`.
/// - requester: A valid `Requester`.
public init(output success: Success, with requester: Requester) {
self.result = .success(success)
}

/// Init.
///
/// - parameters:
/// - failure: A valid `Error`.
/// - requester: A valid `Requester`.
public init(error failure: Error, with requester: Requester) {
self.result = .failure(failure)
}
}
}

public extension Requester {
/// The associated once type.
typealias Once<S> = Receivables.Once<Self, S>
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ where Parent: URLSessionAsyncReceivable {
}
}

@available(iOS 15, macOS 12, watchOS 8, tvOS 15, *)
extension Receivables.Once: URLSessionAsyncReceivable, URLSessionAsyncMockReceivable
where Requester.Output: URLSessionAsyncReceivable {
/// The underlying response.
public var response: URLSessionAsyncRequester.Response<Success> {
.init(priority: nil) { try self.result.get() }
}
}

@available(iOS 15, macOS 12, watchOS 8, tvOS 15, *)
extension Receivables.Pager: URLSessionAsyncReceivable, URLSessionAsyncMockReceivable
where Child: URLSessionAsyncReceivable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ where Parent: URLSessionCombineReceivable {
}
}

@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension Receivables.Once: URLSessionCombineReceivable, URLSessionCombineMockReceivable
where Requester.Output: URLSessionCombineReceivable {
/// The underlying response.
public var response: URLSessionCombineRequester.Response<Success> {
switch result {
case .success(let output):
return .init(publisher: Just(output).setFailureType(to: Error.self))
case .failure(let error):
return .init(publisher: Fail(error: error))
}
}
}

@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension Receivables.Pager: URLSessionCombineReceivable, URLSessionCombineMockReceivable
where Child: URLSessionCombineReceivable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import Foundation

import Future

/// A `protocol` defining a mock `URLSessionCompletionReceivable`.
public protocol URLSessionCompletionMockReceivable {
// swiftlint:disable identifier_name
Expand Down Expand Up @@ -35,7 +37,9 @@ public extension URLSessionCompletionReceivable {
/// - returns: `self`.
func onResult(_ handler: @escaping (Result<Success, Error>) -> Void) -> URLSessionCompletionRequester.Response<Success> {
let response = self.response
response.handler.completion = handler
response.future.on(success: { handler(.success($0)) },
failure: { handler(.failure($0)) },
completion: nil)
return response
}

Expand Down Expand Up @@ -99,6 +103,14 @@ where Parent: URLSessionCompletionReceivable {
}
}

extension Receivables.Once: URLSessionCompletionReceivable, URLSessionCompletionMockReceivable
where Requester.Output: URLSessionCompletionReceivable {
/// The response.
public var response: URLSessionCompletionRequester.Response<Success> {
.init(task: nil, future: .init(result: result))
}
}

extension Receivables.Pager: URLSessionCompletionReceivable, URLSessionCompletionMockReceivable
where Child: URLSessionCompletionReceivable {
/// The underlying response.
Expand Down Expand Up @@ -131,20 +143,20 @@ extension Receivables.Switch: URLSessionCompletionReceivable, URLSessionCompleti
where Parent: URLSessionCompletionReceivable, Child: URLSessionCompletionReceivable {
/// The undelrying response.
public var response: URLSessionCompletionRequester.Response<Success> {
let handler = URLSessionCompletionRequester.Response<Success>.Handler()
let promise = Promise<Success, Error>()
let response = parent.response
response.handler.completion = {
switch $0 {
case .success(let success):
response.future.on(
success: {
do {
try self.generator(success).onResult { handler.completion?($0) }.resume()
try self.generator($0).onResult { promise.resolve(result: $0) }.resume()
} catch {
handler.completion?(.failure(error))
promise.fail(error: error)
}
case .failure(let failure):
handler.completion?(.failure(failure))
},
failure: {
promise.fail(error: $0)
}
}
return .init(value: response.value, handler: handler)
)
return .init(task: response.task, future: promise.future)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,103 +7,62 @@

import Foundation

import Future

public extension URLSessionCompletionRequester {
/// A `struct` defining the output for a `URLSessionCompletionRequester`.
struct Response<Success>: URLSessionCompletionReceivable {
/// The underlying value.
public let value: URLSessionCompletionValue
/// The delegate.
public var handler: Handler
/// The underlying data task.
public weak var task: URLSessionDataTask?
/// The underlying future.
public let future: Future<Success, Error>

/// The underlying response.
public var response: URLSessionCompletionRequester.Response<Success> {
self
}

/// Init.
///
/// - parameters:
/// - value: A valid `URLSessionCompletionValue`.
/// - handler: A valid `Handler`.
public init(value: URLSessionCompletionValue, handler: Handler) {
self.value = value
self.handler = handler
}

/// Init.
/// Resume the underlying task, if it exists.
///
/// - parameter request: A valid `Request`.
public init(invalidRequest request: Request) {
self.init(value: .invalidRequest(request), handler: .init())
/// - returns: An optional `URLSessionDataTask`.
@discardableResult
public func resume() -> URLSessionDataTask? {
task?.resume()
return task
}

/// Init.
///
/// - parameters:
/// - task: A valid `URLSessionDataTask`.
/// - handler: A valid `Handler`.
public init(task: URLSessionDataTask, handler: Handler) {
self.init(value: .task(task), handler: handler)
}

@discardableResult
/// Resume.
///
/// - returns: An optional `URLSessionDataTask`.
public func resume() -> URLSessionDataTask? {
switch value {
case .invalidRequest(let request):
// If there's no task you should still
// notify it to the user.
handler.completion?(.failure(Request.Error.invalidRequest(request)))
return nil
case .task(let task):
task.resume()
return task
}
/// - task: An optional `URLSessionDataTask`. Defaults to `nil`.
/// - future: A valid `Future`.
public init(task: URLSessionDataTask? = nil, future: Future<Success, Error>) {
self.task = task
self.future = future
}

/// Flat map the current task.
///
/// - parameter mapper: A valid mapper.
/// - returns: Some `URLSessionCompletionReceivable`.
func chain<S>(_ mapper: @escaping (Result<Success, Error>) -> Result<S, Error>) -> URLSessionCompletionRequester.Response<S> {
let handler = URLSessionCompletionRequester.Response<S>.Handler()
self.handler.completion = { handler.completion?(mapper($0)) }
return .init(value: value, handler: handler)
.init(task: task, future: future.materialize().flatMap { .init(result: mapper($0)) })
}

/// Flat map the current task.
///
/// - parameter mapper: A valid mapper.
/// - returns: Some `URLSessionCompletionReceivable`.
func chain<S>(_ mapper: @escaping (Success) -> Result<S, Error>) -> URLSessionCompletionRequester.Response<S> {
chain { (result: Result<Success, Error>) in result.flatMap(mapper) }
.init(task: task, future: future.flatMap { .init(result: mapper($0)) })
}

/// Flat map the current task.
///
/// - parameter mapper: A valid mapper.
/// - returns: Some `URLSessionCompletionReceivable`.
func chain(_ mapper: @escaping (Error) -> Result<Success, Error>) -> URLSessionCompletionRequester.Response<Success> {
chain { (result: Result<Success, Error>) in result.flatMapError(mapper) }
}
}
}

public extension URLSessionCompletionRequester.Response {
/// A `class` defining a data task handler.
final class Handler {
/// The underlying completion.
public var completion: ((Result<Success, Error>) -> Void)?

/// Init.
///
/// - parameters:
/// - transformer: A valid mapper.
/// - completion: An optional completion handler. Defaults to `nil`.
public init(completion: ((Result<Success, Error>) -> Void)? = nil) {
self.completion = completion
.init(task: task, future: future.flatMapError { .init(result: mapper($0)) })
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import Foundation

import Future

/// A `struct` defining a concrete implementation of `Requester`
/// through _completion handlers_.
public struct URLSessionCompletionRequester {
Expand Down Expand Up @@ -48,21 +50,20 @@ extension URLSessionCompletionRequester: Requester {
/// - note: This is implemented as a `static` function to hide its definition. Rely on `request.prepare(with:)` instead.
public static func prepare(_ endpoint: Request, with requester: Self) -> Output {
guard let request = Request.request(from: endpoint) else {
return .init(invalidRequest: endpoint)
return .init(future: .init(error: Request.Error.invalidRequest(endpoint)))
}
let delegate = Output.Handler()
requester.input.logger?.log(request)
let task = requester.input.session.dataTask(with: request) { [weak delegate] data, response, error in
let promise = Promise<Request.Response, Error>()
let task = requester.input.session.dataTask(with: request) { data, response, error in
if let error = error {
requester.input.logger?.log(.failure(error))
delegate?.completion?(.failure(error))
promise.fail(error: error)
} else if let data = data, let response = response {
let result = Result<Request.Response, Error>.success(.init(data: data, response: response))
requester.input.logger?.log(result)
delegate?.completion?(result)
promise.resolve(result: result)
}
}
return .init(task: task, handler: delegate)
return .init(task: task, future: promise.future)
}
}

Expand Down

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 0fa17ce

Please sign in to comment.