Get string message from catch error in do/try/catch with Swift - swift

I have 2 classes, 1 is where view controller of form and the other is custom class to validate all forms in my app.
In the enum of the list of possible errors I have these:
enum ValidateErrors: ErrorType{
case Empty(desc: String)
case WrongFormat(desc: String)
}
and inside the validate method from the same class I have these:
guard email.characters.count > 0 else { throw ValidateErrors.Empty(desc:"Empty email.") }
When I do the do/try/catch in the view controller, I need to show an error message with "Empty email." but it show: Empty("Empty email.")
These is the code from the view controller where I make the do/try/catch
do{
try ValidateData.validateEmail(emailTextView.text!)
print("Campo Valido")
}catch let error{
print(error)
}

I found a solution here:
https://developer.apple.com/documentation/swift/error
And in Swift 4 try this for your example:
enum ValidateErrors: Error
{
case Empty(desc: String)
case WrongFormat(desc: String)
}
do
{
try ValidateData.validateEmail(emailTextView.text!)
print("Campo Valido")
}
catch ValidateErrors.Empty(desc: let error)
{
print(error)
}

You might try print(error.desc)

Related

Getting error when trying to use Result type with delegate

Im tring to make a network call and instead of using callback I try to use delegate instead.using Result type where .Sucsess is T: Decodable and .failure is Error. passing my model in the .Sucsess is working but when trying to pass an error I get a compile error "Generic parameter 'T' could not be inferred" what am I missing ?
protocol NetworkServiceDelegate: class {
func decodableResponce<T: Decodable>(_ result: Result<T, NetworkError>)
}
let dataTask:URLSessionTask = session.dataTask(with: url) { (dataOrNil, responceOrNil, errOrNil) in
if let error = errOrNil {
switch error {
case URLError.networkConnectionLost,URLError.notConnectedToInternet:
print("no network connection")
self.delegate?.decodableResponce(Result.failure(.networkConnectionLost))
case URLError.cannotFindHost, URLError.notConnectedToInternet:
print("cant find the host, could be to busy, try again in a little while")
case URLError.cancelled:
// if cancelled with the cancelled method the complition is still called
print("dont bother the user, we're doing what they want")
default:
print("error = \(error.localizedDescription)")
}
return
}
guard let httpResponce:HTTPURLResponse = responceOrNil as? HTTPURLResponse
else{
print("not an http responce")
return
}
guard let dataResponse = dataOrNil,
errOrNil == nil else {
print(errOrNil?.localizedDescription ?? "Response Error")
return }
do{
//here dataResponse received from a network request
let decoder = JSONDecoder()
let modelArray = try decoder.decode([Movie].self, from:
dataResponse) //Decode JSON Response Data
DispatchQueue.main.async {
self.delegate?.decodableResponce(Result.success(modelArray))
}
} catch let parsingError {
print("Error", parsingError)
}
print("http status = \(httpResponce.statusCode)")
print("completed")
}
this line generates the error, it dosnt metter if I pass my enum that cumfirms to Error or trying to pass the error from the dataTask
self.delegate?.decodableResponce(Result.failure(.networkConnectionLost))
Well, you have two problems, having to do with the question "what type is this?" Swift is very strict about types, so you need to get clear about that.
.networkConnectionLost is not an Error. It is an error code. You need to pass an Error object to a Result when you want to package up the error. For example, URLError(URLError.networkConnectionLost) is an Error.
The phrase Result<T, NetworkError> makes no sense. Result is already a generic. Your job is to resolve the generic that it already is. You do that by specifying the type.
So for example, you might declare:
func decodableResponce(_ result: Result<Decodable, Error>)
It is then possible to say (as tests):
decodableResponce(.failure(URLError(URLError.networkConnectionLost)))
or (assuming Movie is Decodable):
decodableResponce(.success([Movie()]))
That proves we have our types right, and you can proceed to build up your actual code around that example code.

Specifying custom error type within the Result type in Swift 5

