Skip to content

Commit 95f1eb2

Browse files
Merge pull request #10 from checkout/release/1.0.0
Merge release/1.0.0 into main
2 parents 1f92ca8 + 2b3bf65 commit 95f1eb2

File tree

13 files changed

+229
-20
lines changed

13 files changed

+229
-20
lines changed

.github/scripts/lintEditedFiles.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ git fetch origin main
55
echo "Fetched"
66

77
# Use a conditional to check if there are edited files
8-
if EDITED_FILES=$(git diff HEAD origin/main --name-only --diff-filter=d | grep "\.swift" | grep -v "\.swiftlint\.yml" | xargs echo | tr ' ' ','); then
8+
if EDITED_FILES=$(git diff HEAD origin/main --name-only --diff-filter=d | grep "\.swift" | grep -v "\.swiftlint\.yml" | xargs echo | tr ' ' ',' | sed 's/,/, /g'); then
99
echo "Got edited files"
1010
echo $EDITED_FILES
1111

.github/workflows/verify-pr.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ jobs:
3232
- name: Checkout repository
3333
uses: actions/checkout@v3
3434

35-
- name: Build
35+
- name: Build the Package
3636
run: |
3737
set -o pipefail && xcodebuild -scheme CheckoutNetwork -destination "platform=iOS Simulator,name=iPhone 14 Pro,OS=latest"
38+
39+
- name: Run Tests
40+
run: |
41+
set -o pipefail && xcodebuild -scheme CheckoutNetwork -destination "platform=iOS Simulator,name=iPhone 14 Pro,OS=latest" test

.swiftlint.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
included:
2+
- Source
3+
4+
excluded:
5+
- Tests
6+
7+
opt_in_rules:
8+
- array_init
9+
- closure_end_indentation
10+
- closure_spacing
11+
- collection_alignment
12+
- colon # promote to error
13+
- convenience_type
14+
- discouraged_object_literal
15+
- empty_collection_literal
16+
- empty_count
17+
- empty_string
18+
- enum_case_associated_values_count
19+
- fatal_error_message
20+
- first_where
21+
- force_unwrapping
22+
- implicitly_unwrapped_optional
23+
- last_where
24+
- legacy_random
25+
- literal_expression_end_indentation
26+
- multiline_arguments
27+
- multiline_function_chains
28+
- multiline_literal_brackets
29+
- multiline_parameters
30+
- multiline_parameters_brackets
31+
- operator_usage_whitespace
32+
- overridden_super_call
33+
- pattern_matching_keywords
34+
- prefer_self_type_over_type_of_self
35+
- redundant_nil_coalescing
36+
- redundant_type_annotation
37+
- strict_fileprivate
38+
- toggle_bool
39+
- trailing_closure
40+
- unneeded_parentheses_in_closure_argument
41+
- yoda_condition
42+
43+
disabled_rules:
44+
- multiline_parameters_brackets
45+
46+
analyzer_rules:
47+
- unused_import
48+
49+
line_length:
50+
warning: 160
51+
ignores_urls: true
52+
53+
identifier_name:
54+
excluded:
55+
- id

Package.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import PackageDescription
66
let package = Package(
77
name: "CheckoutNetwork",
88
platforms: [
9-
.iOS(.v11),
9+
.iOS(.v13),
1010
],
1111
products: [
1212
.library(
@@ -19,12 +19,14 @@ let package = Package(
1919
],
2020
targets: [
2121
.target(
22-
name: "CheckoutNetwork"),
22+
name: "CheckoutNetwork",
23+
path: "Sources/CheckoutNetwork"),
2324
.target(
2425
name: "CheckoutNetworkFakeClient",
2526
dependencies: ["CheckoutNetwork"]),
2627
.testTarget(
2728
name: "CheckoutNetworkTests",
28-
dependencies: ["CheckoutNetwork"]),
29+
dependencies: ["CheckoutNetwork"],
30+
path: "Tests"),
2931
]
3032
)

