Skip to content

Commit aa8a703

Browse files
committed
Merge branch 'develop' into issue/2042-m2-feature-switch
# Conflicts: # WooCommerce/WooCommerce.xcodeproj/project.pbxproj
2 parents 7a35927 + b41960e commit aa8a703

File tree

22 files changed

+359
-101
lines changed

22 files changed

+359
-101
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
5726F7312460A8510031CAAC /* ProductImage+Copiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5726F7302460A8510031CAAC /* ProductImage+Copiable.swift */; };
7070
5726F7342460A8F00031CAAC /* CopiableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5726F7332460A8F00031CAAC /* CopiableTests.swift */; };
7171
57BE08D82409B63800F6DCED /* reviews-missing-avatar-urls.json in Resources */ = {isa = PBXBuildFile; fileRef = 57BE08D72409B63700F6DCED /* reviews-missing-avatar-urls.json */; };
72+
57E8FED3246616AC0057CD68 /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E8FED2246616AC0057CD68 /* Result+Extensions.swift */; };
7273
6647C0161DAC6AB6570C53A7 /* Pods_Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */; };
7374
6856DE98F90AC6E4743D18CA /* XCTestCase+Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6856D1228ADBC14D8202E49D /* XCTestCase+Wait.swift */; };
7475
74002D6A2118B26100A63C19 /* SiteVisitStatsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */; };
@@ -401,6 +402,7 @@
401402
5726F7332460A8F00031CAAC /* CopiableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopiableTests.swift; sourceTree = "<group>"; };
402403
573B448C242422DB00E71ADC /* orders-load-all-2.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "orders-load-all-2.json"; sourceTree = "<group>"; };
403404
57BE08D72409B63700F6DCED /* reviews-missing-avatar-urls.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reviews-missing-avatar-urls.json"; sourceTree = "<group>"; };
405+
57E8FED2246616AC0057CD68 /* Result+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extensions.swift"; sourceTree = "<group>"; };
404406
6856D1228ADBC14D8202E49D /* XCTestCase+Wait.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Wait.swift"; sourceTree = "<group>"; };
405407
69314EDE650855CAF927057E /* Pods_NetworkingTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NetworkingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
406408
74002D692118B26000A63C19 /* SiteVisitStatsMapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteVisitStatsMapperTests.swift; sourceTree = "<group>"; };
@@ -1196,6 +1198,7 @@
11961198
B5C151BA217EC34100C7BDC1 /* KeyedDecodingContainer+Woo.swift */,
11971199
021C7BF623863D1800A3BCBD /* Encodable+Serialization.swift */,
11981200
02BDB83423EA98C800BCC63E /* String+HTML.swift */,
1201+
57E8FED2246616AC0057CD68 /* Result+Extensions.swift */,
11991202
);
12001203
path = Extensions;
12011204
sourceTree = "<group>";
@@ -1592,6 +1595,7 @@
15921595
450106852399A7CB00E24722 /* TaxClass.swift in Sources */,
15931596
CE12FBD9221F3A6F00C59248 /* OrderStatus.swift in Sources */,
15941597
7426CA1121AF30BD004E9FFC /* SiteAPIMapper.swift in Sources */,
1598+
57E8FED3246616AC0057CD68 /* Result+Extensions.swift in Sources */,
15951599
020D07BC23D856BF00FD9580 /* MediaRemote.swift in Sources */,
15961600
D823D91022377B4F00C90817 /* ShipmentTrackingProviderGroup.swift in Sources */,
15971601
743E84EC22171F4600FAC9D7 /* ShipmentTracking.swift in Sources */,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
import Foundation
3+
4+
extension Result {
5+
/// Indicates whether `self` is a `.success`.
6+
///
7+
public var isSuccess: Bool {
8+
guard case .success = self else {
9+
return false
10+
}
11+
return true
12+
}
13+
14+
/// Indicates whether `self` is a `.failure`.
15+
///
16+
public var isFailure: Bool {
17+
!isSuccess
18+
}
19+
20+
/// Returns the value of `.failure()` if `self` is a failure.
21+
///
22+
public var failure: Failure? {
23+
guard case let .failure(error) = self else {
24+
return nil
25+
}
26+
27+
return error
28+
}
29+
}

Networking/Networking/Network/AlamofireNetwork.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,23 @@ public class AlamofireNetwork: Network {
4747
}
4848
}
4949

