Extracting type information for enum with CustomStringConvertible

Given the following:

enum Error: Swift.Error {
    case otherError(returnCode: Int32, reasonCode: Int32)
}

[...]

throw Error.otherError(returnCode: returnCode, reasonCode: reasonCode)

The throw will (with some info elided) essentially give me the following:

Fatal error: [...]: ICSF.Error.otherError(returnCode: 8, reasonCode: 10012):

I then added the following:

extension Error: CustomStringConvertible {	
    var description: String {
        switch self {
        case .otherError(let rtnCd, let rsnCd):
            return "ICSF.Error.otherError(returnCode: \(rtnCd), reasonCode: \(rsnCd) [\(String(rsnCd, radix: 16, uppercase: true))])"
        } 
    }
}

This gives me both the decimal and hex values for reasonCode, as follows:

Fatal error: [...]: ICSF.Error.otherError(returnCode: 8, reasonCode: 10012 [271C]):

My question is, is there some property or something I can use so as to not require me to hard code the "type" string "ICSF.Error.otherError"?

I tried "\(self.self)" and that simply crashed and burned during runtime, not even printing "Fatal error" or anything.

I use type(of: self) in many of my CustomStringConvertible implementations.

let foo = Foo()
print(“\(type(of: foo))(name: \(foo.name))”)

// Foo(name: Jacob)
\(type(of: self))

just gives me "Error"

You could just generate it yourself using a separate calculated variable:

extension Error: CustomStringConvertible {
    var errorInfo: String {
        let errorName: String
        var errorData: [String] = []
        switch self {
        case .otherError(let rtnCd, let rsnCd):
            errorName = "otherError"
            errorData.append("returnCode: \(rtnCd)")
            errorData.append("reasonCode: \(rsnCd) [\(String(rsnCd, radix: 16, uppercase: true))]")
        }

        return ".\(errorName)(\(errorData.joined(separator: ", ")))"
    }

    var description: String {
        return "\(type(of: self))\(errorInfo)"
        }
    }
}

let error = Error.otherError(returnCode, reasonCode)
print(error) // Error.otherError(returnCode: 55, reasonCode: 77 [4D])

You don't need to implement a custom description, or in fact anything.

  • String(describing:) gives you the description of a value if its type conforms to CustomStringConvertible or a suitable magical default otherwise.
  • String(reflecting:) gives you the debugDescription of a value if the type conforms to CustomDebugStringConvertible or a suitable magical default otherwise.

In this case, you want a description for debugging, and the compiler already gives you exactly what you want magically:

enum Error: Swift.Error {
  case otherError(returnCode: Int32, reasonCode: Int32)
}

let error = Error.otherError(returnCode: 8, reasonCode: 10012)
String(reflecting: error)
// "__lldb_expr_1.Error.otherError(returnCode: 8, reasonCode: 10012)"

I need it because I want the reasonCode displayed in both decimal and hex. Am I misunderstanding?

Ah. You can get just the portion of the debugging description for the type from String(reflecting: type(of: error)).

The specific case is part of the value and not part of the type, so of course if you are intent on customizing how the value is printed by conforming to CustomStringConvertible, you will have to provide that yourself (since, after all, that is the entire point of conforming to CustomStringConvertible), just as you must the labels for each associated value.

1 Like

That works for the type part. Thanks.

Let me ask this. Is there way to customize the "description" for a particular property only, rather than for an entire type? In this example I want to customize the description for reasonCode but not for returnCode.

FWIW, here is my current version:

public enum CryptoError: Error {
    case keyLabelNotFound(returnCode: Int32, reasonCode: Int32)
    case otherError(returnCode: Int32, reasonCode: Int32)
}

extension CryptoError: CustomStringConvertible {
    var caseDescription: String {
        switch self {
        case .keyLabelNotFound:
             return ".keyLabelNotFound"
        case .otherError:
            return ".otherError"
        }
    }
	
    public var description: String {
        var s: [String] = []
        let s1 = String(reflecting: type(of: self)) + caseDescription
        switch self {
        case .keyLabelNotFound(let rtnCd, let rsnCd),
             .otherError(let rtnCd, let rsnCd):
            s.append(s1 + "(returnCode: \(rtnCd),")
            s.append("reasonCode: \(rsnCd)")
            s.append("[\(String(rsnCd, radix: 16, uppercase: true))])")
        }
        return s.joined(separator: " ") 
    }
}

Additional improvement suggestions are welcome.