I am trying to create a Result variable with a custom error type with the builtin Result type in Foundation for Swift 5, but I can't quite get the type system to understand the kind of error I want to throw.
The code below does not compile.
import Foundation
enum CustomError: String, Error {
case somethingBadHappened
}
struct Model {
let value: Int
}
class Request {
func execute(number: Int, completion: #escaping (Result<Model, CustomError>) -> Void) {
let result = Result { () throws -> Model in
if (number < 20) {
throw CustomError.somethingBadHappened
} else {
return Model(value: number)
}
}
// compiler complains here about: Cannot convert value of type 'Result<Model, Error>' to expected argument type 'Result<Model, CustomError>'
completion(result)
}
}
let request = Request()
request.execute(number: 19) { result in
switch result {
case .success(let value): print("Succeded with \(value)")
case .failure(let error): print("Failed with \(error)")
}
}
Changing the signature of the completion closure to completion: #escaping (Result<Model, Error>) -> Void works, but then I am not using the custom error type.
How can I make the type system understand I would like to use the custom error type?
Apologies for giving a second answer, but there needs to be a corrective for fphilipe's answer.
You can use init(catching:) to form the Result and yet return it as a Result<Model, CustomError>. That is what mapError is for! Like this:
enum CustomError: String, Error {
case somethingBadHappened
}
struct Model {
let value: Int
}
class Request {
func execute(number: Int, completion: #escaping (Result<Model, CustomError>) -> Void) {
let result = Result { () throws -> Model in
if (number < 20) {
throw NSError()
} else {
return Model(value: number)
}
}.mapError { err in
return CustomError.somethingBadHappened
}
completion(result)
}
}
I have to throw something in order to form the initial Result<Model, Error>, so I just throw an NSError as a kind of placeholder. But then mapError comes along and transforms this into a Result<Model, CustomError>. The power of mapError is that it changes only what happens in case of failure.
Thus we are able to keep the original form of the code.
Changing the signature of the completion closure to completion: #escaping (Result<Model, Error>) -> Void works, but then I am not using the custom error type.
Yes, you are! Change the signature in exactly that way, so that you compile, and then run your code. When we get to this line:
case .failure(let error): print("Failed with \(error)")
... we print "Failed with somethingBadHappened". That proves that your CustomError.somethingBadHappened instance came through just fine.
If the problem is that you want to separate out your CustomError explicitly, then separate it out explicitly as you catch it:
case .failure(let error as CustomError): print(error)
default : fatalError("oops, got some other error")
Or if you want to winnow it down still further and catch only the .somethingBadHappened case, specify that:
case .failure(CustomError.somethingBadHappened): print("Something bad happened")
default : fatalError("oops, got some other error")
Those examples are artificial, but they demonstrate what they are intended to demonstrate — that your CustomError instance is coming through with full integrity.
Just create Result manually:
let result: Result<Model, CustomError>
if (number < 20) {
result = .failure(.somethingBadHappened)
} else {
result = .success(Model(value: number))
}
completion(result)

Confused on Error Handling in Swift 3

