I am having trouble figuring out the syntax for accessing the raw value of an enum. The code below should clarify what I am trying to do.
enum Result<T, U> {
case Success(T)
case Failure(U)
}
struct MyError: ErrorType {
var status: Int = 0
var message: String = "An undefined error has occured."
init(status: Int, message: String) {
self.status = status
self.message = message
}
}
let goodResult: Result<String, MyError> = .Success("Some example data")
let badResult: Result<String, MyError> = .Failure(MyError(status: 401, message: "Unauthorized"))
var a: String = goodResult //<--- How do I get the string out of here?
var b: MyError = badResult //<--- How do I get the error out of here?
You can make it without switch like this:
if case .Success(let str) = goodResult {
a = str
}
It's not the prettiest way, but playing around in a playground I was able to extract that value using a switch and case:
switch goodResult {
case let .Success(val):
print(val)
case let .Failure(err):
print(err.message)
}
EDIT:
A prettier way would be to write a method for the enum that returns a tuple of optional T and U types:
enum Result<T, U> {
case Success(T)
case Failure(U)
func value() -> (T?, U?) {
switch self {
case let .Success(value):
return (value, nil)
case let .Failure(value):
return (nil, value)
}
}
}
Related
In my app, I am throwing around errors by using the Error protocol. And there are many of these error types in my app such as AuthError, ApiError, etc. Not to mention the third-party frameworks's errors such as RxCocoaURLError, Twilio's NSError, etc. And then there is an enum to show/hide the error messages on the view:
enum ErrorMessageEvent {
case hide
case show(error: Error)
}
Now, in the unit test, I have a need to compare between two errors like so:
func testGetDataButtonTappedFailed_ShouldShowError() {
let error = ApiError(type: .serviceUnavailable, message: "ABC")
let viewModel = createViewModel(dataProvider: { _ in .error(error) })
var actualErrorMessageEvent: ErrorMessageEvent?
viewModel.errorMessageEvent
.emit(onNext: { actualErrorMessageEvent = $0 })
.disposed(by: disposeBag)
viewModel.getDataButtonTapped(id: 1)
XCTAssertEqual(.show(error: error), actualErrorMessageEvent)
}
Now when I added the Equatable to the ErrorMessageEvent and added this for the comparison func, but it always failed:
static func == (lhs: ErrorMessageEvent, rhs: ErrorMessageEvent) -> Bool {
switch (lhs, rhs) {
case (.hide, .hide): return true
case (.show(let lError), .show(let rError)):
return lError as AnyObject === rError as AnyObject
default: return false
}
}
What is the right way to compare those two errors and fix this? Thanks.
First, make sure that each kind of error that you are dealing with properly conforms to Equatable. Why are we using Equatable rather than ===? Because the error types value types. RxCocoaURLError is an enum for example. And === never works on structs, even if you cast to AnyObject, because two different objects will be created when you do. Example:
enum Foo {
case a(Int)
case b(String)
}
(Foo.a(1) as AnyObject) === (Foo.a(1) as AnyObject) // false
The next step is to make ErrorMessageEvent generic (kind of like Optional, or half of Result):
enum ErrorMessageEvent<Failure: Error> {
case hide
case show(error: Failure)
// this (tries to) changes the type of the error
func mapError<T: Error>(toType type: T.Type) -> ErrorMessageEvent<T>? {
switch self {
case .hide:
return .hide
case .show(error: let e):
return (e as? T).map(ErrorMessageEvent<T>.show)
}
}
}
// messages with an equatable error are also equatable
// the compiler will synthesise the implementation for us
extension ErrorMessageEvent : Equatable where Failure : Equatable {}
You can use ErrorMessageEvent<Error> for the type of observable for viewModel.errorMessageEvent. Then, when you want to compare errors in a test, you'd know which type of error you are comparing at that point, so you can do:
let error = ApiError(type: .serviceUnavailable, message: "ABC")
// ...
// I *think* it can infer the types. If not, write out the full "ErrorMessageEvent<ApiError>.show"
XCTAssertEqual(
.show(error: error),
actualErrorMessageEvent?.mapError(toType: ApiError.self))
At this point, you might have realised. This is just reinventing the wheel of Optional<T>. So rather than ErrorMessageEvent, you can also consider an using an optional. Your ErrorMessageEvent might be overengineering.
let error = ApiError(type: .serviceUnavailable, message: "ABC")
let viewModel = createViewModel(dataProvider: { _ in .error(error) })
var actualErrorMessage: ApiError?
// viewModel.errorMessageEvent would be an Observable<Error?>
viewModel.errorMessageEvent
.emit(onNext: { actualErrorMessage = $0 as? ApiError })
.disposed(by: disposeBag)
viewModel.getDataButtonTapped(id: 1)
XCTAssertEqual(error, actualErrorMessageEvent)
It doesn't have as descriptive names as show and hide (It uses none and some instead), so if that's what you care about, I can understand.
So, based on #Sweeper's answer, I came up with this custom assertion function instead:
func XCTAssertEqualGenericError<ErrorType: Equatable>(_ expected: ErrorType,
_ actual: Error?,
file: StaticString = #file,
line: UInt = #line) throws {
let actualError = try XCTUnwrap(actual as? ErrorType, file: file, line: line)
XCTAssertEqual(expected, actualError, file: file, line: line)
}
And use it in this two assertions:
func XCTAssertErrorMessageEventHide(_ actual: ErrorMessageEvent?,
file: StaticString = #file, line: UInt = #line) {
guard let actual = actual else {
XCTFail("Actual ErrorMessageEvent is nil!")
return
}
switch actual {
case .show: XCTFail("Actual ErrorMessageEvent is not of the type .hide!")
case .hide: return
}
}
func XCTAssertErrorMessageEventShow<ErrorType: Equatable>(_ expected: ErrorMessageEvent,
_ actual: ErrorMessageEvent?,
errorType: ErrorType.Type,
file: StaticString = #file,
line: UInt = #line) throws {
guard let actual = actual else {
XCTFail("Actual ErrorMessageEvent is nil!")
return
}
switch (expected, actual) {
case (.hide, _):
XCTFail("Expected ErrorMessageEvent is not of the type .show(_, _)!")
case (_, .hide):
XCTFail("Actual ErrorMessageEvent is not of the type .show(_, _)!")
case (.show(let lError, let lDuration), .show(let rError, let rDuration)):
XCTAssertEqual(lDuration, rDuration, file: file, line: line)
let expectedError = try XCTUnwrap(lError as? ErrorType, file: file, line: line)
try XCTAssertEqualGenericError(expectedError, rError, file: file, line: line)
default: return
}
}
Thanks again.
What I am trying to achieve is perform a URLSession request in swift 3. I am performing this action in a separate function (so as not to write the code separately for GET and POST) and returning the URLSessionDataTask and handling the success and failure in closures. Sort of like this-
let task = URLSession.shared.dataTask(with: request) { (data, uRLResponse, responseError) in
DispatchQueue.main.async {
var httpResponse = uRLResponse as! HTTPURLResponse
if responseError != nil && httpResponse.statusCode == 200{
successHandler(data!)
}else{
if(responseError == nil){
//Trying to achieve something like below 2 lines
//Following line throws an error soo its not possible
//var errorTemp = Error(domain:"", code:httpResponse.statusCode, userInfo:nil)
//failureHandler(errorTemp)
}else{
failureHandler(responseError!)
}
}
}
}
I do not wish to handle the error condition in this function and wish to generate an error using the response code and return this Error to handle it wherever this function is called from.
Can anybody tell me how to go about this? Or is this not the "Swift" way to go about handling such situations?
In your case, the error is that you're trying to generate an Error instance. Error in Swift 3 is a protocol that can be used to define a custom error. This feature is especially for pure Swift applications to run on different OS.
In iOS development the NSError class is still available and it conforms to Error protocol.
So, if your purpose is only to propagate this error code, you can easily replace
var errorTemp = Error(domain:"", code:httpResponse.statusCode, userInfo:nil)
with
var errorTemp = NSError(domain:"", code:httpResponse.statusCode, userInfo:nil)
Otherwise check the Sandeep Bhandari's answer regarding how to create a custom error type
You can create a protocol, conforming to the Swift LocalizedError protocol, with these values:
protocol OurErrorProtocol: LocalizedError {
var title: String? { get }
var code: Int { get }
}
This then enables us to create concrete errors like so:
struct CustomError: OurErrorProtocol {
var title: String?
var code: Int
var errorDescription: String? { return _description }
var failureReason: String? { return _description }
private var _description: String
init(title: String?, description: String, code: Int) {
self.title = title ?? "Error"
self._description = description
self.code = code
}
}
You should use NSError object.
let error = NSError(domain: "", code: 401, userInfo: [ NSLocalizedDescriptionKey: "Invalid access token"])
Then cast NSError to Error object.
You can create enums to deal with errors :)
enum RikhError: Error {
case unknownError
case connectionError
case invalidCredentials
case invalidRequest
case notFound
case invalidResponse
case serverError
case serverUnavailable
case timeOut
case unsuppotedURL
}
and then create a method inside enum to receive the http response code and return the corresponding error in return :)
static func checkErrorCode(_ errorCode: Int) -> RikhError {
switch errorCode {
case 400:
return .invalidRequest
case 401:
return .invalidCredentials
case 404:
return .notFound
//bla bla bla
default:
return .unknownError
}
}
Finally update your failure block to accept single parameter of type RikhError :)
I have a detailed tutorial on how to restructure traditional Objective - C based Object Oriented network model to modern Protocol Oriented model using Swift3 here https://learnwithmehere.blogspot.in Have a look :)
Hope it helps :)
Details
Xcode Version 10.2.1 (10E1001)
Swift 5
Solution of organizing errors in an app
import Foundation
enum AppError {
case network(type: Enums.NetworkError)
case file(type: Enums.FileError)
case custom(errorDescription: String?)
class Enums { }
}
extension AppError: LocalizedError {
var errorDescription: String? {
switch self {
case .network(let type): return type.localizedDescription
case .file(let type): return type.localizedDescription
case .custom(let errorDescription): return errorDescription
}
}
}
// MARK: - Network Errors
extension AppError.Enums {
enum NetworkError {
case parsing
case notFound
case custom(errorCode: Int?, errorDescription: String?)
}
}
extension AppError.Enums.NetworkError: LocalizedError {
var errorDescription: String? {
switch self {
case .parsing: return "Parsing error"
case .notFound: return "URL Not Found"
case .custom(_, let errorDescription): return errorDescription
}
}
var errorCode: Int? {
switch self {
case .parsing: return nil
case .notFound: return 404
case .custom(let errorCode, _): return errorCode
}
}
}
// MARK: - FIle Errors
extension AppError.Enums {
enum FileError {
case read(path: String)
case write(path: String, value: Any)
case custom(errorDescription: String?)
}
}
extension AppError.Enums.FileError: LocalizedError {
var errorDescription: String? {
switch self {
case .read(let path): return "Could not read file from \"\(path)\""
case .write(let path, let value): return "Could not write value \"\(value)\" file from \"\(path)\""
case .custom(let errorDescription): return errorDescription
}
}
}
Usage
//let err: Error = NSError(domain:"", code: 401, userInfo: [NSLocalizedDescriptionKey: "Invaild UserName or Password"])
let err: Error = AppError.network(type: .custom(errorCode: 400, errorDescription: "Bad request"))
switch err {
case is AppError:
switch err as! AppError {
case .network(let type): print("Network ERROR: code \(type.errorCode), description: \(type.localizedDescription)")
case .file(let type):
switch type {
case .read: print("FILE Reading ERROR")
case .write: print("FILE Writing ERROR")
case .custom: print("FILE ERROR")
}
case .custom: print("Custom ERROR")
}
default: print(err)
}
Implement LocalizedError:
struct StringError : LocalizedError
{
var errorDescription: String? { return mMsg }
var failureReason: String? { return mMsg }
var recoverySuggestion: String? { return "" }
var helpAnchor: String? { return "" }
private var mMsg : String
init(_ description: String)
{
mMsg = description
}
}
Note that simply implementing Error, for instance, as described in one of the answers, will fail (at least in Swift 3), and calling localizedDescription will result in the string "The operation could not be completed. (.StringError error 1.)"
I still think that Harry's answer is the simplest and completed but if you need something even simpler, then use:
struct AppError {
let message: String
init(message: String) {
self.message = message
}
}
extension AppError: LocalizedError {
var errorDescription: String? { return message }
// var failureReason: String? { get }
// var recoverySuggestion: String? { get }
// var helpAnchor: String? { get }
}
And use or test it like this:
printError(error: AppError(message: "My App Error!!!"))
func print(error: Error) {
print("We have an ERROR: ", error.localizedDescription)
}
let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
self.showLoginError(error)
create an NSError object and typecast it to Error ,show it anywhere
private func showLoginError(_ error: Error?) {
if let errorObj = error {
UIAlertController.alert("Login Error", message: errorObj.localizedDescription).action("OK").presentOn(self)
}
}
protocol CustomError : Error {
var localizedTitle: String
var localizedDescription: String
}
enum RequestError : Int, CustomError {
case badRequest = 400
case loginFailed = 401
case userDisabled = 403
case notFound = 404
case methodNotAllowed = 405
case serverError = 500
case noConnection = -1009
case timeOutError = -1001
}
func anything(errorCode: Int) -> CustomError? {
return RequestError(rawValue: errorCode)
}
I know you have already satisfied with an answer but if you are interested to know the right approach, then this might be helpful for you.
I would prefer not to mix http-response error code with the error code in the error object (confused? please continue reading a bit...).
The http response codes are standard error codes about a http response defining generic situations when response is received and varies from 1xx to 5xx ( e.g 200 OK, 408 Request timed out,504 Gateway timeout etc - http://www.restapitutorial.com/httpstatuscodes.html )
The error code in a NSError object provides very specific identification to the kind of error the object describes for a particular domain of application/product/software. For example your application may use 1000 for "Sorry, You can't update this record more than once in a day" or say 1001 for "You need manager role to access this resource"... which are specific to your domain/application logic.
For a very small application, sometimes these two concepts are merged. But they are completely different as you can see and very important & helpful to design and work with large software.
So, there can be two techniques to handle the code in better way:
1. The completion callback will perform all the checks
completionHandler(data, httpResponse, responseError)
2. Your method decides success and error situation and then invokes corresponding callback
if nil == responseError {
successCallback(data)
} else {
failureCallback(data, responseError) // failure can have data also for standard REST request/response APIs
}
Happy coding :)
I'm trying to check whether the statusCode is equal to 2xx, however i'm not quite sure how to create this regex code for that type of error handling. Here is my code at the moment with a variable that retrieve the statusCode from header?
func SignUpWithPassword(email: String, password: String, delegate: CreateUserDelegate) {
testProvider.request(.SignIn(email, password), completion: { result in
switch result {
case let .Success(response):
let statusCode = response.statusCode
case let .Failure(error):
print(error)
let description = "Error! Please try again!"
delegate.CreateUser(didError: description)
}
})
delegate.CreateUser(didSucceed: SignUpUser(email: email, password: password))
}
I believe that NSHTTPURLResponse status code is an Int.
you should be able to use a ranged expression on this in your branching logic:
switch response.statusCode
{
case 200 ... 299 :
print("Success")
default:
print("Failure")
}
I'm using this enum:
enum HTTPStatusCodeGroup: Int {
case Info = 100
case Success = 200
case Redirect = 300
case Client = 400
case Server = 500
case Unknown = 999
init(code: Int) {
switch code {
case 100...199: self = .Info
case 200...299: self = .Success
case 300...399: self = .Redirect
case 400...499: self = .Client
case 500...599: self = .Server
default: self = .Unknown
}
}
}
In Swift, I have created the own Regax class.
// Custom Regex class
class Regex {
let internalExpression: NSRegularExpression
let pattern: String
init(_ pattern: String) {
self.pattern = pattern
do {
self.internalExpression = try NSRegularExpression(pattern: self.pattern, options: .CaseInsensitive)
} catch _ {
self.internalExpression = NSRegularExpression.init();
}
}
func test(input: String) -> Bool {
let matches = self.internalExpression.matchesInString(input, options: .WithTransparentBounds, range: NSMakeRange(0, input.characters.count)) as Array<NSTextCheckingResult>
return matches.count > 0
}
}
// Override operator
infix operator =~ {}
func =~ (input: NSInteger, pattern: String) -> Int {
return (Regex(pattern).test(String(input))) ? input : -1;
}
// Example add it viewDidLoad method
let statusCode = 200
switch statusCode
{
case statusCode =~ "2[0-9][0-9]" :
print("2[0-9][0-9] Success")
break
case statusCode =~ "3[0-9][0-9]" :
print("3[0-9][0-9] Success")
break
case statusCode =~ "4[0-9][0-9]" :
print("4[0-9][0-9] Success")
break
case statusCode =~ "5[0-9][0-9]" :
print("5[0-9][0-9] Success")
break
default:
print("Failure")
}
I have this code:
import Alamofire
extension Alamofire.Request {
public func responseObject<T: ResponseJSONObjectSerializable>(completionHandler:(NSURLRequest?,NSHTTPURLResponse?, Result<T>) -> Void) -> Self {
let responseSerializer = GenericResponseSerializer<T> { request, response, data in
guard let responseData = data else {
let failureReason = "Object could not be serialized because input data was nil."
let error = Error.errorWithCode( .DataserializableFailed, failureReason: failureReason)
//Error: Type of expression is ambiguous without more context
return .Failure(data, error)
}}}
The .DataserializableFailed is defined in Alamofire:
public struct Error {
public enum Code: Int {
case InputStreamReadFailed = -6000
case OutputStreamWriteFailed = -6001
case ContentTypeValidationFailed = -6002
case StatusCodeValidationFailed = -6003
case DataSerializationFailed = -6004
case StringSerializationFailed = -6005
case JSONSerializationFailed = -6006
case PropertyListSerializationFailed = -6007
}
}
If I put Code before .DataserializableFailed it will say: Use of unresolved identifier 'Code'
What's wrong?
Obviously your capitalization/spelling is different. It should be .DataSerializationFailed, not .DataserializableFailed.
Or you could refer to Error.Code.DataSerializationFailed.
I have an object FormField which has two properties: a String name, and a value which can accept any type--hence I've made it Any!. However, I've been told in a separate question to use an enum with associated values instead of Any!.
enum Value {
case Text(String!)
case CoreDataObject(NSManagedObject!)
}
class FormField {
var name: String
var value: Value?
// initializers...
}
This approach makes it awfully verbose to check for nullity however. If I wanted to display an alert view for all the missing fields in the form, I'll have to repeat a nil check for every case in a switch statement:
for field in self.fields {
if let value = field.value {
switch value {
case .Text(let text):
if text == nil {
missingFields.append(field.name)
}
case .CoreDataObject(let object):
if object == nil {
missingFields.append(field.name)
}
}
}
}
Is there a shorter way of accessing the enum's associated value, regardless of the type? If I make FormField.value an Any! the above code would be as easy as:
for field in self.fields {
if field.value == nil {
missingFields.append(field.name)
}
}
Define a method isMissing() inside the enum - write it once and only once. Then you get nearly exactly what you prefer:
for field in self.fields {
if field.value.isMissing() {
missingFields.append(field.name)
}
}
It would look something like this (from the Swift Interpreter):
1> class Foo {}
>
2> enum Value {
3. case One(Foo!)
4. case Two(Foo!)
5.
6. func isMissing () -> Bool {
7. switch self {
8. case let .One(foo): return foo == nil
9. case let .Two(foo): return foo == nil
10. }
11. }
12. }
13> let aVal = Value.One(nil)
aVal: Value = One {
One = nil
}
14> aVal.isMissing()
$R0: Bool = true
With Swift 2 it's possible to get the associated value using reflection.
To make that easier just add the code below to your project and extend your enum with the EVAssociated protocol.
public protocol EVAssociated {
}
public extension EVAssociated {
public var associated: (label:String, value: Any?) {
get {
let mirror = Mirror(reflecting: self)
if let associated = mirror.children.first {
return (associated.label!, associated.value)
}
print("WARNING: Enum option of \(self) does not have an associated value")
return ("\(self)", nil)
}
}
}
Then you can access the .asociated value with code like this:
class EVReflectionTests: XCTestCase {
func testEnumAssociatedValues() {
let parameters:[EVAssociated] = [usersParameters.number(19),
usersParameters.authors_only(false)]
let y = WordPressRequestConvertible.MeLikes("XX", Dictionary(associated: parameters))
// Now just extract the label and associated values from this enum
let label = y.associated.label
let (token, param) = y.associated.value as! (String, [String:Any]?)
XCTAssertEqual("MeLikes", label, "The label of the enum should be MeLikes")
XCTAssertEqual("XX", token, "The token associated value of the enum should be XX")
XCTAssertEqual(19, param?["number"] as? Int, "The number param associated value of the enum should be 19")
XCTAssertEqual(false, param?["authors_only"] as? Bool, "The authors_only param associated value of the enum should be false")
print("\(label) = {token = \(token), params = \(param)")
}
}
// See http://github.com/evermeer/EVWordPressAPI for a full functional usage of associated values
enum WordPressRequestConvertible: EVAssociated {
case Users(String, Dictionary<String, Any>?)
case Suggest(String, Dictionary<String, Any>?)
case Me(String, Dictionary<String, Any>?)
case MeLikes(String, Dictionary<String, Any>?)
case Shortcodes(String, Dictionary<String, Any>?)
}
public enum usersParameters: EVAssociated {
case context(String)
case http_envelope(Bool)
case pretty(Bool)
case meta(String)
case fields(String)
case callback(String)
case number(Int)
case offset(Int)
case order(String)
case order_by(String)
case authors_only(Bool)
case type(String)
}
The code above is now available as a cocoapod susbspec at
https://github.com/evermeer/Stuff#enum
It also has an other nice enum extension for enumerating all enum values.
If the associated values were of the same type for all enum cases the following approach could help.
enum Value {
case text(NSString!), two(NSString!), three(NSString!) // This could be any other type including AnyClass
}
// Emulating "fields" datastruct for demo purposes (as if we had struct with properties).
typealias Field = (en: Value, fieldName: String)
let fields: [Field] = [(.text(nil),"f1"), (.two(nil), "f2"), (.three("Hey"), "f3")] // this is analog of "fields"
let arrayOfFieldNamesWithEmptyEnums: [String] = fields.compactMap({
switch $0.en {
case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.fieldName } else { return nil }}
})
print("arrayOfFieldNamesWithEmptyEnums \(arrayOfFieldNamesWithEmptyEnums)")
Many other things can be obtained similarly.
let arrayOfEnumsWithoutValues: [Value] = fields.compactMap({
switch $0.en {
case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.en } else { return nil }}
})
print("arrayOfEnumsWithoutValues \(arrayOfEnumsWithoutValues)")
// just to check ourselves
if let index = arrayOfEnumsWithoutValues.index(where: { if case .two = $0 { return true }; return false }) {
print(".two found at index \(index)")
}