Addev
Addev

Reputation: 32243

How to handle Void success case with Result lib (success/failure)

Introduction:

I'm introducing a Result framework (antitypical) in some points of my app. In example, given this function:

func findItem(byId: Int, completion: (Item?,Error?) -> ());

foo.findItem(byId: 1) { item, error in
   guard let item = item else {
       // Error case
       handleError(error!)
       return;
   }
   // Success case
   handleSuccess(item)
}

I implement it this way with Result:

func findItem(byId: Int, completion: Result<Item,Error>) -> ());

foo.findItem(byId: 1) { result in
   swith result {
      case let success(item):
         // Success case
         handleSuccess(item)
      case let failure(error):
         // Error case
         handleError(error!)
   }
}

Question What is the correct way of implementing a result where the success case returns nothing?. Something like:

func deleteItem(byId: Int, completion: (Error?) -> ());

foo.deleteItem(byId: 1) { error in
   if let error = error {
       // Error case
       handleError(error)
       return;
   }
   // Success case
   handleSuccess()
}

In java I would implement a Result whats the correct way to do this in Swift

Upvotes: 23

Views: 11157

Answers (4)

Rob Napier
Rob Napier

Reputation: 299355

The best way is exactly what you've done: Error? where nil indicates success. It's quite clear and simple.

That said, another answer (and one that I've used) is exactly in your question: "How to handle Void success case with Result." The success case passes Void, so pass Void:

Result<Void, Error>

"Void" doesn't mean "returns nothing." It's a type in Swift, a type that has exactly one value: the empty tuple (). That also happens to be the type:

public typealias Void = ()

As a matter of convention, we use Void to mean the type, and () to mean the value. The one thing that's a bit strange about using Void this way in a Result is the syntax. You wind up with something like:

return .success(())

The double-parentheses are a little ugly and slightly confusing. So even though this is nicely parallel to other Result-using code, I typically just use Error? in this case. If I had a lot of it, though, I'd consider creating a new type for it:

enum VoidResult {
    case success
    case failure(Error)
}

Upvotes: 72

YanSte
YanSte

Reputation: 10839

You can add this extension, to simplify your life.

public extension Result where Success == Void {
    
    /// A success, storing a Success value.
    ///
    /// Instead of `.success(())`, now  `.success`
    static var success: Result {
        return .success(())
    }
}


// Now
return .success

Gists

Upvotes: 10

Reimond Hill
Reimond Hill

Reputation: 4760

I found Rob's answer really interesting and smart. I just want to contribute with a possible working solution to help others:

enum VoidResult {
    case success
    case failure(Error)
}

/// Performs a request that expects no data back but its success depends on the result code
/// - Parameters:
///   - urlRequest: Url request with the request config
///   - httpMethodType: HTTP method to be used: GET, POST ...
///   - params: Parameters to be included with the request
///   - headers: Headers to be included with the request
///   - completion: Callback trigered upon completion
func makeRequest(url: URL,
                 httpMethodType: HTTPMethodType,
                 params: [String:Any],
                 headers: [String:String],
                 completion: @escaping (VoidResult) -> Void){
    let alamofireHTTPMethod = httpMethodType.toAlamofireHTTPMethod()
    
    let parameterEncoder: ParameterEncoding
    switch alamofireHTTPMethod {
    case .get:
        parameterEncoder = URLEncoding.default
    case .post:
        parameterEncoder = JSONEncoding.default
    default:
        parameterEncoder = URLEncoding.default
    }
    
    Log.d(message: "Calling: \(url.absoluteString)")
    AF.request(url,
               method: alamofireHTTPMethod,
               parameters: params,
               encoding:parameterEncoder,
               headers: HTTPHeaders(headers)).response { response in
                guard let statusCode = response.response?.statusCode,
                    (200 ..< 300) ~= statusCode else {
                        completion(.failure(NetworkFetcherError.networkError))
                        return
                }
                completion(.success)
                
    }
    
  }

Upvotes: 1

KKRocks
KKRocks

Reputation: 8322

Try this

Note this is example you can change as per your test

typealias resultHandler = (_ responseItems: AnyObject, _ error: Error) -> Void

func deleteItem(byId: Int, completion: resultHandler){
       completion(Items, error) 
 }

Calling

self.deleteItem(byId: 1) { (result, error) in
            if error ==nil{

            }
        }

Upvotes: -3

Related Questions