Created
December 24, 2018 06:51
-
-
Save hemangshah/77a2f15db01bc9d9e5aa76f60427fc7b to your computer and use it in GitHub Desktop.
Trying to implement the best way to handle custom errors through in a login flow (as an example).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
// ---- [LoginManager.swift] ---- starts | |
enum LoginError: Error { | |
case minUserNameLength(String), minPasswordLength(String), invalidUserName(String), invalidPassword(String) | |
} | |
enum LoginResult { | |
case result([String: Any]) | |
} | |
struct LoginManager { | |
static func validate(user: String, pass: String, completion: (LoginResult) -> Void ) throws { | |
guard (!user.isEmpty && user.count > 8) else { throw LoginError.minUserNameLength("A minimum username length is >= 8 characters.") } | |
guard (!pass.isEmpty && pass.count > 8) else { throw LoginError.minPasswordLength("A minimum password length is >= 8 characters.") } | |
//Call Login API to confirm the credentials with provided userName and password values. | |
//Here we're checking locally for testing purpose. | |
if user != "iosdevfromthenorthpole" { throw LoginError.invalidUserName("Invalid Username.") } | |
if pass != "polardbear" { throw LoginError.invalidPassword("Invalid Password.") } | |
//The actual result will be passed instead of below static result. | |
completion(LoginResult.result(["userId": 1, "email": "[email protected]"])) | |
} | |
static func handle(error: LoginError, completion: (_ title: String, _ message: String) -> Void) { | |
//Note that all associated values are of the same type for the LoginError cases. | |
//Can we write it in more appropriate way? | |
let title = "Login failed." | |
switch error { | |
case .minUserNameLength(let errorMessage): | |
completion(title, errorMessage) | |
case .minPasswordLength(let errorMessage): | |
completion(title, errorMessage) | |
case .invalidUserName(let errorMessage): | |
completion(title, errorMessage) | |
case .invalidPassword(let errorMessage): | |
completion(title, errorMessage) | |
} | |
} | |
} | |
// ---- [LoginManager.swift] ---- ends | |
// ---- [LoginViewController.swift] ---- starts | |
//Confirming the user credentials when user taps on the "Login" button. | |
do { | |
try LoginManager.validate(user: "iosdevfromthenorthpole", pass: "polardbear", completion: { (loginResult) in | |
switch loginResult { | |
case .result (let result): | |
print("userId: ", result["userId"] ?? "Not available.") | |
print("email: ", result["email"] ?? "Not available.") | |
} | |
}) | |
} catch let error as LoginError { | |
LoginManager.handle(error: error) { (title, message) in | |
//Show an alert with title and message to the user. | |
print(title + " " + message) | |
} | |
} | |
// ---- [LoginViewController.swift] ---- ends |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi,I usually use 2 enums to keep things simple when testing this scenario, so i'd love to share it and would love if someone would give a feedback on it too,
1st, i validate the required fields that we gather from the UI to send to our backend and some other validations too through a custom validator class
first i'll declare an enum of Validations Error
then I declare a protocol called validator which has only one function that throws errors
and we can implement it in a class like this
or a class for let's say an email field like this
so in my interactor, viewModel, worker whichever layer u do ur logic in
i have 3 functions that i use to handle any logic and i override them in the children of my BaseInteractor that looks like this
then if am validating the input of login i do it this way
then when am using this class
i just call
loginInteractor.performARequest()
when i do this line, the validations occurs and if there is something that occurred and failed in my validations, it wouldn't go in the catch block and then i can show the user the errorMessage directly in a popup or an alert
so, what if the BE returns his own custom error?
i usually use this approach when creating an enum out of a response JSON which contains an error message or an error flag or an error case (ex.: BRANCH_NOT_AVAILABLE_NOW)
then i make an extension (or just write the extension in the enum declaration, however u like)
so those are my two approaches that i use mainly when doing validations or building an errorMessage out of a response, sometimes not
anyways, id really love to hear someone's feedback on this approach (also, i got a feedback from a senior friend of mine that this was an overkill, but IMO i think this is just a separation of concerns to make life stupid simple when testing my code)