Error Handling : Capture error objects in catch block - ios

I have the below Error Model.
enum MyErrorType: ErrorType
{
case RequestFormationError(errorDomain : ARSErrorDomain)
case NetworkError(errorDomain : ARSErrorDomain)
case FileNameError(errorDomain : ARSErrorDomain)
case FileNotFoundError(errorDomain : ARSErrorDomain)
case ValidationError(errorDomain : ARSErrorDomain)
}
class MyErrorDomain: NSObject
{
var errorMessage : String?
//var errorCode : String?
convenience init(errorMessage: String)
{
self.init()
self.errorMessage = errorMessage
}
}
Whenever an error happens in the code, i pass it to the calling function. My problem is how do i access the MyErrorDomain in the catch block? Can anyone let me know the syntax?

You need to use following syntax:
do {
....
} catch MyErrorType.RequestFormationError(let errorDomain) {
print(errorDomain)
}
It is described in docs.

Related

Swift access enum param

I have this function:
#objc(syncUser:rejecter:)
func syncUser(resolve: #escaping RCTPromiseResolveBlock, reject: #escaping RCTPromiseRejectBlock) {
self.cancelableSyncUser = MyService.shared?.syncingUser()
.sink(receiveCompletion: {
switch $0 {
case .failure(let error):
print("PRINT \(error)")
reject(error.localizedDescription, error.localizedDescription, error);
case .finished:
}
}, receiveValue: {
resolve($0);
})
}
I get an error object that contains some information that I want to use, but I cannot access its properties (code and message).
If I print the error object, I get this:
PRINT syncUser(Optional(mydomain.OpenpathErrorData(err: mydomain.OpenpathErrorData.Err(message: "Error message", code: "X"))), nil)
As we can see, it contains code and message.
OpenpathErrorData is an enum defined in another class:
enum OpenpathError: Error {
case syncUser(OpenpathErrorData?, Error?)
}
struct OpenpathErrorData: Codable {
struct Err: Codable {
var message:String
var code:String
}
var err: Err
}
The problem is that I cannot access those properties. I can only access error.localizedDescription.
I've tried everything but either I cannot access it or I don't know the right syntax.
Any ideas? I know it's hard to understand without seeing the whole code but if it's about the syntax maybe someone can give me a hint.
Thanks a lot in advance.
You can use if case ... to access the content of the error structure.
if case let OpenpathError.syncUser(errorData, otherError) = error
With this we check if error is of the case .synchUser and at the same time we also assign the associated values of the enum case to the two (optional) variables errorData and otherError that you can then use in your code.
if case let OpenpathError.syncUser(errorData, otherError) = error {
if let errorData = errorData {
print(errorData.err.message, errorData.err.code)
}
if let otherError = otherError {
print(otherError)
}
}

Cast from 'AFError?' to unrelated type 'URLError' always fails warning