Sources/CheckoutNetwork/CheckoutClientInterface.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ public protocol CheckoutClientInterface {
1919
/// Create, customise and run a request with the given configuration, calling the completion handler once completed
2020
func runRequest<T: Decodable>(with configuration: RequestConfiguration,
2121
completionHandler: @escaping CompletionHandler<T>)
22-
22+
23+
/// Async wrapper of func runRequest(_:_:) with CompletionHandler<T>
24+
func runRequest<T: Decodable>(with configuration: RequestConfiguration) async throws -> T
25+
2326
/// Create, customise and run a request with the given configuration, calling the completion handler once completed
2427
func runRequest(with configuration: RequestConfiguration,
2528
completionHandler: @escaping NoDataResponseCompletionHandler)

Sources/CheckoutNetwork/CheckoutNetworkClient.swift

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,17 @@ public class CheckoutNetworkClient: CheckoutClientInterface {
3535
completionHandler(.failure(error))
3636
return
3737
}
38-
if let responseError = self.getErrorFromResponse(response) {
39-
completionHandler(.failure(responseError))
40-
return
41-
}
4238

4339
guard let data = data else {
4440
completionHandler(.failure(CheckoutNetworkError.noDataResponseReceived))
4541
return
4642
}
43+
44+
if let responseError = self.getErrorFromResponse(response, data: data) {
45+
completionHandler(.failure(responseError))
46+
return
47+
}
48+
4749
do {
4850
let dataResponse = try JSONDecoder().decode(T.self, from: data)
4951
completionHandler(.success(dataResponse))
@@ -55,7 +57,20 @@ public class CheckoutNetworkClient: CheckoutClientInterface {
5557
task.resume()
5658
}
5759
}
58-
60+
61+
public func runRequest<T: Decodable>(with configuration: RequestConfiguration) async throws -> T {
62+
return try await withCheckedThrowingContinuation { continuation in
63+
runRequest(with: configuration) { (result: Result<T, Error>) in
64+
switch result {
65+
case .success(let response):
66+
continuation.resume(returning: response)
67+
case .failure(let error):
68+
continuation.resume(throwing: error)
69+
}
70+
}
71+
}
72+
}
73+
5974
public func runRequest(with configuration: RequestConfiguration,
6075
completionHandler: @escaping NoDataResponseCompletionHandler) {
6176
taskQueue.sync {
@@ -68,7 +83,7 @@ public class CheckoutNetworkClient: CheckoutClientInterface {
6883
completionHandler(error)
6984
return
7085
}
71-
if let responseError = self.getErrorFromResponse(response) {
86+
if let responseError = self.getErrorFromResponse(response, data: nil) {
7287
completionHandler(responseError)
7388
return
7489
}
@@ -87,11 +102,20 @@ public class CheckoutNetworkClient: CheckoutClientInterface {
87102
return taskID
88103
}
89104

90-
private func getErrorFromResponse(_ response: URLResponse?) -> Error? {
91-
guard let response = response as? HTTPURLResponse else {
105+
private func getErrorFromResponse(_ response: URLResponse?, data: Data?) -> Error? {
106+
guard let response = response as? HTTPURLResponse else {
92107
return CheckoutNetworkError.unexpectedResponseCode(code: 0)
93108
}
94109

110+
guard response.statusCode != 422 else {
111+
do {
112+
let errorReason = try JSONDecoder().decode(ErrorReason.self, from: data ?? Data())
113+
return CheckoutNetworkError.invalidData(reason: errorReason)
114+
} catch {
115+
return CheckoutNetworkError.invalidDataResponseReceivedWithNoData
116+
}
117+
}
118+
95119
guard response.statusCode >= 200,
96120
response.statusCode < 300 else {
97121
return CheckoutNetworkError.unexpectedResponseCode(code: response.statusCode)

Sources/CheckoutNetwork/CheckoutNetworkError.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,11 @@ public enum CheckoutNetworkError: Error, Equatable {
1717

1818
/// Network call and completion appear valid but no data was returned making the parsing impossible. Use different call if no data is expected
1919
case noDataResponseReceived
20+
21+
/// Network response returned with HTTP Code 422
22+
case invalidData(reason: ErrorReason)
23+
24+
25+
/// HTTP code 422 received with no meaningful data alongside
26+
case invalidDataResponseReceivedWithNoData
2027
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// ErrorReason.swift
3+
//
4+
//
5+
// Created by Okhan Okbay on 18/01/2024.
6+
//
7+
8+
import Foundation
9+
10+
public struct ErrorReason: Decodable, Equatable {
11+
public let requestID: String
12+
public let errorType: String
13+
public let errorCodes: [String]
14+
15+
enum CodingKeys: String, CodingKey {
16+
case requestID = "request_id"
17+
case errorType = "error_type"
18+
case errorCodes = "error_codes"
19+
}
20+
}

Sources/CheckoutNetworkFakeClient/CheckoutNetworkFakeClient.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,23 @@ import Foundation
99
import CheckoutNetwork
1010

1111
final public class CheckoutNetworkFakeClient: CheckoutClientInterface {
12-
1312
public var calledRequests: [(config: RequestConfiguration, completion: Any)] = []
14-
13+
14+
public var calledAsyncRequests: [RequestConfiguration] = []
15+
public var dataToBeReturned: Decodable!
16+
1517
public func runRequest<T: Decodable>(with configuration: RequestConfiguration,
1618
completionHandler: @escaping CompletionHandler<T>) {
1719
calledRequests.append((config: configuration, completion: completionHandler))
1820
}
19-
21+
22+
public func runRequest<T: Decodable>(with configuration: CheckoutNetwork.RequestConfiguration) async throws -> T {
23+
calledAsyncRequests.append(configuration)
24+
return dataToBeReturned as! T
25+
}
26+
2027
public func runRequest(with configuration: RequestConfiguration,
2128
completionHandler: @escaping NoDataResponseCompletionHandler) {
2229
calledRequests.append((configuration, completionHandler))
2330
}
24-
2531
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// AsyncWrapperTests.swift
3+
//
4+
//
5+
// Created by Okhan Okbay on 18/01/2024.
6+
//
7+
8+
@testable import CheckoutNetwork
9+
import XCTest
10+
11+
final class AsyncWrapperTests: XCTestCase {
12+
13+
func test_whenRunRequestReturnsData_ThenAsyncRunRequestPropagatesIt() async throws {
14+
let fakeSession = FakeSession()
15+
let fakeDataTask = FakeDataTask()
16+
fakeSession.calledDataTasksReturn = fakeDataTask
17+
let client = CheckoutNetworkClientSpy(session: fakeSession)
18+
let testConfig = try! RequestConfiguration(path: FakePath.testServices)
19+
20+
let expectedResult = FakeObject(id: "some response")
21+
client.expectedResult = expectedResult
22+
client.expectedError = nil
23+
let result: FakeObject = try await client.runRequest(with: testConfig)
24+
XCTAssertEqual(client.configuration.request, testConfig.request)
25+
XCTAssertEqual(client.runRequestCallCount, 1)
26+
XCTAssertEqual(result, expectedResult)
27+
}
28+
29+
func test_whenRunRequestReturnsError_ThenAsyncRunRequestPropagatesIt() async throws {
30+
let fakeSession = FakeSession()
31+
let fakeDataTask = FakeDataTask()
32+
fakeSession.calledDataTasksReturn = fakeDataTask
33+
let client = CheckoutNetworkClientSpy(session: fakeSession)
34+
let testConfig = try! RequestConfiguration(path: FakePath.testServices)
35+
36+
let expectedError = FakeError.someError
37+
client.expectedResult = nil
38+
client.expectedError = expectedError
39+
40+
do {
41+
let _: FakeObject = try await client.runRequest(with: testConfig)
42+
XCTFail("An error was expected to be thrown")
43+
} catch let error as FakeError {
44+
XCTAssertEqual(client.configuration.request, testConfig.request)
45+
XCTAssertEqual(client.runRequestCallCount, 1)
46+
XCTAssertEqual(error, expectedError)
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)