Skip to content

Commit 0fa17ce

Browse files
authored
chore(merge): merge #45 into main
2 parents 485bd9b + dd36fae commit 0fa17ce

21 files changed

+138
-123
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.DS_Store
2+
*.xcodeproj
23
.swiftpm/
34
.swiftpm/*
4-
*.xcodeproj
55
!Followers.xcodeproj
66
/.build
77
/Packages

Package.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,25 @@ let package = Package(
1515
// Exposed libraries.
1616
products: [.library(name: "Requests",
1717
targets: ["Requests"]),
18-
.library(name: "Storage",
19-
targets: ["Storage"]),
20-
.library(name: "StorageCrypto",
21-
targets: ["StorageCrypto"])],
18+
.library(name: "Storages",
19+
targets: ["Storages"]),
20+
.library(name: "EncryptedStorages",
21+
targets: ["EncryptedStorages"])],
2222
// Package dependencies.
2323
dependencies: [.package(url: "https://github.com/kishikawakatsumi/KeychainAccess",
24-
.upToNextMinor(from: "4.2.2"))],
24+
.upToNextMinor(from: "4.2.2")),
25+
.package(url: "https://github.com/kean/Future",
26+
.upToNextMinor(from: "1.4.0"))],
2527
// All targets.
2628
targets: [.target(name: "Core"),
2729
.target(name: "Requests",
28-
dependencies: ["Core"]),
29-
.target(name: "Storage",
30+
dependencies: ["Core", "Future"]),
31+
.target(name: "Storages",
3032
dependencies: []),
31-
.target(name: "StorageCrypto",
32-
dependencies: ["Storage", "KeychainAccess"]),
33+
.target(name: "EncryptedStorages",
34+
dependencies: ["Storages", "KeychainAccess"]),
3335
.testTarget(name: "ComposableRequestTests",
34-
dependencies: ["Requests", "Storage", "StorageCrypto"])]
36+
dependencies: ["Requests", "Storages", "EncryptedStorages"])]
3537
)
3638

3739
if ProcessInfo.processInfo.environment["TARGETING_WATCHOS"] == "true" {

Sources/StorageCrypto/KeychainStorage.swift renamed to Sources/EncryptedStorages/KeychainStorage.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import Foundation
99

1010
import KeychainAccess
11-
import protocol Storage.Storable
12-
import protocol Storage.ThrowingStorage
11+
import protocol Storages.Storable
12+
import protocol Storages.ThrowingStorage
1313

1414
/// A `typealias` for `KeychainAccess.Keychain`.
1515
///
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// Receivables+Once.swift
3+
// ComposableRequest
4+
//
5+
// Created by Stefano Bertagno on 27/08/21.
6+
//
7+
8+
import Foundation
9+
10+
public extension Receivables {
11+
/// A `struct` defining a `Requester`-based receivable
12+
/// receiving immediately either a success or failure.
13+
struct Once<Requester: Requests.Requester, Success>: Receivable {
14+
/// The underlying result.
15+
public let result: Result<Success, Error>
16+
17+
/// Init.
18+
///
19+
/// - parameters:
20+
/// - success: A valid `Success`.
21+
/// - requester: A valid `Requester`.
22+
public init(output success: Success, with requester: Requester) {
23+
self.result = .success(success)
24+
}
25+
26+
/// Init.
27+
///
28+
/// - parameters:
29+
/// - failure: A valid `Error`.
30+
/// - requester: A valid `Requester`.
31+
public init(error failure: Error, with requester: Requester) {
32+
self.result = .failure(failure)
33+
}
34+
}
35+
}
36+
37+
public extension Requester {
38+
/// The associated once type.
39+
typealias Once<S> = Receivables.Once<Self, S>
40+
}

Sources/Requests/Requester/Async/URLSessionAsyncReceivable.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ where Parent: URLSessionAsyncReceivable {
111111
}
112112
}
113113

114+
@available(iOS 15, macOS 12, watchOS 8, tvOS 15, *)
115+
extension Receivables.Once: URLSessionAsyncReceivable, URLSessionAsyncMockReceivable
116+
where Requester.Output: URLSessionAsyncReceivable {
117+
/// The underlying response.
118+
public var response: URLSessionAsyncRequester.Response<Success> {
119+
.init(priority: nil) { try self.result.get() }
120+
}
121+
}
122+
114123
@available(iOS 15, macOS 12, watchOS 8, tvOS 15, *)
115124
extension Receivables.Pager: URLSessionAsyncReceivable, URLSessionAsyncMockReceivable
116125
where Child: URLSessionAsyncReceivable {

Sources/Requests/Requester/Combine/URLSessionCombineReceivable.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ where Parent: URLSessionCombineReceivable {
100100
}
101101
}
102102

103+
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
104+
extension Receivables.Once: URLSessionCombineReceivable, URLSessionCombineMockReceivable
105+
where Requester.Output: URLSessionCombineReceivable {
106+
/// The underlying response.
107+
public var response: URLSessionCombineRequester.Response<Success> {
108+
switch result {
109+
case .success(let output):
110+
return .init(publisher: Just(output).setFailureType(to: Error.self))
111+
case .failure(let error):
112+
return .init(publisher: Fail(error: error))
113+
}
114+
}
115+
}
116+
103117
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
104118
extension Receivables.Pager: URLSessionCombineReceivable, URLSessionCombineMockReceivable
105119
where Child: URLSessionCombineReceivable {

Sources/Requests/Requester/Completion/URLSessionCompletionReceivable.swift

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import Foundation
99

10+
import Future
11+
1012
/// A `protocol` defining a mock `URLSessionCompletionReceivable`.
1113
public protocol URLSessionCompletionMockReceivable {
1214
// swiftlint:disable identifier_name
@@ -35,7 +37,9 @@ public extension URLSessionCompletionReceivable {
3537
/// - returns: `self`.
3638
func onResult(_ handler: @escaping (Result<Success, Error>) -> Void) -> URLSessionCompletionRequester.Response<Success> {
3739
let response = self.response
38-
response.handler.completion = handler
40+
response.future.on(success: { handler(.success($0)) },
41+
failure: { handler(.failure($0)) },
42+
completion: nil)
3943
return response
4044
}
4145

@@ -99,6 +103,14 @@ where Parent: URLSessionCompletionReceivable {
99103
}
100104
}
101105

106+
extension Receivables.Once: URLSessionCompletionReceivable, URLSessionCompletionMockReceivable
107+
where Requester.Output: URLSessionCompletionReceivable {
108+
/// The response.
109+
public var response: URLSessionCompletionRequester.Response<Success> {
110+
.init(task: nil, future: .init(result: result))
111+
}
112+
}
113+
102114
extension Receivables.Pager: URLSessionCompletionReceivable, URLSessionCompletionMockReceivable
103115
where Child: URLSessionCompletionReceivable {
104116
/// The underlying response.
@@ -131,20 +143,20 @@ extension Receivables.Switch: URLSessionCompletionReceivable, URLSessionCompleti
131143
where Parent: URLSessionCompletionReceivable, Child: URLSessionCompletionReceivable {
132144
/// The undelrying response.
133145
public var response: URLSessionCompletionRequester.Response<Success> {
134-
let handler = URLSessionCompletionRequester.Response<Success>.Handler()
146+
let promise = Promise<Success, Error>()
135147
let response = parent.response
136-
response.handler.completion = {
137-
switch $0 {
138-
case .success(let success):
148+
response.future.on(
149+
success: {
139150
do {
140-
try self.generator(success).onResult { handler.completion?($0) }.resume()
151+
try self.generator($0).onResult { promise.resolve(result: $0) }.resume()
141152
} catch {
142-
handler.completion?(.failure(error))
153+
promise.fail(error: error)
143154
}
144-
case .failure(let failure):
145-
handler.completion?(.failure(failure))
155+
},
156+
failure: {
157+
promise.fail(error: $0)
146158
}
147-
}
148-
return .init(value: response.value, handler: handler)
159+
)
160+
return .init(task: response.task, future: promise.future)
149161
}
150162
}

Sources/Requests/Requester/Completion/URLSessionCompletionRequester+Response.swift

Lines changed: 20 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,103 +7,62 @@
77

88
import Foundation
99

10+
import Future
11+
1012
public extension URLSessionCompletionRequester {
1113
/// A `struct` defining the output for a `URLSessionCompletionRequester`.
1214
struct Response<Success>: URLSessionCompletionReceivable {
13-
/// The underlying value.
14-
public let value: URLSessionCompletionValue
15-
/// The delegate.
16-
public var handler: Handler
15+
/// The underlying data task.
16+
public weak var task: URLSessionDataTask?
17+
/// The underlying future.
18+
public let future: Future<Success, Error>
1719

1820
/// The underlying response.
1921
public var response: URLSessionCompletionRequester.Response<Success> {
2022
self
2123
}
2224

23-
/// Init.
24-
///
25-
/// - parameters:
26-
/// - value: A valid `URLSessionCompletionValue`.
27-
/// - handler: A valid `Handler`.
28-
public init(value: URLSessionCompletionValue, handler: Handler) {
29-
self.value = value
30-
self.handler = handler
31-
}
32-
33-
/// Init.
25+
/// Resume the underlying task, if it exists.
3426
///
35-
/// - parameter request: A valid `Request`.
36-
public init(invalidRequest request: Request) {
37-
self.init(value: .invalidRequest(request), handler: .init())
27+
/// - returns: An optional `URLSessionDataTask`.
28+
@discardableResult
29+
public func resume() -> URLSessionDataTask? {
30+
task?.resume()
31+
return task
3832
}
3933

4034
/// Init.
4135
///
4236
/// - parameters:
43-
/// - task: A valid `URLSessionDataTask`.
44-
/// - handler: A valid `Handler`.
45-
public init(task: URLSessionDataTask, handler: Handler) {
46-
self.init(value: .task(task), handler: handler)
47-
}
48-
49-
@discardableResult
50-
/// Resume.
51-
///
52-
/// - returns: An optional `URLSessionDataTask`.
53-
public func resume() -> URLSessionDataTask? {
54-
switch value {
55-
case .invalidRequest(let request):
56-
// If there's no task you should still
57-
// notify it to the user.
58-
handler.completion?(.failure(Request.Error.invalidRequest(request)))
59-
return nil
60-
case .task(let task):
61-
task.resume()
62-
return task
63-
}
37+
/// - task: An optional `URLSessionDataTask`. Defaults to `nil`.
38+
/// - future: A valid `Future`.
39+
public init(task: URLSessionDataTask? = nil, future: Future<Success, Error>) {
40+
self.task = task
41+
self.future = future
6442
}
6543

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

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

8460
/// Flat map the current task.
8561
///
8662
/// - parameter mapper: A valid mapper.
8763
/// - returns: Some `URLSessionCompletionReceivable`.
8864
func chain(_ mapper: @escaping (Error) -> Result<Success, Error>) -> URLSessionCompletionRequester.Response<Success> {
89-
chain { (result: Result<Success, Error>) in result.flatMapError(mapper) }
90-
}
91-
}
92-
}
93-
94-
public extension URLSessionCompletionRequester.Response {
95-
/// A `class` defining a data task handler.
96-
final class Handler {
97-
/// The underlying completion.
98-
public var completion: ((Result<Success, Error>) -> Void)?
99-
100-
/// Init.
101-
///
102-
/// - parameters:
103-
/// - transformer: A valid mapper.
104-
/// - completion: An optional completion handler. Defaults to `nil`.
105-
public init(completion: ((Result<Success, Error>) -> Void)? = nil) {
106-
self.completion = completion
65+
.init(task: task, future: future.flatMapError { .init(result: mapper($0)) })
10766
}
10867
}
10968
}

Sources/Requests/Requester/Completion/URLSessionCompletionRequester.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import Foundation
99

10+
import Future
11+
1012
/// A `struct` defining a concrete implementation of `Requester`
1113
/// through _completion handlers_.
1214
public struct URLSessionCompletionRequester {
@@ -48,21 +50,20 @@ extension URLSessionCompletionRequester: Requester {
4850
/// - note: This is implemented as a `static` function to hide its definition. Rely on `request.prepare(with:)` instead.
4951
public static func prepare(_ endpoint: Request, with requester: Self) -> Output {
5052
guard let request = Request.request(from: endpoint) else {
51-
return .init(invalidRequest: endpoint)
53+
return .init(future: .init(error: Request.Error.invalidRequest(endpoint)))
5254
}
53-
let delegate = Output.Handler()
54-
requester.input.logger?.log(request)
55-
let task = requester.input.session.dataTask(with: request) { [weak delegate] data, response, error in
55+
let promise = Promise<Request.Response, Error>()
56+
let task = requester.input.session.dataTask(with: request) { data, response, error in
5657
if let error = error {
5758
requester.input.logger?.log(.failure(error))
58-
delegate?.completion?(.failure(error))
59+
promise.fail(error: error)
5960
} else if let data = data, let response = response {
6061
let result = Result<Request.Response, Error>.success(.init(data: data, response: response))
6162
requester.input.logger?.log(result)
62-
delegate?.completion?(result)
63+
promise.resolve(result: result)
6364
}
6465
}
65-
return .init(task: task, handler: delegate)
66+
return .init(task: task, future: promise.future)
6667
}
6768
}
6869

Sources/Requests/Requester/Completion/URLSessionCompletionValue.swift

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)