5

I am using generics and codable with URLSession.

When I receive a response from an API, I check the status is in the 200 - 299 range and decode the data like so

        guard let data = data, let value = try? JSONDecoder().decode(T.self, from: data) else {
            return completion(.error("Could not decode JSON response"))
        }
        completion(.success(value)) 

This is then passed off to the completion handler and everything is OK.

I have a new endpoint I must POST too however, this endpoint returns a 204 with no content body.

As such, I cannot decode the response, simply as I cannot pass in a type?

My completion handler expects

enum Either<T> {
    case success(T)
    case error(String?)
}

and switching on my response statusCode like so

   case 204:
        let value = String(stringLiteral: "no content")
        return completion(.success(value))

produces an error of

Member 'success' in 'Either<>' produces result of type 'Either', but context expects 'Either<>'

My APIClient is

protocol APIClientProtocol: class {
    var task: URLSessionDataTask { get set }
    var session: SessionProtocol { get }
    func call<T: Codable>(with request: URLRequest, completion: @escaping (Either<T>) -> Void) -> Void
    func requestRefreshToken<T: Codable>(forRequest failedRequest: URLRequest, completion: @escaping (Either<T>) -> Void) -> Void
}

extension APIClientProtocol {
    func call<T: Codable>(with request: URLRequest, completion: @escaping (Either<T>) -> Void) -> Void {
        task = session.dataTask(with: request, completionHandler: { [weak self] data, response, error in
            guard error == nil else {
                return completion(.error("An unknown error occured with the remote service"))
            }
            guard let response = response as? HTTPURLResponse else {
                return completion(.error("Invalid HTTPURLResponse recieved"))
            }

            switch response.statusCode {
            case 100...299:
                guard let data = data else { return completion(.error("No data in response")) }
                guard let value = try? JSONDecoder().decode(T.self, from: data) else { return completion(.error("Could not decode the JSON response")) }
                completion(.success(value))
            case 300...399: return completion(.error(HTTPResponseStatus.redirection.rawValue))
            case 400: return completion(.error(HTTPResponseStatus.clientError.rawValue))
            case 401: self?.requestRefreshToken(forRequest: request, completion: completion)
            case 402...499: return completion(.error(HTTPResponseStatus.clientError.rawValue))
            case 500...599: return completion(.error(HTTPResponseStatus.serverError.rawValue))
            default:
                return completion(.error("Request failed with a status code of \(response.statusCode)"))
            }
        })
        task.resume()
    }
}
Tim J
  • 1,211
  • 1
  • 14
  • 31
  • Is returning `.error(value)` an option? Or add a third case. By the way, why is the string in the error case optional? A `nil` value makes no sense. I'd even `catch` the error while decoding the JSON and return it (`case error(DecodingError)`) – vadian Jan 06 '19 at 11:07
  • Can you post your networking client? However you consume URLSession please – nodediggity Jan 06 '19 at 11:49

2 Answers2

3

Make your Either enum success type optional

enum Either<T> {
    case success(T?)
    case error(String)
}

Create a case for a 204 response status, passing nil

    case 204:
        completion(.success(nil))

You can then use a typealias and create something generic like

typealias NoContentResponse = Either<Bool>

You should then be able to switch on NoContentResponse.

nodediggity
  • 2,458
  • 1
  • 9
  • 12
1
case 200...299:  //success status code 
if data.isEmpty {
     let dummyData = "{}".data(using: .utf8)
     //Use empty model 
     model = try JSONDecoder().decode(T, from: dummyData!)
}
QrrrQ
  • 11
  • 1