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

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)
}

Related

websiteUp set to false, but does not print Failure

I am trying to learn Swift/Xcode and have come across an error that I can't explain.
Here is some code that should print the error code, but still prints Success even though I have changed websiteUp to false!
enum WebpageError: Error {
case success
case failure(Int)
}
func getWebpage(uRL: String, siteUp: Bool) throws -> String {
if siteUp == false {
WebpageError.failure(404)
}
return "Success"
}
let webpageURL = "http://apple.com"
let websiteUp = false
do {
let status = try getWebpage(uRL: webpageURL, siteUp: websiteUp)
print(status)
} catch {
print(error)
}
I can't spot what is wrong, as it all looks OK to me!
You are doing two silly things:
How can a case of an Error enum be a .success? It's for failures!
It isn't enough to say WebpageError.failure. You have to throw the error.
So:
enum WebpageError: Error {
case failure(Int) // <== look
}
func getWebpage(uRL: String, siteUp: Bool) throws -> String {
if !siteUp {
throw WebpageError.failure(404) // <== look
}
return "Success"
}
let webpageURL = "http://apple.com"
let websiteUp = false
do {
let status = try getWebpage(uRL: webpageURL, siteUp: websiteUp)
print(status)
} catch {
print(error)
}
Having said all that, let's go back to the question of why you included a .success case. Maybe your idea was to return a value signalling success or failure. That would be a Result. Like this:
enum WebpageError: Error {
case failure(Int)
}
func getWebpage(uRL: String, siteUp: Bool) -> Result<String,Error> {
return Result {
if !siteUp {
throw WebpageError.failure(404)
}
return "Success"
}
}
let webpageURL = "http://apple.com"
let websiteUp = false
let status = getWebpage(uRL: webpageURL, siteUp: websiteUp)
do {
try print(status.get())
} catch {
print(error)
}
That might be more sophisticated than you had in mind at this stage, but it's worth thinking about.

Swift: How to cast error to generic struct

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"
}
}

How to have Error Type inference for a throwing function in a protocol

Is there a way to create a throwing function with a specified Error sub type that will be thrown? Below code is an example of how I'd like it to be.
protocol ValidatorError: Error {
var somePrintableThingy: String { get }
}
protocol Validator {
associatedType T
var model: T { get set }
init(model: T)
func validate() throws where Error: ValidatorError
}
Example Use Case
class SomeModelErrorNotValidatorError: Error {
case invalidUsername
}
class SomeModelError: ValidatorError {
case invalidUsername
var somePrintableThingy: String {
switch self { case .invalidUsername: return "haha" }
}
}
class SomeModel {
var username: String = ""
init(username: String) { self.username = username }
}
class SomeModelValidator: Validator {
var model: SomeModel
init(model: SomeModel) { self.model = model }
func validate() throws {
guard self.model.username.isEmpty else {
// this line should be fine
throw SomeModelError.invalidUsername
// if i replace it with this line it should not be fine
throw SomeModelErrorNotValidatorError.invalidUsername
}
}
}
class SomeModelViewController: UIViewController {
// .. some other codes here
func buttonClicked(_ sender: Any? = nil) {
let model = SomeModel(username: self.txtUsername.text ?? "")
let validator = SomeModelValidator(model: model)
do {
try validator.validate()
} catch let error {
// since I already know that this is a `SomeModelError`
// I could just do this
print(error.somePrintableThingy)
}
}
}
PS: The possible workaround for this is to create a non throwing function with a callback but I don't want to do that
PS PS: Another possible workaround for this would be to use a linting tool, but that's too much.

Throw an exception from a singleton initializer

I have a singleton that manages a set of data. If for some reason that data is unavailable I'd like the creation of the singleton to fail by throwing an exception. The compiler isn't a fan of marking the getter with throws NOTE: I realize I could handle this other ways but now I'm curious if it's even possible.
class Foo {
class var `default`: Foo {
let instance = Foo()
return instance
}
private init() throws {
// Throw if unable to load required data...
}
}
You can do it like this(code from my playground), downside every call of your singleton will have to be done with try.
enum FooError : Error {
case RuntimeError(String)
}
class Foo {
static func defaultFoo() throws -> Foo {
if let instance = Foo("Success") {
return instance
} else {
throw FooError.RuntimeError("Singleton is not created")
}
}
private init?(_ parameter: String?) {
if let _ = parameter {
print("Init Succeded")
} else {
print("Init failed")
return nil;
}
}
}
try Foo.defaultFoo()

How can I catch and log all errors?

I'm using some third-party functions, and I don't know what types of errors they can produce.
I want to catch and log all such errors, but ErrorType protocol does not have any members that could provide a meaningful message or error code. What can be done to log any type of errors properly?
do {
try someFunction()
} catch WellKnownError { // handle it }
} catch let err {
let message = ... // how can I get error type and some meaningful message from err?
NSLog(message)
}
I think it's not an option...
But you can try something like this...
//----------------------------------------
// Private API
//----------------------------------------
public enum CarError : ErrorType
{
case NeverTurnOn
case OutOfFuel
case Other(String)
}
public class Car
{
public func turnOn(newState:Int) throws -> Bool
{
if newState == 1
{
throw CarError.NeverTurnOn
}
else if case 2...5 = newState
{
throw CarError.OutOfFuel
}
else if newState == 10
{
throw CarError.Other("Something is gonna wrong")
}
else if newState == 11
{
throw CarError.Other(":(")
}
return false
}
}
Error Handling
func main()
{
let car = Car()
do {
try car.turnOn(4)
} catch CarError.NeverTurnOn {
print("Turn off")
} catch let err as NSError {
print(err.description)
} catch {
print("It's madness!!!")
}
}
main()
With this test... I had this result...
//Error Domain=trycatchtest.CarError Code=1 "The operation couldn’t be completed. (trycatchtest.CarError error 1.)"
//Program ended with exit code: 0
It's not the best way to solve your problem, but a new way to understand what is happening in background of this framework.
Now, asking a favor.
Please, let me now about the final result ;)
It's doesn't matter if is a good or bad result.