Swift - is there a way to match an error without throwing? - swift

In some cases, an error is provided as an argument rather than being thrown. One example could be completion handlers where Result<T, Error> is provided. I would like to match the error without throwing it.
Example of error matching by throwing it:
enum MyError: Error {
case error
}
func process(error: Error) {
do {
throw error
} catch MyError.error {
print("this is it")
} catch {
print("unknown error")
}
}
process(error: MyError.error)
UPDATE:
The matching should work even for the system provided errors, like URLError which are not enum types.
func process(error: Error) {
do {
throw error
} catch URLError.timedOut {
print("this is not it")
} catch URLError.cancelled {
print("this is it")
} catch {
print("unknown error")
}
}
process(error: URLError(.cancelled))

You can use the if case syntax.
In your example,
enum MyError: Error {
case error
}
func process(error: Error) {
if case MyError.error = error {
print("this is it")
} else {
print("unknown error")
}
}
process(error: MyError.error)

There are several ways to match errors that are not thrown. A way that keeps the code simple with many errors to check for, is the use of a switch statement:
Update:
I have now updated the code to include the URLError that you have requested.
enum CommonError: Error {
case input
case output
}
enum RareError: Error {
case language
case mathematics
}
let error: Error = RareError.language
// or
let error: Error = URLError(.cancelled)
switch error {
case CommonError.input: print("Common input error")
case CommonError.output: print("Common output error")
case RareError.language: print("Rare language error") // This is executed
case RareError.mathematics: print("Rare mathematics error")
case URLError.cancelled: print("URLError -> cancelled") // - or this
case URLError.timedOut: print("URLError -> timed out")
default: print("The error is unknown; maybe you should consider throwing it now?")
}

Related

What error do I need to throw to for a specific catch block to handle + Swift error handling

I want to add code to throw an error that requires the "catch let printerErrorsecond" and the last catch block to handle an error. I have tried updating the value for the toPrinter parameter to a number of values without any luck. I also tried a number of else if statements and cannot seem to get either of the last two catch blocks to handle the errors. It seems like a case for both could be added in the PrinterError enum but after several attempts I could not resolve the issue. Any code based resolutions you can provide the better!
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
} else if printerName == "Fire" {
throw PrinterError.onFire
} else if printerName == "Empty" {
throw PrinterError.outOfPaper
}
return "Job sent"
}
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
// Prints "Job sent"
try to implement it by this way
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
case deFault
case noHandeling
}
func send(job: Int, toPrinter printerName: String) throws -> String {
switch printerName {
case PrinterError.outOfPaper.localizedDescription:
print("outOfPaper")
return PrinterError.outOfPaper.localizedDescription
case PrinterError.noToner.localizedDescription:
print("noToner")
return PrinterError.noToner.localizedDescription
case PrinterError.onFire.localizedDescription:
print("onFire")
return PrinterError.onFire.localizedDescription
default:
print("default")
return PrinterError.deFault.localizedDescription
}
}
do {
try send(job: 100, toPrinter: "AAA")
} catch {
print(error.localizedDescription)
}

Swift error handling with multiple catch and conditions based on the error's enum associated value

Is there a way to catch errors with some condition on the catch based on the value of the associated value of the error enum?
Example:
enum Errors : Error {
case error1(str: String?) // optional associated value
case error2
case error3
}
func f() throws {
throw Errors.error1(str: nil)
}
do {
try f()
}
catch Errors.error1(let str) {
if(str != nil) {
print(str!)
}
else {
//throw the same error to be caught in the last catch
}
}
catch {
print("all other errors")
}
Yes of course!
In a catch, you can do pattern matching with the error, just like in a switch statement. You can read more about this in the Swift guide under the section "Handling errors using Do-Catch". This means you can use where:
do {
try f()
}
catch Errors.error1(let str) where str != nil{
}
catch {
print("all other errors")
}

Handle error in Swift 4.2 without downcasting error

I defined an error type called "HelloError", after multiple calls, I have to do downcast using as! and the error in do-catch block.
enum HelloError: Error {
case A
case B
}
func hello() throws {
try helloAgain()
}
func helloAgain() throws {
throw HelloError.A
}
do {
try hello()
} catch {
switch error as! HelloError {
case .A:
print("A")
case .B:
print("B")
}
}
Is it possible to catch the error without downcasting the error type? I want to write code like this:
do {
try hello()
} catch {
switch error {
case HelloError.A:
print("A")
case HelloError.B:
print("B")
}
}
Thank you.
You can do the switch with multiple catch blocks
do {
try hello()
} catch HelloError.A {
print("A")
} catch HelloError.B {
print("B")
}
More detailed information is in Swift Language Guide: Error handling

