Swift: How to cast error to generic struct - swift

I am being passed an Error at runtime. This error is actually a generic struct. I need to cast the error to this struct so I can get its details. How can I do this?
Code Example:
protocol MinorErrorType: Error {}
struct MajorError<T: MinorErrorType>: Error {
let minorError: T
}
enum SomeMinorError: MinorErrorType {
case error
}
func getName(_ error: Error) -> String {
"Some Error"
}
func getName<T: MinorErrorType>(_ error: MajorError<T>) -> String {
"MajorError"
}
func printName(_ error: Error) {
print(getName(error))
}
let error = MajorError<SomeMinorError>(minorError: .error)
printName(error)
// output:
// Some Error
You can see in the above code the generic getName is not called. If there is a solution where I only need to modify func printName that would be awesome.
Update: In production, I want to use this pattern for logging. I want a logger to be passed an error and be able to log MajorError's. I do not want to have to cast to MajorError<SomeMinorError> in getName as that would mean I would need to cast for all new implementations of MinorErrorType. This would make my logger need to know about too much information.
In the meantime, I used type erasure (here is a great article on the subject)
protocol MajorErrorType: Error {
func eraseToAnyMajorError() -> AnyMajorError
}
enum AnyMajorError {
case majorError(MinorErrorType)
}
protocol MinorErrorType: Error {}
struct MajorError<T: MinorErrorType>: Error {
let minorError: T
func eraseToAnyMajorError() -> AnyMajorError {
.majorError(minorError)
}
}
...
func printName(_ error: Error) {
if let majorError = (error as? MajorErrorType)?.eraseToAnyMajorError() {
print(getName(majorError))
else {
print(getName(error))
}
}
This lets the rest of my code use the generic structs (fun) and my logger in the dark about other error types.

Not sure if it helps, but I would use proper encapsulation to help with this issue. I.e. define getName as a function of your struct, enum, etc., so you can call it on appropriate type:
protocol MinorErrorType: Error {}
extension Error {
func getName() -> String {
"Some Error in extension"
}
}
struct MajorError<T: MinorErrorType>: Error {
let minorError: T
func getName() -> String {
"MajorError in struct"
}
}
enum SomeMinorError: MinorErrorType {
case error
}
Now inside printError you can control what to call:
func printName(_ error: Error) {
if let majorError = error as? MajorError<SomeMinorError> {
print(majorError.getName())
} else {
print(error.getName())
}
}
So if you call
let error = MajorError<SomeMinorError>(minorError: .error)
printName(error) // prints MajorError in struct
But you don't need printName function with it's casting at all: you can call getName(), and it will call getName from Error extension only if nothing else is defined:
let error = MajorError<SomeMinorError>(minorError: .error)
let smthElse = NSError(domain: "s", code: 123, userInfo: nil)
print(error.getName()) // prints MajorError in struct as before
print(smthElse.getName()) // prints Some Error in extension

You just need to switch your error. No need to create a another method:
func getName(_ error: Error) -> String {
switch error {
case is MajorError<SomeMinorError>: return "MajorError"
default: return "Some Error"
}
}
To access your error properties you can cast the error instead of checking its type:
func getName(_ error: Error) -> String {
switch error {
case let error as MajorError<SomeMinorError>:
return "\(error.minorError)"
default: return "Some Error"
}
}
Playground testing:
protocol MinorErrorType: Error {}
struct MajorError<T: MinorErrorType>: Error {
let minorError: T
}
enum SomeMinorError: MinorErrorType {
case minor
}
func getName(_ error: Error) -> String {
switch error {
case let error as MajorError<SomeMinorError>:
return "\(error.minorError)"
default: return "Some Error"
}
}
let error = MajorError<SomeMinorError>(minorError: .minor)
print(getName(error)) // "minor\n"
You can also give your error a RawValue:
enum SomeMinorError: String, MinorErrorType {
case minor
}
func getName(_ error: Error) -> String {
switch error {
case let error as MajorError<SomeMinorError>:
return error.minorError.rawValue
default: return "Some Error"
}
}

Related

How to get the string I put into a Error instance?

Basicly I got this,
// MyPackage.swift
enum Error: LocalizedError {
case general(String)
}
func foobar() throws {
throw Error.general("haha")
}
do {
try foobar()
} catch Error.general(let message) {
// will print "haha"
print(message)
}
And then in the unit test, I need to check if I got the exact same error,
import Quick
import Nimble
import MyPackage
class MySpec: QuickSpec {
override func spec() {
describe("") {
let input = "haha"
let count = 2
let expectation = Error.general("非纯数字字符串无法使用本方法")
context("输入_" + input) {
it("预期_" + expectation.localizedDescription) {
// got the error
// but expectation.localizedDescription was not what I looking for
expect(try numberStringByAddingZerosInto(input, tillReach: count))
.to(throwError(expectation))
}
}
}
}
}
It worked, but expectation.localizedDescription was not "haha", which made the name of the test case useless.
I also tried expectation.errorDescription with no luck.
Where could I get it?
And why is it like this?
override errorDescription var
enum Error: LocalizedError {
case general(String)
var errorDescription: String? {
switch self {
case .general(let errorMessage):
return errorMessage
}
}
}
Now, you can also write do-catch block by this
do {
try foobar()
} catch let error {
print(error.localizedDescription)
}

Compile Error when using singleton with generics and completionHandler

I have the following generic type:
public enum APIResult<T> {
case success(T)
case failure(NetworkError)
public var value: T? {
if case let .success(value) = self {
return value
}
return nil
}
public var error: NetworkError? {
if case let .failure(error) = self {
return error
}
return nil
}
}
And I have the following call that I make defined inside a singleton:
public func getGenericData<T>(urlEndPoint:String,completionHandler:#escaping(APIResult<T>)->()) {}
I am calling it using the following code:
APIManager.shared.getGenericData(urlEndPoint: "getuserprofile") { (result:Any) in
}
but swift refusing it saying:
Generic parameter 'T' could not be inferred.
What would be my problem?
You need to tell the compiler what kind of APIResult to expect, like this
getGenericData(urlEndPoint: "getuserprofile") { (result:APIResult<String>) in
}

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.

What is the best way to determine the type (actually subclass) of an Error thrown by a Swift function? [duplicate]

In Swift error handling - how do you know what error type is being thrown without looking at the implementation and how do you handle the case where ErrorType-derived error and NSError can be thrown by the method?
e.g.
Code does not show what type of error will be thrown.
public func decode(jwt: String) throws -> JWT {
return try DecodedJWT(jwt: jwt)
}
You can catch the thrown error to a variable and do runtime analysis of the variable. E.g., for some unknown implementation:
/* ---------------------- */
/* unknown implementation */
enum HiddenError: ErrorType {
case SomeError
}
class AnotherError : NSError { }
func foo() throws -> Int {
let foo = arc4random_uniform(3);
if foo == 0 {
throw HiddenError.SomeError
}
else if foo == 1 {
throw AnotherError(domain: "foo", code: 0, userInfo: [:])
}
else if foo == 2 {
throw NSError(domain: "foo", code: 0, userInfo: [:])
}
else {
return Int(foo)
}
}
/* ---------------------- */
Investigate the error as:
/* "External" investigation */
func bar() throws -> Int {
return try foo()
}
func fuzz() {
do {
let buzz = try bar()
print("Success: got \(buzz)")
} catch let unknownError {
print("Error: \(unknownError)")
print("Error type: \(unknownError.dynamicType)")
if let dispStyle = Mirror(reflecting: unknownError).displayStyle {
print("Error type displaystyle: \(dispStyle)")
}
}
}
fuzz()
/* Output examples:
Error: SomeError
Error type: HiddenError
Error type displaystyle: Enum
//
Error: Error Domain=foo Code=0 "(null)"
Error type: AnotherError
Error type displaystyle: Class
//
Error: Error Domain=foo Code=0 "(null)"
Error type: NSError
Error type displaystyle: Class */

Using Do/Catch in Swift

I am working on an app and want to get data back from a function. However sometimes data is missing or is different from the kind of that I want to retrieve. I am new to Swift and I can't find a way to write a function that does a little bit of processing and returns this data. When this data is missing, the function should give back a string "Not Found". Like this:
func processData(data:String) {
do {
//processing
var result = processedData
} catch {
var result = "Not Found"
}
return result
}
It would be very nice if somebody could help me.
You should check if the result is nil.
func processData(data: String?) -> String {
guard let result = data else {
return "Not Found"
  }
return result
}
The most concise way of doing it would be using the guard-let construct:
func processData(data: String?) -> String {
// Assuming getProcessedData(:) returns your processed data
guard let result = getProcessedData(data) else {
return "Not found"
}
return result
}
Also, your function is missing a return type. You must specify the return type like -> TYPE in all functions that return some value.
Those answer were written till mine are right. There is one way: with handler check get result and use by your point.
enum Errors: Error {
case noData
case unknownError
}
func progress(data: String?, completionHandler: #escaping (_ result: String? , _ error: Error?) -> Void ) {
guard let data = data else {
// Data is missing
throw nil, Errors.noData
}
// Do other things, and throw if necessary
result = data
return result, nil
}
// example of calling this function
process(data: "A data to process"){(result, error) -> Void in
//do any stuff
/*if error == nil {
}*/
}
A good practice in swift would be to use correctly the throws errors
This is an example inspired from yours :
enum Errors: Error {
case noData
case unknownError
}
func progress(data: String?) throws -> String {
guard let data = data else {
// Data is missing
throw Errors.noData
}
// Do other things, and throw if necessary
result = data
return result
}
do {
try process(data: "A data to process")
} catch {
print("An error occurred: \(error)")
}
You can try this code as is in a Swift Playgound
Your function needs to be explicit about returning something with e.g. -> String Also do-catch is for methods that can throw an error. It seems like you need to take a look at how to use optionals. Optionals can have a value or they can have no value.
fun processData(data: String) -> String {
var result: String?
// Do some processing and assign the result to result variable
guard let result = result else { return "Not Found" }
return result
}