I have created a Result enum to propagate the result of a REST API call.
enum Result {
case success([AnyObject])
case failure(APIError)
}
I also have a Error enum to handler errors
enum APIError: Error {
case requestFailed(String)
case invalidData(String)
case invalidQuery(String)
case invalidURL(String)
}
I send the error in a completion closure like
completion(.failure(.invalidURL("Invalid URL")))
How can I access this string in a if case scenario?
I am trying to do something like
if case .failure(let res) = result /*, case res.invalidQuery(let invalid) */ {
print(res)
}
How can I achieve this?
If what you're trying to do is having two cases on the same line, you can do it like so:
if case .failure(let error) = result, case .invalidQuery(let message) = error {
print(message)
}
Related
I am studying networking (Alamofire).
And in his pet project on Viper architecture.
I am making a get request and getting a to-do list from a local server.
The data is returned to me successfully.
But I just can't figure out how to get them and transfer them to Interactor...
I want my fetchToDos method to return an array. But I keep making mistakes.
func fetchToDos() -> [ToDo]? { // <- My mistake is clearly here
let request = Session.default.request("http://localhost:3003/")
request.responseDecodable(of: ToDos.self) { (response) in
switch response.result {
case .success(let tasks):
print("SUCCESS to FETCH JSON: \(tasks)")
case .failure(let error):
print("FAILED to FETCH JSON: \(error.localizedDescription)")
}
}
}
you are using an asynchronous function, and so one way to get something out
of it when it is finished, is to use a completion handler, something like this:
(note you need to do the error checking etc...before you can use this for real)
class ToDoNetworking {
func fetchToDos(completion: #escaping ([ToDo] -> Void)) { // <- try this
let request = Session.default.request("http://localhost:3003/")
request.responseDecodable(of: [ToDos].self) { (response) in
switch response.result {
case .success(let tasks):
print("SUCCESS to FETCH JSON: \(tasks)")
completion(tasks) // <-- assuming tasks is [ToDo]
case .failure(let error):
print("FAILED to FETCH JSON: \(error.localizedDescription)")
completion([])
}
}
}
}
What is the type of data being returned by the network call? If it's an array of ToDo objects and your ToDo object supports Codable, then it's likely you want:
request.responseDecodable(of: [ToDo].self)
you pass in the type of object that you want to decode, which it sounds like, is an array of ToDo objects, hence [ToDo].self.
How can I get a value that is derived from Result's success value in the case of success or from an error value in the case of failure, without having to create a temporary variable?
Here's a working but verbose example of what I'm trying to do:
let result = Result<Int, Error>.success(1)
let displayText: String
switch result {
case let .success(value): displayText = String(value)
case let .failure(error): displayText = "Result not available. \(error)"
}
updateDisplayText(displayText)
I could get something close to what by converting the result to an optional value in the desired format, testing it with try operator, and using the nil-coalescing operator to provide the fallback.
updateDisplayText((try? result.map{ v in String(v) }.get()) ?? "Result not available.")
However, that approach doesn't access the failure's error. It also seems overly complex. Is there an approach that works with a single expression, similar to what can be done with an F# match expression?
There are no "match expressions" in Swift. As far as I know, if you want to match against enums with associated values, you must use some kind of statement, whether that's an if case or switch or whatever.
However, no one can stop you from putting that statement into a method, which you can then put inside an extension!
extension Result {
func mapBoth<T>(success: (Success) -> T, error: (Failure) -> T) -> T {
switch self {
case .success(let s):
return success(s)
case .failure(let e):
return error(e)
}
}
}
updateDisplayText can then be called like this:
updateDisplayText(result.mapBoth(
success: { String($0) },
failure: { "Result not available. \($0)" }
))
I started using Result as a return type and I mostly like it but when I have nothing to return for success then I am at a loss about what to do in that case statement. Any hints?
All that I could think of was let _ = 0
func createAppDirectory(_ searchPath: FileManager.SearchPathDirectory) -> Result<Void,Error>
...
switch createAppDirectory(searchPath) {
case .success(_): let _ = 0
case .failure(let error): return .failure(error)
}
I am beginning to think that maybe Result isn't a good fit when the success type is Void.
BTW createAppDirectory just creates Library/Application Support/<Bundle ID>. There is no value to return if it succeeds.
Use a break statement:
switch createAppDirectory(searchPath) {
case .success:
break
case .failure(let error): return .failure(error)
}
EDIT:
As Mojtaba pointed out, if you're not going to use the associated value for a particular case of your enum you can simply skip it. I've edited my answer above to remove the (_) from the .success case
Just ignore it:
case .success: break
Also if you want absolutely no overwork when it isn't failure case, gaurd it at the very beginning of the scope:
guard case .failure(let error) = createAppDirectory(searchPath) else { return <#Value#> }
If only the error is significant Result is inappropriate.
A better pattern is to throw the error and return nothing
func createAppDirectory(_ searchPath: FileManager.SearchPathDirectory) throws
...
do {
try createAppDirectory(searchPath)
} catch { print(error)}
Return simple void result,
switch createAppDirectory(searchPath) {
case .success: return .success(())
case .failure(let error): return .failure(error)
}
I have this code in a ServiceClient. It handles service-level calls, like signIn(user, password, completion), listObjects(completion), addObject(objectID, content, completion), getObject(id, completion) etc. It contains (but doesn't subclass) an APIClient, which performs only basic HTTPS services like perform(request, completion).
I don't really want the controller that sits above this to deal with 404s as success, which means trapping the error in ServiceClient. So the idea is APIClient deals with networking errors whereas ServiceClient deals with unexpected HTTP results.
So I end up with this in ServiceClient, where errors like invalidURL are converted from an APIClient enum to a ServiceClient enum:
apiClient.perform(request) {result in
switch result {
case .success(let data):
guard data.statusCode == 200 else {
completion(.failure(.badResponse))
return
}
completion(.success(data))
case .failure(let error):
switch error {
case .invalidURL:
completion(.failure(.invalidURL))
case .requestFailed:
completion(.failure(.requestFailed))
case .decodingFailure:
completion(.failure(.decodingFailure))
}
}
}
I think in this case I'll just make APIClient handle invalid HTTP status codes, but what's the more general solution to this? At some point I'll want different error codes for different service clients, at which point this becomes a problem again.
I suggest using Int type enumeration for both ServiceClient and APIClient.
As I understood this is your custom enumerations.
So, assuming you have ServiceClientError and APIClientError you can implement them using this way:
enum ServiceClientError: Int {
case invalidURL, requestFailed, decodingFailure
}
enum APIClientError: Int {
case invalidURL, requestFailed, decodingFailure
}
You can create your custom conversion method:
extension ServiceClientError {
static func create(from apiClientError: APIClientError) -> ServiceClientError {
return ServiceClientError(rawValue: apiClientError.rawValue)
}
}
Wanted function:
apiClient.perform(request) {result in
switch result {
case .success(let data):
guard data.statusCode == 200 else {
completion(.failure(.badResponse))
return
}
completion(.success(data))
case .failure(let error):
guard let serviceClientError = ServiceClientError.create(from: error) else {
/// Handle incorrect behavior
fatalError("Wrong enumeration mapping")
return
}
completion(.failure(serviceClientError))
}
}
CNContactStore's executeSaveRequest(_:) method throws an error according to the documentation.
I am trying to catch this error in a do/catch, but I cannot figure out what error I need to catch.
do{
try store.executeSaveRequest(saveRequest)
} catch *???* {
//alert the user
}
What is supposed to replace the ???'s in the code above?
You have a few options actually.
Catch any error without knowing the error
catch {...}
Catch any error with the specific error message
catch let error { // Use error }
Use exhaustive catch clauses to handle specific errors using the CNErrorCode enum.
enum CNErrorCode : Int {
case CommunicationError
case DataAccessError
case AuthorizationDenied
case RecordDoesNotExist
case InsertedRecordAlreadyExists
case ContainmentCycle
case ContainmentScope
case ParentRecordDoesNotExist
case ValidationMultipleErrors
case ValidationTypeMismatch
case ValidationConfigurationError
case PredicateInvalid
case PolicyViolation
}