Catch multiple errorTypes?
Asked Answered
A

3

17

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)")
}
Alan answered 19/4, 2016 at 21:35 Comment(0)
B
18

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
Baty answered 19/4, 2016 at 22:40 Comment(4)
When I try catch let error as AppErrors where error == .NotFound, I get the error message binary operator == cannot be applied to two AppErrors operands. I think this is because these errorTypes have associated values. Is there a way around this?Alan
I think the ErrorType extension at the bottom of answer can probably work for that.Baty
Since it uses a == implementation for comparison, it doesn't work either. Right now I think only a switch implementation can package up multiple associated-value errorTypes.Alan
You can put whatever logic you want in the extension.Baty
G
2

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.

Glyceryl answered 22/11, 2022 at 8:15 Comment(0)
F
1

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

Fluvial answered 3/12, 2016 at 4:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.