Skip to content

Commit

Permalink
Merge pull request #11 from venmo/swift5.0
Browse files Browse the repository at this point in the history
Swift 5.0
  • Loading branch information
dgallagher-venmo authored Feb 20, 2019
2 parents 76b232d + 362ab23 commit b586fd1
Show file tree
Hide file tree
Showing 12 changed files with 484 additions and 460 deletions.
103 changes: 103 additions & 0 deletions ERRORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Errors

## Handling

It is up to the consumer of QuizTrain to handle all errors. However `ObjectAPI` will handle 429 Too Many Request (rate limit reached) errors automatically unless you set `.handle429TooManyRequestErrors` to `false`.

Errors are defined here:

- [API.swift](QuizTrain/Network/API.swift) in the *Errors* section.
- [ObjectAPI.swift](QuizTrain/Network/ObjectAPI.swift) in the *Errors* section.

You can handle errors two ways:

1. Simply by using `error.debugDescription` to print a rich description of an error.
- *Provided by errors conforming to `CustomDebugStringConvertible`.*
2. Advanced by `switch`'ing on an error.

Simple is best for debugging and logging. Advanced is best for everything else.

All API and ObjectAPI errors conform to [`CustomDebugStringConvertible`](https://developer.apple.com/documentation/swift/customdebugstringconvertible). If these errors print a `URLRequest` through this protocol then its `AUTHORIZATION` header will be stripped to avoid exposing your TestRail credentials.

### Example

This shows both simple and advanced error handling when adding a new Case to a Section.

#### Setup

let newCase = NewCase(estimate: nil, milestoneId: nil, priorityId: nil, refs: nil, templateId: nil, title: "New Case Title", typeId: nil, customFields: nil)

#### Simple

objectAPI.addCase(newCase, toSectionWithId: 5) { (outcome) in
switch outcome {
case .failure(let error):
print(error.debugDescription)
case .success(let `case`):
print(`case`.title) // Do something with the newly created `case`.
}
}

#### Advanced

objectAPI.addCase(newCase, toSectionWithId: 5) { (outcome) in
switch outcome {
case .failure(let error):
switch error {
case .apiError(let apiError): // API.RequestError
switch apiError {
case .error(let request, let error):
print(request)
print(error)
case .invalidResponse(let request, let response):
print(request)
print(response)
case .nilResponse(let request):
print(request)
}
case .dataProcessingError(let dataProcessingError): // ObjectAPI.DataProcessingError
switch dataProcessingError {
case .couldNotConvertDataToJSON(let data, let error):
print(data)
print(error)
case .couldNotDeserializeFromJSON(let objectType, let json):
print(objectType)
print(json)
case .invalidJSONFormat(let json):
print(json)
}
case .objectConversionError(let objectConversionError): // ObjectAPI.ObjectConversionError
switch objectConversionError {
case .couldNotConvertObjectsToData(let objects, let json, let error):
print(objects)
print(json)
print(error)
case .couldNotConvertObjectToData(let object, let json, let error):
print(object)
print(json)
print(error)
}
case .statusCodeError(let statusCodeError): // ObjectAPI.StatusCodeError
switch statusCodeError {
case .clientError(let clientError): // ObjectAPI.ClientError
print(clientError.message)
print(clientError.statusCode)
print(clientError.requestResult.request)
print(clientError.requestResult.response)
print(clientError.requestResult.data)
case .otherError(let otherError): // API.RequestResult
print(otherError.request)
print(otherError.response)
print(otherError.data)
case .serverError(let serverError): // ObjectAPI.ServerError
print(serverError.message)
print(serverError.statusCode)
print(serverError.requestResult.request)
print(serverError.requestResult.response)
print(serverError.requestResult.data)
}
}
case .success(let `case`):
print(`case`.title) // Do something with the newly created `case`.
}
}
9 changes: 9 additions & 0 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -331,13 +331,16 @@
TargetAttributes = {
FED7AF2F207EA0F400169889 = {
CreatedOnToolsVersion = 9.3;
LastSwiftMigration = 1020;
};
FED7AF43207EA0F500169889 = {
CreatedOnToolsVersion = 9.3;
LastSwiftMigration = 1020;
TestTargetID = FED7AF2F207EA0F400169889;
};
FED7AF4E207EA0F500169889 = {
CreatedOnToolsVersion = 9.3;
LastSwiftMigration = 1020;
TestTargetID = FED7AF2F207EA0F400169889;
};
};
Expand Down Expand Up @@ -644,6 +647,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = QuizTrain.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
Expand All @@ -660,6 +664,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = QuizTrain.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
Expand All @@ -678,6 +683,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = QuizTrain.ExampleTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example";
};
Expand All @@ -697,6 +703,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = QuizTrain.ExampleTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example";
};
Expand All @@ -715,6 +722,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = QuizTrain.ExampleUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Example;
};
Expand All @@ -733,6 +741,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = QuizTrain.ExampleUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Example;
};
Expand Down
2 changes: 1 addition & 1 deletion Example/Example/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}

Expand Down
21 changes: 7 additions & 14 deletions Example/Shared/QuizTrainManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@ final class QuizTrainManager: NSObject {
super.init()
}

// MARK: - Outcome

enum Outcome<Succeeded, Failed: Error> {
case succeeded(Succeeded)
case failed(Failed)
}

// MARK: - Testing

