Simplest way to throw an error/exception with a custom message in Swift?
IosSwiftIos Problem Overview
I want to do something in Swift that I'm used to doing in multiple other languages: throw a runtime exception with a custom message. For example (in Java):
throw new RuntimeException("A custom message here")
I understand that I can throw enum types that conform to the ErrorType protocol, but I don't want to have to define enums for every type of error I throw. Ideally, I'd like to be able mimic the example above as closely as possible. I looked into creating a custom class that implements the ErrorType protocol, but I can't even figure out that what that protocol requires. Ideas?
Ios Solutions
Solution 1 - Ios
The simplest approach is probably to define one custom enum
with just one case
that has a String
attached to it:
enum MyError: ErrorType {
case runtimeError(String)
}
Or, as of Swift 4:
enum MyError: Error {
case runtimeError(String)
}
Example usage would be something like:
func someFunction() throws {
throw MyError.runtimeError("some message")
}
do {
try someFunction()
} catch MyError.runtimeError(let errorMessage) {
print(errorMessage)
}
If you wish to use existing Error
types, the most general one would be an NSError
, and you could make a factory method to create and throw one with a custom message.
Solution 2 - Ios
The simplest way is to make String
conform to Error
:
extension String: Error {}
Then you can just throw a string:
throw "Some Error"
To make the string itself be the localizedString
of the error you can instead extend LocalizedError
:
extension String: LocalizedError {
public var errorDescription: String? { return self }
}
Solution 3 - Ios
@nick-keets's solution is most elegant, but it did break down for me in test target with the following compile time error:
Redundant conformance of 'String' to protocol 'Error'
Here's another approach:
struct RuntimeError: Error {
let message: String
init(_ message: String) {
self.message = message
}
public var localizedDescription: String {
return message
}
}
And to use:
throw RuntimeError("Error message.")
Solution 4 - Ios
Swift 4:
As per:
https://developer.apple.com/documentation/foundation/nserror
if you don't want to define a custom exception, you could use a standard NSError object as follows:
import Foundation
do {
throw NSError(domain: "my error domain", code: 42, userInfo: ["ui1":12, "ui2":"val2"] )
}
catch let error as NSError {
print("Caught NSError: \(error.localizedDescription), \(error.domain), \(error.code)")
let uis = error.userInfo
print("\tUser info:")
for (key,value) in uis {
print("\t\tkey=\(key), value=\(value)")
}
}
Prints:
Caught NSError: The operation could not be completed, my error domain, 42
User info:
key=ui1, value=12
key=ui2, value=val2
This allows you to provide a custom string (the error domain), plus a numeric code and a dictionary with all the additional data you need, of any type.
N.B.: this was tested on OS=Linux (Ubuntu 16.04 LTS).
Solution 5 - Ios
Check this cool version out. The idea is to implement both String and ErrorType protocols and use the error's rawValue.
enum UserValidationError: String, Error {
case noFirstNameProvided = "Please insert your first name."
case noLastNameProvided = "Please insert your last name."
case noAgeProvided = "Please insert your age."
case noEmailProvided = "Please insert your email."
}
Usage:
do {
try User.define(firstName,
lastName: lastName,
age: age,
email: email,
gender: gender,
location: location,
phone: phone)
}
catch let error as User.UserValidationError {
print(error.rawValue)
return
}
Solution 6 - Ios
Simplest solution without extra extensions, enums, classes and etc.:
NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()
Solution 7 - Ios
In case you don't need to catch the error and you want to immediately stop the application you can use a fatalError:
fatalError ("Custom message here")
Solution 8 - Ios
Based on @Nick keets answer, here is a more complete example:
extension String: Error {} // Enables you to throw a string
extension String: LocalizedError { // Adds error.localizedDescription to Error instances
public var errorDescription: String? { return self }
}
func test(color: NSColor) throws{
if color == .red {
throw "I don't like red"
}else if color == .green {
throw "I'm not into green"
}else {
throw "I like all other colors"
}
}
do {
try test(color: .green)
} catch let error where error.localizedDescription == "I don't like red"{
Swift.print ("Error: \(error)") // "I don't like red"
}catch let error {
Swift.print ("Other cases: Error: \(error.localizedDescription)") // I like all other colors
}
Originally published on my swift blog: http://eon.codes/blog/2017/09/01/throwing-simple-errors/
Solution 9 - Ios
I like @Alexander-Borisenko's answer, but the localized description was not returned when caught as an Error. It seems that you need to use LocalizedError instead:
struct RuntimeError: LocalizedError
{
let message: String
init(_ message: String)
{
self.message = message
}
public var errorDescription: String?
{
return message
}
}
See this answer for more details.
Solution 10 - Ios
First, let's see few usage examples, then how to make those samples work (Definition).
Usage
do {
throw MyError.Failure
} catch {
print(error.localizedDescription)
}
Or more specific style:
do {
try somethingThatThrows()
} catch MyError.Failure {
// Handle special case here.
} catch MyError.Rejected {
// Another special case...
} catch {
print(error.localizedDescription)
}
Also, categorization is possible:
do {
// ...
} catch is MyOtherErrorEnum {
// If you handle entire category equally.
} catch let error as MyError {
// Or handle few cases equally (without string-compare).
switch error {
case .Failure:
fallthrough;
case .Rejected:
myShowErrorDialog(error);
default:
break
}
}
Definition
public enum MyError: String, LocalizedError {
case Failure = "Connection fail - double check internet access."
case Rejected = "Invalid credentials, try again."
case Unknown = "Unexpected REST-API error."
public var errorDescription: String? { self.rawValue }
}
Pros and Cons
Swift defines error
variable automatically, and a handler only needs to read localizedDescription
property.
But that is vague, and we should use "catch MyError.Failure {}
" style instead (to be clear about what-case we handle), although, categorization is possible as shown in usage example.
-
Teodor-Ciuraru's answer (which's almost equal) still needs a long manual cast (like "
catch let error as User.UserValidationError { ... }
"). -
The accepted categorization-enum approach's disadvantages:
- Is too vague as he comments himself, so that catchers may need to compare
String
message!? (just to know exact error). - For throwing same more than once, needs copy/pasting message!!
- Also, needs a long phrase as well, like "
catch MyError.runtimeError(let errorMessage) { ... }
".
- Is too vague as he comments himself, so that catchers may need to compare
-
The NSException approach has same disadvantages of categorization-enum approach (except maybe shorter catching paragraph), also, even if put in a factory method to create and throw, is quite complicated.
Conclusion
This completes other existing solutions, by simply using LocalizedError
instead of Error
, and hopefully saves someone from reading all other posts like me.
(My laziness sometimes causes me a lot of work.)
Testing
import Foundation
import XCTest
@testable import MyApp
class MyErrorTest: XCTestCase {
func testErrorDescription_beSameAfterThrow() {
let obj = MyError.Rejected;
let msg = "Invalid credentials, try again."
XCTAssertEqual(obj.rawValue, msg);
XCTAssertEqual(obj.localizedDescription, msg);
do {
throw obj;
} catch {
XCTAssertEqual(error.localizedDescription, msg);
}
}
func testThrow_triggersCorrectCatch() {
// Specific.
var caught = "None"
do {
throw MyError.Rejected;
} catch MyError.Failure {
caught = "Failure"
} catch MyError.Rejected {
caught = "Successful reject"
} catch {
caught = "Default"
}
XCTAssertEqual(caught, "Successful reject");
}
}
Other tools:
#1 If implementing errorDescription
for each enum
is a pain, then implement it once for all, like:
extension RawRepresentable where RawValue == String, Self: LocalizedError {
public var errorDescription: String? {
return self.rawValue;
}
}
#2 What if we need additional context, like FileNotFound
with file-path associated? see my other post for that:
https://stackoverflow.com/a/70448052/8740349
>Basically, copy and add LocalizedErrorEnum
from above link into your project once, and reuse as many times as required with associative-enums.
Solution 11 - Ios
Throwing code should make clear whether the error message is appropriate for display to end users or is only intended for developer debugging. To indicate a description is displayable to the user, I use a struct DisplayableError
that implements the LocalizedError
protocol.
struct DisplayableError: Error, LocalizedError {
let errorDescription: String?
init(_ description: String) {
errorDescription = description
}
}
Usage for throwing:
throw DisplayableError("Out of pixie dust.")
Usage for display:
let messageToDisplay = error.localizedDescription
Solution 12 - Ios
First, let's see LocalizedErrorEnum
enum's usage examples, then how to make those samples work (in Sorce-code section).
Usage
Definition:
public enum MyError: LocalizedErrorEnum {
case FileNotFound(String = "Failed to find file.", file: String)
case Connection(String = "Connection fail - double check internet access.")
}
>The first argument is treated as message (in LocalizedErrorEnum
enum).
Trigger:
do {
let path = "/path/to/file.txt";
throw MyError.FileNotFound(
file: path
);
} catch {
print(error.localizedDescription);
}
Output:
Failed to find file. {
file: /path/to/file.txt
}
Requirements (background)
#1 Firstly, I want messages without copy/pasting, and that with ability to catch
a group of different error-cases, without listing each-case (solution, enum
is pretty unique without copy/paste need, and each enum can be considered another group).
#2 Secondly, some errors like "FileNotFound" need to have variable context/details, like for file-path (but Raw-Value enum
does not support instance-variables, and unlike #1, the built-in enum
is not the solution).
#3 Lastly, I want to be able to catch each case separately, NOT catching entire struct
and/or class
then doing switch
inside the catch
, and want to avoid forgeting the rethrow of cases we don't handle.
Source-code (solution meeting requirements)
Simply, copy and add LocalizedErrorEnum
from below into your project once, and reuse as many times as required with associative-enums.
public protocol LocalizedErrorEnum: LocalizedError {
var errorDescription: String? { get }
}
extension LocalizedErrorEnum {
public var errorDescription: String? {
if let current = Mirror(reflecting: self).children.first {
let mirror = Mirror(reflecting: current.value);
// Initial error description.
let message = mirror.children.first?.value as? String
?? current.label ?? "Unknown-case";
var context = "";
// Iterate additional context.
var i = 0;
for associated in mirror.children {
if i >= 1 {
if let text = associated.value as? String {
context += "\n ";
if let label: String = associated.label {
context += "\(label): "
}
context += text;
}
}
i += 1;
}
return context.isEmpty ? message : (
message + " {" + context + "\n}"
);
}
return "\(self)";
}
}
>Note that as mentioned on my profile, reusing above code does not need attribution (and allows Apache 2.0 license). > >See also my other answer if you don't need additional context-variable with error (or for a comparison with other approaches).
Solution 13 - Ios
I would like to suggest a variation of some of the proposed solutions:
public enum MyError: Error {
var localizedDescription: String {
get {
switch(self) {
case .network(let message, let code):
return "\(message) (\(code))"
case .invalidInput(message: let message):
return message
}
}
}
case network(message: String, code: Int)
case invalidInput(message: String)
}
It's a little more work to create but it provides the best of all worlds:
- It's an
enum
so it can be used in aswitch
statement. - All the errors must be created with a message that can be a different one even for the same types of error (unlike enums that extend
String
) - It provides the message under the
localizedDescription
that every developer is expecting.