Skip to content

Commit

Permalink
Merge pull request #2 from acorscadden/main
Browse files Browse the repository at this point in the history
Add market data
  • Loading branch information
acorscadden committed Sep 10, 2020
2 parents be65fb1 + 509da67 commit c46a8fd
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 3 deletions.
18 changes: 15 additions & 3 deletions Sources/CoinGecko/ApiClient.swift
Expand Up @@ -9,26 +9,31 @@ import Foundation

public typealias Callback<T> = (Result<T, CoinGeckoError>) -> Void

let CustomKeyUserInfoKey = CodingUserInfoKey(rawValue: "customKey")!

public struct Resource<T: Codable> {

fileprivate let endpoint: Endpoint
fileprivate let method: Method
fileprivate let pathParam: String?
fileprivate let params: [URLQueryItem]?
fileprivate let parse: ((Data) -> T)? //optional parse function if Data isn't directly decodable to T
fileprivate let customKey: String?
fileprivate let completion: (Result<T, CoinGeckoError>) -> Void //called on main thread

public init(_ endpoint: Endpoint,
method: Method,
pathParam: String? = nil,
params: [URLQueryItem]? = nil,
parse: ((Data) -> T)? = nil,
customKey: String? = nil,
completion: @escaping (Result<T, CoinGeckoError>) -> Void) {
self.endpoint = endpoint
self.method = method
self.pathParam = pathParam
self.params = params
self.parse = parse
self.customKey = customKey
self.completion = completion
}
}
Expand All @@ -39,6 +44,8 @@ public enum Method: String {

public enum CoinGeckoError: Error {
case general
case noData
case jsonDecoding
}

public class CoinGeckoClient {
Expand All @@ -61,18 +68,23 @@ public class CoinGeckoClient {
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { completion(.failure(.general)); return }
guard let data = data else { completion(.failure(.noData)); return }
do {
var result: T
if let parse = resource.parse {
result = parse(data)
} else {
result = try JSONDecoder().decode(T.self, from: data)
let decoder = JSONDecoder()
if let customKey = resource.customKey {
decoder.userInfo = [CustomKeyUserInfoKey: customKey]
}
result = try decoder.decode(T.self, from: data)
}
DispatchQueue.main.async {
completion(.success(result))
}
} catch _ {
} catch let e {
print("JSON parsing error: \(e)")
DispatchQueue.main.async {
completion(.failure(.general))
}
Expand Down
63 changes: 63 additions & 0 deletions Sources/CoinGecko/Models/MarketData.swift
@@ -0,0 +1,63 @@
//
// MarketData.swift
//
//
// Created by Adrian Corscadden on 2020-09-08.
//

import Foundation

public struct MarketContainer: Codable {

public var marketCap: Double { return market_data.marketCap }
public var totalVolume: Double { return market_data.totalVolume }
public var high24h: Double { return market_data.high24h }
public var low24h: Double { return market_data.low24h }

private let market_data: MarketData
}

public struct MarketData: Codable {

public let marketCap: Double
public let totalVolume: Double
public let high24h: Double
public let low24h: Double

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
guard let customKey = decoder.userInfo[CustomKeyUserInfoKey] as? String else { throw CoinGeckoError.jsonDecoding }
self.marketCap = try extractDouble(container: container, key1: "market_cap", key2: customKey)
self.totalVolume = try extractDouble(container: container, key1: "total_volume", key2: customKey)
self.high24h = try extractDouble(container: container, key1: "high_24h", key2: customKey)
self.low24h = try extractDouble(container: container, key1: "low_24h", key2: customKey)
}
}

private func extractDouble(container: KeyedDecodingContainer<DynamicCodingKeys>, key1: String, key2: String) throws -> Double {
var result: Double?
try container.allKeys.forEach {
if $0.stringValue == key1 {
let x = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: $0)
try x.allKeys.forEach {
if $0.stringValue == key2 {
result = try x.decode(Double.self, forKey: $0)
}
}
}
}
guard let r = result else { throw CoinGeckoError.jsonDecoding }
return r
}

struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}

var intValue: Int?
init?(intValue: Int) {
return nil
}
}
12 changes: 12 additions & 0 deletions Sources/CoinGecko/Resources.swift
Expand Up @@ -15,6 +15,7 @@ public enum Endpoint: String {

case coinsList = "/coins/list"
case coinsMarketChart = "/coins/%@/market_chart"
case coin = "/coins/%@"
}

public enum Resources {}
Expand Down Expand Up @@ -60,6 +61,17 @@ extension Resources {

// MARK: - Coins
extension Resources {

public static func coin<CoinResponse>(currencyId: String, vs: String,
_ callback: @escaping (Result<CoinResponse, CoinGeckoError>) -> Void) -> Resource<CoinResponse>{
let params = [URLQueryItem(name: "market_data", value: "true"),
URLQueryItem(name: "localization", value: "false"),
URLQueryItem(name: "tickers", value: "false"),
URLQueryItem(name: "community_data", value: "false"),
URLQueryItem(name: "developer_data", value: "false")]
return Resource(.coin, method: .GET, pathParam: currencyId, params: params, customKey: vs, completion: callback)
}

public static func coins<CoinList>(_ callback: @escaping Callback<CoinList>) -> Resource<CoinList> {
return Resource(.coinsList, method: .GET, completion: callback)
}
Expand Down
14 changes: 14 additions & 0 deletions Tests/CoinGeckoTests/CoinTests.swift
Expand Up @@ -34,4 +34,18 @@ final class CoinTests: XCTestCase {
client.load(chart)
wait(for: [exp], timeout: 10.0)
}

func testMarketData() {
let exp = XCTestExpectation()
let marketData = Resources.coin(currencyId: "bitcoin", vs: "cad") { (result: Result<MarketContainer, CoinGeckoError>) in
guard case .success(let data) = result else { XCTFail(); exp.fulfill(); return }
XCTAssert(data.marketCap > 0)
XCTAssert(data.high24h > 0)
XCTAssert(data.low24h > 0)
XCTAssert(data.totalVolume > 0)
exp.fulfill()
}
client.load(marketData)
wait(for: [exp], timeout: 10.0)
}
}

0 comments on commit c46a8fd

Please sign in to comment.