enum Result: String {
Expand Down Expand Up @@ -120,7 +113,7 @@ final class QuizTrainManager: NSObject {
var completed = [NewCaseResults.Result]()
for caseId in caseIds {
guard let complete = started.filter({ $0.caseId == caseId }).first,
let index = started.index(of: complete) else {
let index = started.firstIndex(of: complete) else {
fatalError("You cannot complete caseId \(caseId) because it has not been started.")
}
completed.append(complete)
Expand Down Expand Up @@ -279,10 +272,10 @@ extension QuizTrainManager {
group.enter()
objectAPI.addPlan(newPlan, to: project.project) { (outcome) in
switch outcome {
case .failed(let error):
case .failure(let error):
print("Plan creation failed: \(error.debugDescription)")
return
case .succeeded(let aPlan):
case .success(let aPlan):
plan = aPlan
}
group.leave()
Expand Down Expand Up @@ -322,9 +315,9 @@ extension QuizTrainManager {
group.enter()
objectAPI.addResultsForCases(newCaseResults, to: run) { (outcome) in
switch outcome {
case .failed(let error):
case .failure(let error):
errors.append(error)
case .succeeded(let someResults):
case .success(let someResults):
results.append(contentsOf: someResults)
}
group.leave()
Expand All @@ -348,9 +341,9 @@ extension QuizTrainManager {
group.enter()
objectAPI.closePlan(plan) { (outcome) in
switch outcome {
case .failed(let error):
case .failure(let error):
print("Closing plan failed: \(error)")
case .succeeded(_):
case .success(_):
break
}
group.leave()
Expand Down
45 changes: 20 additions & 25 deletions Example/Shared/QuizTrainProject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,10 @@ struct QuizTrainProject {

// MARK: - Creation

enum Outcome<Succeeded, Failed: Error> {
case succeeded(Succeeded)
case failed(Failed)
}

/*
Asynchronously calls the ObjectAPI and populates a QuizTrainProject.
*/
static func populatedProject(forProjectId projectId: QuizTrain.Project.Id, objectAPI: QuizTrain.ObjectAPI, completionHandler: @escaping (Outcome<QuizTrainProject, QuizTrain.ObjectAPI.GetError>) -> Void) {
static func populatedProject(forProjectId projectId: QuizTrain.Project.Id, objectAPI: QuizTrain.ObjectAPI, completionHandler: @escaping (Swift.Result<QuizTrainProject, QuizTrain.ObjectAPI.GetError>) -> Void) {
DispatchQueue.global().async {

let group = DispatchGroup()
Expand Down Expand Up @@ -183,37 +178,37 @@ struct QuizTrainProject {

let project: QuizTrain.Project
switch projectOutcome! {
case .failed(let error):
completionHandler(.failed(error))
case .failure(let error):
completionHandler(.failure(error))
return
case .succeeded(let aProject):
case .success(let aProject):
project = aProject
}

let suites: [QuizTrain.Suite]
switch suitesOutcome! {
case .failed(let error):
completionHandler(.failed(error))
case .failure(let error):
completionHandler(.failure(error))
return
case .succeeded(let someSuites):
case .success(let someSuites):
suites = someSuites
}

let statuses: [QuizTrain.Status]
switch statusesOutcome! {
case .failed(let error):
completionHandler(.failed(error))
case .failure(let error):
completionHandler(.failure(error))
return
case .succeeded(let someStatuses):
case .success(let someStatuses):
statuses = someStatuses
}

let users: [QuizTrain.User]
switch usersOutcome! {
case .failed(let error):
completionHandler(.failed(error))
case .failure(let error):
completionHandler(.failure(error))
return
case .succeeded(let someUsers):
case .success(let someUsers):
users = someUsers
}

Expand Down Expand Up @@ -242,29 +237,29 @@ struct QuizTrainProject {
var cases = [QuizTrain.Case]()
for casesOutcome in casesOutcomes {
switch casesOutcome {
case .failed(let error):
completionHandler(.failed(error))
case .failure(let error):
completionHandler(.failure(error))
return
case .succeeded(let someCases):
case .success(let someCases):
cases.append(contentsOf: someCases)
}
}

var sections = [QuizTrain.Section]()
for sectionsOutcome in sectionsOutcomes {
switch sectionsOutcome {
case .failed(let error):
completionHandler(.failed(error))
case .failure(let error):
completionHandler(.failure(error))
return
case .succeeded(let someSections):
case .success(let someSections):
sections.append(contentsOf: someSections)
}
}

// Assemble the QuizTrainProject with all data.

let quizTrainProject = QuizTrainProject(project: project, suites: suites, sections: sections, cases: cases, statuses: statuses, users: users)
completionHandler(.succeeded(quizTrainProject))
completionHandler(.success(quizTrainProject))
}
}

Expand Down
4 changes: 2 additions & 2 deletions Example/Shared/TestManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ final class TestManager: NSObject {
#error("Replace the projectId below with one from your TestRail instance. Then comment out this macro.")
QuizTrainProject.populatedProject(forProjectId: 99999, objectAPI: objectAPI) { (outcome) in
switch outcome {
case .failed(let error):
case .failure(let error):
print("QuizTrainManager setup failed: \(error)")
fatalError(error.localizedDescription)
case .succeeded(let project):
case .success(let project):
quizTrainManager = QuizTrainManager(objectAPI: objectAPI, project: project)
}
group.leave()
Expand Down
Loading

0 comments on commit b586fd1

Please sign in to comment.