Issue #222

For more mature networking, visit https://github.com/onmyway133/Miami

final class NetworkClient {
    let session: URLSession
    let baseUrl: URL

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

    func make(options: Options, completion: @escaping (Result<Data, Error>) -> Void) {
        guard let request = options.toRequest(baseUrl: baseUrl) else {
            completion(.failure(AppError.request))
            return
        }

        let task = session.dataTask(with: request, completionHandler: { data, _, error in
            if let data = data {
                completion(.success(data))
            } else if let error = error {
                completion(.failure(error))
            } else {
                completion(.failure(AppError.unknown))
            }
        })

        task.resume()
    }

    func makeJson(options: Options, completion: @escaping (Result<[String: Any], Error>) -> Void) {
        make(options: options, completion: { result in
            let mapped = result.flatMap({ data -> Result<[String: Any], Error> in
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    if let json = json as? [String: Any] {
                        return Result<[String: Any], Error>.success(json)
                    } else {
                        return Result<[String: Any], Error>.failure(AppError.parse)
                    }
                } catch {
                    return Result<[String: Any], Error>.failure(error)
                }
            })

            completion(mapped)
        })
    }
}

struct Options {
    var path: String = ""
    var httpMethod: HttpMethod = .get
    var parameters: [String: Any] = [:]

    func toRequest(baseUrl: URL) -> URLRequest? {
        let url = baseUrl.appendingPathComponent(path)
        let items: [URLQueryItem] = parameters.map({ tuple -> URLQueryItem in
            return URLQueryItem(name: tuple.key, value: "\(tuple.value)")
        })

        var components = URLComponents(url: url, resolvingAgainstBaseURL: false)

        if httpMethod == .get {
            components?.queryItems = items
        }

        guard let finalUrl = components?.url else {
            return nil
        }

        var request = URLRequest(url: finalUrl)

        if httpMethod == .post {
            let data = try? JSONSerialization.data(withJSONObject: parameters, options: [])
            request.httpBody = data
        }

        request.httpMethod = httpMethod.rawValue
        return request
    }
}

enum AppError: Error {
    case request
    case unknown
    case parse
}

enum HttpMethod: String {
    case get = "GET"
    case put = "PUT"
    case post = "POST"
    case patch = "PATCH"
}