LocalizedError.errorDescription gets lost for wrapped errors

While optimizing error handling in my Swift application, I encountered the following odd behavior. My custom error types implement LocalizedError so I can provide errorDescription to the user. Since I cannot rely on third-party libraries to do the same, I have an ErrorWrapper enum with only one case underlying(_: Error):
import Foundation
enum CustomError: Error {
case test(_: String)
}
extension CustomError: LocalizedError {
var errorDescription: String {
switch self {
case let .test(err):
return "An error occured: \(err)"
}
}
}
enum ErrorWrapper: Error {
case underlying(_: Error)
}
extension ErrorWrapper: LocalizedError {
var errorDescription: String {
switch self {
case let .underlying(error) where error is LocalizedError:
// I verified execution jumps into this block,
// but error.errorDescription is nil
return (error as! LocalizedError).errorDescription ?? "Unknown error"
case let .underlying(error):
return error.localizedDescription
}
}
}
let err = CustomError.test("Foo")
let wrapped = ErrorWrapper.underlying(err)
print(err.errorDescription) // prints: "An error occured: Foo\n"
print(wrapped.errorDescription) // prints: "Unknown error\n"
Is there a way to solve this problem? I thought about implementing my custom Error protocol, but would like to stick with Swift's builtin error protocols, if possible.

Catch multiple errorTypes?

I'm looking for a way to catch multiple types of errors in a catch. I've tried fallthrough and the comma separated style from a switch statement and neither works. The docs say nothing about catching multiple but pattern 1. It's not clear to me which of the pattern syntaxes would work here.
Error definitions (sample):
enum AppErrors {
case NotFound(objectType: String, id: Int)
case AlreadyUsed
}
Works:
do {
//...
} catch AppErrors.NotFound {
makeNewOne()
} catch AppErrors.AlreadyUsed {
makeNewOne()
} catch {
print("Unhandled error: \(error)")
}
Does not compile, is it possible to do something like this?
do {
//...
} catch AppErrors.NotFound, AppErrors.AlreadyUsed {
makeNewOne()
} catch {
print("Unhandled error: \(error)")
}
If you want to catch all AppErrors, you can use this pattern:
catch is AppErrors
If you're looking for more specific matching, it seems to quickly get ugly.
This will let us catch specific cases of AppErrors:
catch let error as AppErrors where error == .NotFound || error == .AlreadyUsed
There's also this syntax which seems to work:
catch let error as AppErrors where [.NotFound, .AlreadyUsed].contains(error)
For completeness sake, I'll also add this option, which allows us to catch errors of two different types, but it doesn't allow us to specify which case within those types:
catch let error where error is AppErrors || error is NSError
Finally, based on the fact that anything we catch will conform to the ErrorType protocol, we can clean up the second & third examples I provided with an ErrorType extension and use that in conjunction where a where clause in our catch:
extension ErrorType {
var isFooError: Bool {
guard let err = self as? AppErrors else { return false }
return err == .NotFound || err == .AlreadyUsed
}
}
And just catch it like this:
catch let error where error.isFooError
You can create a case that contains an AppErrors array:
indirect enum AppErrors: Error {
case NotFound
case AlreadyUsed
case errors([AppErrors])
}
Then, for the catch statement:
catch AppErrors.errors(let errors) where errors == [.NotFound, .AlreadyUsed]
Do note, however, that errors is an Array and the order matters when comparing with ==. An alternative is to use case errors(Set<AppErrors>), but that would require AppErrors to conform to Hashable protocol.
UPDATE: Come to think of it, it's better to use an OptionSet type:
public struct InvalidInput: OptionSet, Error {
public var rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
public static let noAccount = InvalidInput(rawValue: 1 << 0)
public static let noKey = InvalidInput(rawValue: 1 << 1)
public static let invalidKey = InvalidInput(rawValue: 1 << 2)
}
func validateInput() throws -> Void {
var invalid: InvalidInput = []
invalid.insert(.noKey)
invalid.insert(.noAccount)
if !invalid.isEmpty {
throw invalid
}
}
do {
try validateInput()
} catch [InvalidInput.noAccount, InvalidInput.noKey] as InvalidInput {
print("Account and key are both required.")
}
Link: http://www.chrisamanse.xyz/2016/12/03/catching-multiple-errors-in-swift
Multi-Pattern Catch Clauses proposal (SE-0276) is implemented in Swift 5.3
And now code snippet from the question is valid:
do {
//...
} catch AppErrors.NotFound, AppErrors.AlreadyUsed {
makeNewOne()
} catch {
print("Unhandled error: \(error)")
}
You can check updated docs.