I got confused error handling in swift3. I try to do like "if XX function got error then try YY function"
Let me show you what I try:
class MyClass {
enum error: Error
{
case nilString
}
func findURL() {
do {
let opt = try HTTP.GET(url_adr!)
opt.start { response in
if let err = response.error {
print("error: \(err.localizedDescription)")
return //also notify app of failure as needed
}
do
{
/* This is func1. and got error. I want to if this function has error then go next function. */
try self.stringOperation(data: response.description)
}
catch{
print("doesn't work on func1. trying 2nd func")
self.stringOperation2(data:response.descritption)
}
}
} catch let error {
print("got an error creating the request: \(error)")
}
}
func stringOperation(data:String)throws -> Bool{
do{
/** 1 **/
if let _:String = try! data.substring(from: data.index(of: "var sources2")!){
print("its done")
}else{
throw error.nilString
}
IN 1: I got this fatal error on this line:
"fatal error: unexpectedly found nil while unwrapping an Optional value" and program crashed.
I googled error handling try to understand and apply to in my code. However not succeed yet. Can someone explain where did I wrong?
Additional info: I got String extension for .substring(from:...) , and .index(of:"str"). So these lines doesn't got you confused.
As a general rule, try avoiding using force unwrapping (!), where you have
if let _: String= try! data.substring...
Instead use
if let index = data.index(of: "var sources2"),
let _: String = try? data.substring(from: index) { ... } else { ... }
That way you remove the two force unwraps that may be causing your crash. You already have the if let protection for catching the nil value, so you can make the most of it by using the conditional unwrapping.

Is it possible to combine catches?

I'm trying to do this:
catch LocksmithError.Duplicate, LocksmithError.Allocate {...}
But I get an error at , saying :
Expected '{' after 'catch' pattern
Does this mean you can't combine cases like case expression2, expression3 :? Any reason it's made this way?
No, it's currently not possible to combine multiple patterns in a catch clause – the grammar (as detailed by the Swift Language Guide) only allows for a single pattern to match against:
catch-clause → catch pattern­opt ­where-clause­opt ­code-block­
Another possible solution to the ones proposed already, as long as your error enum is trivial (which LocksmithError appears to be), is to use a where clause after a binding pattern:
catch let error as LocksmithError where error == .Duplicate || error == .Allocate {
// ...
}
Given that you let your LocksmithError have some rawvalue type (e.g. Int), you could, in a single catch statement, bind the error thrown and use its rawValue to test for inclusion into one of several error cases (using the where clause after binding). E.g.:
enum FooError: Int, Error {
case err1 = 1, err2, err3, err4, err5
}
func foo(_ bar: Int) throws {
if let err = FooError(rawValue: bar) { throw err }
}
func tryFoo(_ bar: Int) {
do {
try foo(bar)
} catch let err as FooError where (1...4).contains(err.rawValue) {
print("Any of 1st through 4th error!")
} catch FooError.err5 {
print("5th error!")
} catch {}
}
tryFoo(1) // Any of 1st through 4th error!
tryFoo(4) // Any of 1st through 4th error!
tryFoo(5) // 5th error!
As suggested by #user28434 (thanks!), there's really no need to apply the rawValue restraint as the same method above could be used to direcly see if the binded err is a member of an array of given cases.
enum FooError: Error {
case err1, err2, err3
}
func foo(_ bar: Int) throws {
guard bar != 1 else { throw FooError.err1 }
guard bar != 2 else { throw FooError.err2 }
guard bar != 3 else { throw FooError.err3 }
}
func tryFoo(_ bar: Int) {
do {
try foo(bar)
} catch let err as FooError where [.err1, .err2].contains(err) {
print("1st or 2nd error!")
} catch FooError.err3 {
print("3rd error!")
} catch {}
}
tryFoo(1) // 1st or 2nd error!
tryFoo(2) // 1st or 2nd error!
tryFoo(3) // 3rd error!
This reduces to basically just a variation of the accepted answer (possibly useful for catch blocks covering more than just two cases, but in such cases, possibly the error enum should consider refactoring).
It's just a syntactical problem, it may be improved in Swift 4+.
Right now you can use this:
catch let locksmithError as LocksmithError {
switch locksmithError {
case .Duplicate, .Allocate:
// your code
…
}
}

Accessing code in Swift 3 Error

New in Xcode 8 beta 4, NSError is bridged to the Swift Error protocol type. This affects StoreKit when dealing with failed SKPaymentTransactions. You ought to check to be sure the error didn't occur because the transaction was cancelled to know whether or not to show an error message to the user. You do this by examining the error's code. But with Error instead of NSError, there is no code defined. I haven't been able to figure out how to properly get the error code from Error.
This worked in the previous version of Swift 3:
func failedTransaction(_ transaction: SKPaymentTransaction) {
if let transactionError = transaction.error {
if transactionError.code != SKErrorCode.paymentCancelled.rawValue {
//show error to user
}
}
...
}
Now that error is an Error not NSError, code is not a member.
Another option to access code and domain properties in Swift 3 Error types is extending it as follow:
extension Error {
var code: Int { return (self as NSError).code }
var domain: String { return (self as NSError).domain }
}
Now in Xcode 8 and swift 3 the conditional cast is always succeeds, so you need do following:
let code = (error as NSError).code
and check the code for your needs. Hope this helps
Casting to SKError seems to be working for me in xCode 8 and Swift 3...
guard let error = transaction.error as? SKError else {return}
switch error.code { // https://developer.apple.com/reference/storekit/skerror.code
case .unknown: break
case .paymentCancelled: break
case .clientInvalid: break
case .paymentInvalid: break
case .paymentNotAllowed: break
case .cloudServiceNetworkConnectionFailed: break
case .cloudServicePermissionDenied: break
case .storeProductNotAvailable: break
}
No need for rawValue.
This is correct (Apple's own tests use this approach):
if error._code == SKError.code.paymentCancelled.rawValue { ... }
On the other hand, casting to NSError will probably be deprecated soon:
let code = (error as NSError).code // CODE SMELL!!
if code == SKError.code.paymentCancelled.rawValue { ... }
Use
error._code == NSURLErrorCancelled
to match the error code.
A lot is changing. Here's update for Xcode 11.
if let skError = transaction.error as? SKError, skError.code == .paymentCancelled {
print("Cancelled")
}