50+
/// Executes the specified Network Request. Upon completion, the payload will be sent back to the caller as a Data instance.
51+
///
52+
/// - Important:
53+
/// - Authentication Headers will be injected, based on the Network's Credentials.
54+
///
55+
/// - Parameters:
56+
/// - request: Request that should be performed.
57+
/// - completion: Closure to be executed upon completion.
58+
///
59+
public func responseData(for request: URLRequestConvertible, completion: @escaping (Swift.Result<Data, Error>) -> Void) {
60+
let authenticated = AuthenticatedRequest(credentials: credentials, request: request)
61+
62+
Alamofire.request(authenticated).responseData { response in
63+
completion(response.result.toSwiftResult())
64+
}
65+
}
66+
5067
public func uploadMultipartFormData(multipartFormData: @escaping (MultipartFormData) -> Void,
5168
to request: URLRequestConvertible,
5269
completion: @escaping (Data?, Error?) -> Void) {
@@ -93,3 +110,18 @@ private extension Alamofire.DataResponse {
93110
}
94111
}
95112
}
113+
114+
// MARK: - Swift.Result Conversion
115+
116+
private extension Alamofire.Result {
117+
/// Convert this `Alamofire.Result` to a `Swift.Result`.
118+
///
119+
func toSwiftResult() -> Swift.Result<Value, Error> {
120+
switch self {
121+
case .success(let value):
122+
return .success(value)
123+
case .failure(let error):
124+
return .failure(error)
125+
}
126+
}
127+
}

