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
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)
}
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
}
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.
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 */
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
}