Search code examples
swiftcompletion

How to fix: "Member 'value' in X produces result of type X, but context expects X" (X == "Either<List, APIError>")


I'm setting up an api client with Openweathermap for a Swift based iOS app. I followed a tutorial for how to set it up, but had to change weather providers because their service no longer provides free api keys. I have a bug with the type of the completion of my client but don't fully understand what's going on. The tutorial used a template type but I changed that to try to solve another bug.

I have been experimenting with using template vs a non templated completion format, but I don't understand the purpose or how it works fully.

class WeatherAPIClient: APIClient {  
    var session: URLSession

    init(session: URLSession = URLSession.shared) {
        self.session = session
    }

    func weather(with endpoint: WeatherEndpoint, completion: @escaping (Either<List, APIError>) -> Void){
        let request = endpoint.request
        self.fetch(with: request) { (either: Either<[List], APIError>) in
            switch either {
            case .value(let weather):
                completion(.value(weather))  // <--- Original error line here
            case .error(let error):
                completion(.error(error))
            }
        }
    }
}
enum Either<List, APIError> { // <-- Error changing this to [List] (comment error 1)
    case value(List)
    case error(APIError)
}

enum APIError: Error {
    case apiError
    case badResponse
    case jsonDecoder
    case unknown(String)
}

protocol APIClient {
    var session : URLSession {get}
    func fetch<List: Codable> (with request: URLRequest, completion: @escaping (Either<[List], APIError>) -> Void)
}


extension APIClient {
    func fetch<List: Codable> (with request: URLRequest, completion: @escaping (Either<[List], APIError>) -> Void){
        let task = session.dataTask(with: request) { (data, response, error) in
            guard error == nil else  {
                completion(.error(.apiError))
                return
            }
            guard let httpResponse = response as? HTTPURLResponse else {
                print(response)
                return
            }
            guard httpResponse.statusCode == 200 else {
                print(httpResponse.statusCode)
                print(response)
                completion(.error(.badResponse))
                return
            }

            do {
                let dictionaryFromJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]

                let jsonItem = dictionaryFromJSON["list"] as? NSArray

                let jsonData = try JSONSerialization.data(withJSONObject: jsonItem!, options: [])

                let responseModel = try JSONDecoder().decode([List].self, from: jsonData as! Data)
                // Error changing line the above [List] to List (comment error 2)

                print(responseModel)
                completion(.value(responseModel))

            }
            catch {
                print("darn")
                print(error)
                completion(.error(.jsonDecoder))
            }
        }
        task.resume()
    }
}

When I print the response model I have a valid json with the data I want. The problem is passing it to the completion correctly. I get a compile time error that: "Member 'value' in 'Either' produces result of type 'Either', but context expects 'Either'"

Originally the error made sense because the result type was different than the context expected, but I changed the api client code so that they matched, yet I am still getting an error.


Solution

  • I removed the brackets and used an array object in the decode call to fix the code:

    let responseModel = try JSONDecoder().decode([List].self, from: jsonData as! Data)
    

    to

    let responseModel = try JSONDecoder().decode(Array<List>.self, from: jsonData)
    

    I then removed brackets from List everywhere and changed the enum definition of Either to use the new array format: case value(Array<List>)

    I solved an intermediate error ("Expected to decode Dictionary but found an array instead.") when removing brackets from the JSON decoding line (shown above) using: Swift4 JSONDecoderExpected to decode Dictionary<String, Any> but found an array instead.