4

Let me prefix with why I think this is not a duplicate of How to provide a localized description with an Error type in Swift?

The answers provided will still result in some static/class function call and not an initializer style or require casting to NSError (which I like to avoid).


A brief summery of the reasons:

A Swift function that throws does not declare the error type. We cannot enforce the catch to pass a custom type that simply conforms to the Error protocol. Knowing that, on the do-catch side, we get no help from the compiler as to what type (if custom) of error we get and by default we'll expect the known NSError properties. Otherwise, we need to simply rely on textual documentations explaining the type of errors we can catch, as the catch will simply pass an Error type.

Now, unlike NSError, Error is a protocol where the properties we get in userInfo are read only. So we resort to constructing an NSError type, and casting it as Error.


I am trying to create a simple clean struct that return an Error type (not NSError) where I can throw like:

throw MYError(domain: "com.somthing.error", code: 0, userInfo: [NSLocalizedDescriptionKey : "Something bad happened"])

Main issue is that the only way to set the NSLocalizedDescriptionKey, is by initializing an NSError object. Doing so will require casting to Error type (which is something I'm trying to avoid).

I first tried to use extension Error {..., but cannot use an initializer.

If I use a struct conforming to Error protocol (struct MyError: Error {...), I still have the problem of localizedDescription is get only.

What I use is actually something like:

struct MYError: Error {

    static func with(domain: String = "com.somthing.error", code: Int = 0, localizedDescription: String) -> Error {
        return NSError(domain: domain, code: code, userInfo: [NSLocalizedDescriptionKey : localizedDescription]) as Error
    }
}

Which I can use like:

throw MYError.with(localizedDescription: "Some Error has happened")

Intuitively, I would expect a type like MYError to just use an initializer MYError(domain:..., not a static function.


The more Swifty way would be something like:

enum ErrorType: Error {
    case one
    case two
    case three(with: String)
}

...

// In some function:
throw ErrorThrown.three(with: "Three!")

...

// Catch like:
catch ErrorType.three(let str) {
    print("Error three: \(str)")
}

It is not clear if we're there yet. It seems that NSError is still much at play where I know I can expect to get a localizedDescription an optional localizedFailureReason and the familiar NSError properties.

bauerMusic
  • 5,470
  • 5
  • 38
  • 53
  • 1
    Possible duplicate of [How to provide a localized description with an Error type in Swift?](https://stackoverflow.com/questions/39176196/how-to-provide-a-localized-description-with-an-error-type-in-swift) – Coder-256 Jun 14 '17 at 14:33
  • @Coder256 Not exactly a duplicate. I read that one, not talking about localizing, I am talking about an initializer. – bauerMusic Jun 14 '17 at 14:35
  • `Error` is just a protocol, so you can't initialize it. Read: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html – JAL Jun 14 '17 at 14:37
  • If you want to always include the same localized description for every unique error, then just do `throw MyError.customError` in that link for example. There is no need to initialize it. However, if you want to include a dynamic message (or different message for the same error), you probably should just include that in the `userInfo`, but you could use an [associated value](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID148). – Coder-256 Jun 14 '17 at 14:40

3 Answers3

15

Similarly as in How to provide a localized description with an Error type in Swift? you can define a custom error type adopting the LocalizedError protocol:

public struct MyError: Error {
    let msg: String

}

extension MyError: LocalizedError {
    public var errorDescription: String? {
            return NSLocalizedString(msg, comment: "")
    }
}

Example:

do {
    throw MyError(msg: "Something happened")
} catch let error {
    print(error.localizedDescription)
}

This prints the localized version of the given message. Note that error in the catch-clause is a general Error, so the caller does not need to cast it to the concrete error type (or even know which error type is thrown).

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I was trying to avoid using a static function. A static function `MyError.customError` is what I am using (in a different way). – bauerMusic Jun 15 '17 at 04:58
  • @bauerMusic: Perhaps I misunderstood your question. Is the updated answer what you are looking for? – Martin R Jun 15 '17 at 05:06
  • Good answer, the only issue is the need for casting (error as! MYError) to access the localizedDescription. – bauerMusic Jun 15 '17 at 06:18
  • @bauerMusic: No. As I pointed out, there is *no need* to cast the error to MyError in the catch clause. Did you try the above example? Please let me know if it does not work. – Martin R Jun 15 '17 at 06:40
  • I get `The operation couldn’t be completed. (ExeptionsInSwift.MyError error 1.)` when I try to access `localizedDescription` – bauerMusic Jun 15 '17 at 06:44
  • @bauerMusic: Strange, I tested the my code with Swift 3 and 4 on macOS with Xcode 8.3.3. and 9, and it works as expected. What platform are you on? – Martin R Jun 15 '17 at 06:53
  • I tested it on a 'Command Line Tool' App. Could be an issue there. – bauerMusic Jun 15 '17 at 07:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146721/discussion-between-martin-r-and-bauermusic). – Martin R Jun 15 '17 at 07:01
  • Summery points: `var errorDescription` is what return in `localizedDescription`. In the `catch let error`, the `let error` can be omitted. – bauerMusic Jun 15 '17 at 07:27
  • @bauerMusic: Yes, but I prefer to write `let error` explicitly, so that the reader of the code does not have to "know" or "guess" where it comes from. Similarly for `didSet` and `willSet` which have "automatic" parameters oldValue/newValue. – Surely a matter of personal preference! – Martin R Jun 15 '17 at 07:30
7

Error is a protocol, you can throw anything which conforms to that protocol

For example

struct MYError : Error {
    let description : String
    let domain : String

    var localizedDescription: String {
        return NSLocalizedString(description, comment: "")
    }
}

And you can use it:

func test() throws
{
  throw MYError(description: "Some Error has happened", domain: "com.somthing.error")
}

do {
    try test()
} catch let error as MYError{
    print("error: ", error.domain, error.localizedDescription)
}
vadian
  • 274,689
  • 30
  • 353
  • 361
2

Finally found a way to stick anything in a generic Error type. Thanks to vadian and Martin R, on which answers I finally came to this part.

Comes down to this:

struct MyError: Error {

    var locString: String
    var reason: String?
    var code: Int
    var wish: String
}

// The errorDescription is the only part that actually comes generically with Error
extension MyError: LocalizedError {

    // This is actually part of LocalizedError
    var errorDescription: String? {
        return locString
    }
}

extension Error {

    var errorCode: Int {
        return (self as! MyError).code
    }

    var failureReason: String? {
        return (self as! MyError).reason
    }

    var everythingYouWishFor: String {
        return (self as! MyError).wish
    }
}

...

throw MyError(locString: "Localized string", reason: "The reason", code: 12, wish: "Best wishes")
bauerMusic
  • 5,470
  • 5
  • 38
  • 53