-
Notifications
You must be signed in to change notification settings - Fork 0
/
WealthsimpleAccount.swift
159 lines (146 loc) · 5.61 KB
/
WealthsimpleAccount.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//
// Account.swift
//
//
// Created by Steffen Kötte on 2020-07-12.
//
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
/// Errors which can happen when retrieving an Account
public enum AccountError: Error {
/// When no data is received from the HTTP request
case noDataReceived
/// When an HTTP error occurs
case httpError(error: String)
/// When the received data is not valid JSON
case invalidJson(error: String)
/// When the received JSON does not have the right type
case invalidJsonType(json: Any)
/// When the received JSON does not have all expected values
case missingResultParamenter(json: [String: Any])
/// When the received JSON does have an unexpected value
case invalidResultParamenter(json: [String: Any])
/// An error with the token occured
case tokenError(_ error: TokenError)
}
/// Type of the account
///
/// Note: Currently only Canadian Accounts are supported
public enum AccountType: String {
/// Tax free savings account (CA)
case tfsa = "ca_tfsa"
/// Cash (chequing) account (CA)
case chequing = "ca_cash_msb"
/// Saving (CA)
case saving = "ca_cash"
/// Registered Retirement Savings Plan (CA)
case rrsp = "ca_rrsp"
/// Non-registered account (CA)
case nonRegistered = "ca_non_registered"
/// Non-registered crypto currency account (CA)
case nonRegisteredCrypto = "ca_non_registered_crypto"
/// Locked-in retirement account (CA)
case lira = "ca_lira"
/// Joint account (CA)
case joint = "ca_joint"
/// Registered Retirement Income Fund (CA)
case rrif = "ca_rrif"
/// Life Income Fund (CA)
case lif = "ca_lif"
}
/// An Account at Wealthsimple
public protocol Account {
/// Type of the account
var accountType: AccountType { get }
/// Operating currency of the account
var currency: String { get }
/// Wealthsimple id for the account
var id: String { get }
/// Number of the account
var number: String { get }
}
struct WealthsimpleAccount: Account {
private static let url = URL(string: "https://api.production.wealthsimple.com/v1/accounts")!
let accountType: AccountType
let currency: String
let id: String
let number: String
private init(json: [String: Any]) throws {
guard let id = json["id"] as? String,
let typeString = json["type"] as? String,
let object = json["object"] as? String,
let currency = json["base_currency"] as? String,
let number = json["custodian_account_number"] as? String else {
throw AccountError.missingResultParamenter(json: json)
}
guard let type = AccountType(rawValue: typeString), object == "account" else {
throw AccountError.invalidResultParamenter(json: json)
}
self.id = id
self.accountType = type
self.currency = currency
self.number = number
}
static func getAccounts(token: Token, completion: @escaping (Result<[Account], AccountError>) -> Void) {
var request = URLRequest(url: url)
let session = URLSession.shared
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
token.authenticateRequest(request) {
switch $0 {
case let .failure(error):
completion(.failure(.tokenError(error)))
case let .success(request):
let task = session.dataTask(with: request) { data, response, error in
handleResponse(data: data, response: response, error: error, completion: completion)
}
task.resume()
}
}
}
private static func handleResponse(data: Data?, response: URLResponse?, error: Error?, completion: @escaping (Result<[Account], AccountError>) -> Void) {
guard let data else {
if let error {
completion(.failure(AccountError.httpError(error: error.localizedDescription)))
} else {
completion(.failure(AccountError.noDataReceived))
}
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(AccountError.httpError(error: "No HTTPURLResponse")))
return
}
guard httpResponse.statusCode == 200 else {
completion(.failure(AccountError.httpError(error: "Status code \(httpResponse.statusCode)")))
return
}
do {
completion(try parse(data: data))
} catch {
completion(.failure(AccountError.invalidJson(error: error.localizedDescription)))
return
}
}
private static func parse(data: Data) throws -> Result<[Account], AccountError> {
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return .failure(AccountError.invalidJsonType(json: try JSONSerialization.jsonObject(with: data, options: [])))
}
do {
guard let results = json["results"] as? [[String: Any]], let object = json["object"] as? String else {
throw AccountError.missingResultParamenter(json: json)
}
guard object == "account" else {
throw AccountError.invalidResultParamenter(json: json)
}
var accounts = [Account]()
for result in results {
accounts.append(try Self(json: result))
}
return .success(accounts)
} catch {
return .failure(error as! AccountError) // swiftlint:disable:this force_cast
}
}
}