Networking/Networking/Network/MockupNetwork.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,33 @@ class MockupNetwork: Network {
5252
/// Otherwise, an error will be relayed back (.notFound!).
5353
///
5454
func responseData(for request: URLRequestConvertible, completion: @escaping (Data?, Error?) -> Void) {
55+
responseData(for: request) { result in
56+
switch result {
57+
case .success(let data):
58+
completion(data, nil)
59+
case .failure(let error):
60+
completion(nil, error)
61+
}
62+
}
63+
}
64+
65+
/// Whenever the Request's URL matches any of the "Mocked Up Patterns", we'll return the
66+
/// specified response file, loaded as *Data*. Otherwise, an error will be relayed back (.notFound!).
67+
///
68+
func responseData(for request: URLRequestConvertible, completion: @escaping (Swift.Result<Data, Error>) -> Void) {
5569
requestsForResponseData.append(request)
5670

5771
if let error = error(for: request) {
58-
completion(nil, error)
72+
completion(.failure(error))
5973
return
6074
}
6175

6276
guard let name = filename(for: request), let data = Loader.contentsOf(name) else {
63-
completion(nil, NetworkError.notFound)
77+
completion(.failure(NetworkError.notFound))
6478
return
6579
}
6680

67-
completion(data, nil)
81+
completion(.success(data))
6882
}
6983

7084
func uploadMultipartFormData(multipartFormData: @escaping (MultipartFormData) -> Void,

Networking/Networking/Network/Network.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ public protocol Network {
3333
///
3434
func responseData(for request: URLRequestConvertible, completion: @escaping (Data?, Error?) -> Void)
3535

36+
/// Executes the specified Network Request. Upon completion, the payload will be sent back to
37+
/// the caller as a Data instance.
38+
///
39+
/// - Parameters:
40+
/// - request: Request that should be performed.
41+
/// - completion: Closure to be executed upon completion.
42+
///
43+
func responseData(for request: URLRequestConvertible,
44+
completion: @escaping (Swift.Result<Data, Error>) -> Void)
45+
3646
/// Executes the specified Network Request for file uploads. Upon completion, the payload will be sent back to the caller as a Data instance.
3747
///
3848
/// - Parameters:

Networking/Networking/Network/NullNetwork.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ public final class NullNetwork: Network {
1111

1212
public func responseData(for request: URLRequestConvertible, completion: @escaping (Data?, Error?) -> Void) { }
1313

14+
public func responseData(for request: URLRequestConvertible,
15+
completion: @escaping (Swift.Result<Data, Error>) -> Void) {
16+
17+
}
18+
1419
public func uploadMultipartFormData(multipartFormData: @escaping (MultipartFormData) -> Void,
1520
to request: URLRequestConvertible,
1621
completion: @escaping (Data?, Error?) -> Void) { }

Networking/Networking/Remote/OrdersRemote.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import Foundation
2-
import Alamofire
3-
42

53
/// Order: Remote Endpoints
64
///
@@ -22,7 +20,7 @@ public class OrdersRemote: Remote {
2220
before: Date? = nil,
2321
pageNumber: Int = Defaults.pageNumber,
2422
pageSize: Int = Defaults.pageSize,
25-
completion: @escaping ([Order]?, Error?) -> Void) {
23+
completion: @escaping (Result<[Order], Error>) -> Void) {
2624
let utcDateFormatter = DateFormatter.Defaults.iso8601
2725

2826
let parameters: [String: Any] = {

Networking/Networking/Remote/Remote.swift

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Foundation
2-
import Alamofire
3-
2+
import protocol Alamofire.URLRequestConvertible
43

54
/// Represents a collection of Remote Endpoints
65
///
@@ -58,7 +57,7 @@ public class Remote {
5857
///
5958
/// - Parameters:
6059
/// - request: Request that should be performed.
61-
/// - mapper: Mapper entitity that will be used to attempt to parse the Backend's Response.
60+
/// - mapper: Mapper entity that will be used to attempt to parse the Backend's Response.
6261
/// - completion: Closure to be executed upon completion.
6362
///
6463
func enqueue<M: Mapper>(_ request: URLRequestConvertible, mapper: M, completion: @escaping (M.Output?, Error?) -> Void) {
@@ -84,6 +83,40 @@ public class Remote {
8483
}
8584
}
8685

86+
/// Enqueues the specified Network Request.
87+
///
88+
/// - Important:
89+
/// - Parsing will be performed by the Mapper.
90+
///
91+
/// - Parameters:
92+
/// - request: Request that should be performed.
93+
/// - mapper: Mapper entity that will be used to attempt to parse the Backend's Response.
94+
/// - completion: Closure to be executed upon completion.
95+
///
96+
func enqueue<M: Mapper>(_ request: URLRequestConvertible, mapper: M,
97+
completion: @escaping (Result<M.Output, Error>) -> Void) {
98+
network.responseData(for: request) { result in
99+
switch result {
100+
case .success(let data):
101+
if let dotcomError = DotcomValidator.error(from: data) {
102+
self.dotcomErrorWasReceived(error: dotcomError, for: request)
103+
completion(.failure(dotcomError))
104+
return
105+
}
106+
107+
do {
108+
let parsed = try mapper.map(response: data)
109+
completion(.success(parsed))
110+
} catch {
111+
DDLogError("<> Mapping Error: \(error)")
112+
completion(.failure(error))
113+
}
114+
case .failure(let error):
115+
completion(.failure(error))
116+
}
117+
}
118+
}
119+
87120
/// Enqueues the specified Network Request for upload with multipart form data encoding.
88121
///
89122
/// - Important:

Networking/NetworkingTests/Remote/OrdersRemoteTests.swift

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,43 +36,49 @@ final class OrdersRemoteTests: XCTestCase {
3636
network.removeAllSimulatedResponses()
3737
}
3838

39-
4039
// MARK: - Load All Orders Tests
4140

4241
/// Verifies that loadAllOrders properly parses the `orders-load-all` sample response.
4342
///
44-
func testLoadAllOrdersProperlyReturnsParsedOrders() {
43+
func testLoadAllOrdersProperlyReturnsParsedOrders() throws {
44+
// Given
4545
let remote = OrdersRemote(network: network)
46-
let expectation = self.expectation(description: "Load All Orders")
4746

4847
network.simulateResponse(requestUrlSuffix: "orders", filename: "orders-load-all")
4948

50-
remote.loadAllOrders(for: sampleSiteID) { orders, error in
51-
XCTAssertNil(error)
52-
XCTAssertNotNil(orders)
53-
XCTAssert(orders!.count == 4)
54-
expectation.fulfill()
49+
// When
50+
var result: Result<[Order], Error>?
51+
waitForExpectation { expectation in
52+
remote.loadAllOrders(for: sampleSiteID) { aResult in
53+
result = aResult
54+
expectation.fulfill()
55+
}
5556
}
5657

57-
wait(for: [expectation], timeout: Constants.expectationTimeout)
58+
// Then
59+
let orders = try XCTUnwrap(result?.get())
60+
XCTAssert(orders.count == 4)
5861
}
5962

6063
/// Verifies that loadAllOrders properly relays Networking Layer errors.
6164
///
62-
func testLoadAllOrdersProperlyRelaysNetwokingErrors() {
65+
func testLoadAllOrdersProperlyRelaysNetwokingErrors() throws {
66+
// Given
6367
let remote = OrdersRemote(network: network)
64-
let expectation = self.expectation(description: "Load All Orders")
6568

66-
remote.loadAllOrders(for: sampleSiteID) { orders, error in
67-
XCTAssertNil(orders)
68-
XCTAssertNotNil(error)
69-
expectation.fulfill()
69+
// When
70+
var result: Result<[Order], Error>?
71+
waitForExpectation { expectation in
72+
remote.loadAllOrders(for: sampleSiteID) { aResult in
73+
result = aResult
74+
expectation.fulfill()
75+
}
7076
}
7177

72-
wait(for: [expectation], timeout: Constants.expectationTimeout)
78+
// Then
79+
XCTAssertTrue(try XCTUnwrap(result).isFailure)
7380
}
7481

75-
7682
// MARK: - Load Order Tests
7783

7884
/// Verifies that loadOrder properly parses the `order` sample response.

0 commit comments

Comments
 (0)