I'm getting this warning Cast from 'AFError?' to unrelated type 'URLError' always fails when I try to cast the error in the following function
func requestBlock() {
struct ValidationConsumer: ResponseDelegate {
weak var syncRequest: Transfer_PurchaseValidation?
var productIdentifier: String
func didSucceed(_ _: JSON, _ _: AFDataResponse<Any>?) {
DDLogInfo("Purchase payload for productId = \(productIdentifier) was sent")
syncRequest?.didSucceed()
}
func didFail(_ json: JSON, _ code: Int?, _ dataResponse: AFDataResponse<Any>?) {
syncRequest?.didFail(with: .PurchaseValidationError(code,
dataResponse?.error as? URLError))
}
}
guard data.shouldBeTransferred else {
return
}
guard isUnderTest == nil else {
executeTestsequence()
return
}
guard let receiptDataString = data.receiptDataString,
let productIdentifier = data.productIdentifier else {
didFail(with: .InvalidData); return
}
let validationConsumer = ValidationConsumer(syncRequest: self,
productIdentifier: productIdentifier)
self.validatePurchase(receiptDataString, productIdentifier,
validationDelegate: validationConsumer)
}
at this part syncRequest?.didFail(with: .PurchaseValidationError(code, dataResponse?.error as? URLError))
I tried to use NSError or Error classes but no success.
Can anyone let me know how I can get rid of this warning?
Thanks in advance
Alamofire returns AFError instances by default which are not convertible to URLError. You can examine the AFError documentation or source code for the full details, but underlying errors like network failures are exposed through the underlyingError property. So in the case of a network failure, response?.error?.underlyingError as? URLError should give you the correct value, but only if the underlying error is actually a URLError.
Ultimately I suggest you handle the AFError directly, as it's the only full representation of all of the errors Alamofire can return. Either that, or your own error type which does the handling for you. Casting is not the correct way to capture these errors, as any cast will lose some error information.
I had a problem similar to yours.
I made a model as follows:
struct MyErorr: Error {
public var errorCode: Int
public var erorrMessage: String
}
and create object from myError this error part like this:
// error instance from AFError
if error.responseCode == 401 {
let myError: MyErorr = .init(errorCode: 401, erorrMessage: "Unauthorized")
callback(myError)
} else { ... }
I hope it is useful for you
The solution was to add another cast.
So, instead of
syncRequest?.didFail(with: .PurchaseValidationError(code,
dataResponse?.error as? URLError))
I did the case as following
syncRequest?.didFail(with: .PurchaseValidationError(code,
dataResponse?.error as NSError? as? URLError))

How to override localizedDescription for custom Error in Swift 3? [duplicate]

This question already has answers here:
How to provide a localized description with an Error type in Swift?
(7 answers)
Closed 5 years ago.
Error protocol has only one property localizedDescription.
I tried to create custom object inherited from NSObject and Error but I can not override localizedDescription. How can I do that?
This code does not allow me to get custom description:
class MyError: NSObject, Error {
var desc = ""
init(str: String) {
desc = str
}
override var description: String {
get {
return "MyError: \(desc)"
}
}
var localizedDescription: String {
get {
return self.description
}
}
}
func test_my_code() {
let error = MyError(str: "my test string")
let x = error as Error
print(x.localizedDescription)
}
Calling function "test_my_code" get unexpected result:
"The operation couldn’t be completed...".
What should I do to get result "MyError: my test string" ?
The documentation about new Error bridging feature is not clear enough still now, so this answer may need some updates in the near future, but according to SE-0112 and the latest Swift source code, you may need to use LocalizedError rather than Error and implement errorDescription.
class MyError: NSObject, LocalizedError {
var desc = ""
init(str: String) {
desc = str
}
override var description: String {
get {
return "MyError: \(desc)"
}
}
//You need to implement `errorDescription`, not `localizedDescription`.
var errorDescription: String? {
get {
return self.description
}
}
}
func test_my_code() {
let error = MyError(str: "my test string")
let x = error as Error
print(x.localizedDescription)
}
test_my_code() //->MyError: my test string
Other than using LocalizedError, this default implementation works:
(NSError.swift, the link shown above)
public extension Error {
/// Retrieve the localized description for this error.
var localizedDescription: String {
return NSError(domain: _domain, code: _code, userInfo: nil).localizedDescription
}
}
It is a little complicated how Swift defines _domain or _code from arbitrary types just conforming to Error, but it seems that NSError generates "The operation couldn’t be completed..." for unknown combinations of domain and code.
If custom type conforms to protocol CustomStringConvertible and provides localized description, then the following extension of LocalizedError might be useful:
extension LocalizedError where Self: CustomStringConvertible {
var errorDescription: String? {
return description
}
}
Example code:
class MyError: LocalizedError, CustomStringConvertible {
let desc: String
init(str: String) {
desc = str
}
var description: String {
let format = NSLocalizedString("Operation error: %#", comment: "Error description")
return String.localizedStringWithFormat(format, desc)
}
}
let error = MyError(str: "my test string")
let x = error as Error
print(x.localizedDescription) // Prints "Operation error: my test string"
print(String(describing: x)) // Prints "Operation error: my test string"

Generate your own Error code in swift 3

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 :)

Accessing values in swift enums

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)
}
}
}

Resources