From 01e4aeb116be7c2d7b3d125563a19026e0bf765c Mon Sep 17 00:00:00 2001 From: JagCesar Date: Fri, 17 Nov 2017 23:43:59 +0100 Subject: [PATCH 1/2] Codable ftw! --- Source/Models/BoolResponses.swift | 13 ++ Source/Models/ChargeState.swift | 110 +++++------ Source/Models/Token.swift | 25 ++- Source/Models/Vehicle.swift | 46 ++--- Source/Models/VehicleState.swift | 93 +++++++++ Source/Requests/AuthenticateRequest.swift | 14 +- Source/Requests/ChargeStateRequest.swift | 20 +- Source/Requests/ListVehiclesRequest.swift | 18 +- Source/Requests/LockRequest.swift | 20 +- .../MobileEnabledForVehicleRequest.swift | 24 +-- Source/Requests/OpenChargePortRequest.swift | 20 +- Source/Requests/VehicleStateRequest.swift | 185 ++++++++++++++++++ Source/WebRequest.swift | 15 +- Tesla-API.xcodeproj/project.pbxproj | 44 ++++- Tests/ModelMocks.swift | 42 ++-- .../ChargeStateRequestTests.swift | 6 +- .../ListVehiclesRequestTests.swift | 6 +- Tests/Request Tests/LockRequestTests.swift | 12 +- .../MobileEnabledForRequestTests.swift | 8 +- .../OpenChargePortRequestTests.swift | 6 +- .../VehicleStateRequestTests.swift | 22 +++ Tests/TeslaAPITests.swift | 6 +- 22 files changed, 549 insertions(+), 206 deletions(-) create mode 100644 Source/Models/BoolResponses.swift create mode 100644 Source/Models/VehicleState.swift create mode 100644 Source/Requests/VehicleStateRequest.swift create mode 100644 Tests/Request Tests/VehicleStateRequestTests.swift diff --git a/Source/Models/BoolResponses.swift b/Source/Models/BoolResponses.swift new file mode 100644 index 0000000..b18175e --- /dev/null +++ b/Source/Models/BoolResponses.swift @@ -0,0 +1,13 @@ +import Foundation + +struct BoolResponse: Codable { + let response: Bool +} + +struct ResultResponse: Codable { + let response: BoolResult +} + +struct BoolResult: Codable { + let result: Bool +} diff --git a/Source/Models/ChargeState.swift b/Source/Models/ChargeState.swift index 21674c2..dfd5b7a 100644 --- a/Source/Models/ChargeState.swift +++ b/Source/Models/ChargeState.swift @@ -1,11 +1,15 @@ import Foundation -public class ChargeState { - public enum ChargingState: String { - case disconnected = "Disconnected" - case charging = "Charging" - case complete = "Complete" - } +public class ChargeStateResponse: Codable { + let response: ChargeState +} + +public class ChargeState: Codable { +// public enum ChargingState: String { +// case disconnected = "Disconnected" +// case charging = "Charging" +// case complete = "Complete" +// } public let batteryHeaterOn: Bool? public let batteryLevel: Int @@ -29,7 +33,7 @@ public class ChargeState { public let chargerPilotCurrent: Int? public let chargerPower: Int public let chargerVoltage: Int - public let chargingState: ChargingState +// public let chargingState: ChargingState public let estBatteryRange: Double public let euVehicle: Bool public let fastChargerPresent: Bool @@ -49,55 +53,47 @@ public class ChargeState { public let usableBatteryLevel: Int public let userChargeEnableRequest: Bool? - init(dictionary: [String: Any]) { - batteryHeaterOn = dictionary["battery_heater_on"] as? Bool - batteryLevel = dictionary["battery_level"] as! Int - batteryRange = dictionary["battery_range"] as! Double - chargeCurrentRequest = dictionary["charge_current_request"] as! Int - chargeCurrentRequestMax = dictionary["charge_current_request_max"] as! Int - chargeEnableRequest = dictionary["charge_enable_request"] as! Bool - chargeEnergyAdded = dictionary["charge_energy_added"] as! Double - chargeLimitSoc = dictionary["charge_limit_soc"] as! Int - chargeLimitSocMax = dictionary["charge_limit_soc_max"] as! Int - chargeLimitSocMin = dictionary["charge_limit_soc_min"] as! Int - chargeLimitSocStd = dictionary["charge_limit_soc_std"] as! Int - chargeMilesAddedIdeal = dictionary["charge_miles_added_ideal"] as! Double - chargeMilesAddedRated = dictionary["charge_miles_added_rated"] as! Double - chargePortDoorOpen = dictionary["charge_port_door_open"] as? Bool - chargePortLatch = dictionary["charge_port_latch"] as! String - chargeRate = dictionary["charge_rate"] as! Double - chargeToMaxRange = dictionary["charge_to_max_range"] as! Int - chargerActualCurrent = dictionary["charger_actual_current"] as! Int - chargerPhases = dictionary["charger_phases"] as? Int - chargerPilotCurrent = dictionary["charger_pilot_current"] as? Int - chargerPower = dictionary["charger_power"] as! Int - chargerVoltage = dictionary["charger_voltage"] as! Int - chargingState = ChargingState(rawValue: dictionary["charging_state"] as! String)! - estBatteryRange = dictionary["est_battery_range"] as! Double - euVehicle = dictionary["eu_vehicle"] as! Bool - fastChargerPresent = dictionary["fast_charger_present"] as! Bool - fastChargerType = dictionary["fast_charger_type"] as? String - idealBatteryRange = dictionary["ideal_battery_range"] as! Double - managedChargingActive = dictionary["managed_charging_active"] as! Bool - if let timestamp = dictionary["managed_charging_start_time"] as? Double { - managedChargingStartTime = Date(timeIntervalSince1970: timestamp) - } else { - managedChargingStartTime = nil - } - managedChargingUserCanceled = dictionary["managed_charging_user_canceled"] as! Bool - maxRangeChargeCounter = dictionary["max_range_charge_counter"] as! Int - motorizedChargePort = dictionary["motorized_charge_port"] as! Bool - notEnoughPowerToHeat = dictionary["not_enough_power_to_heat"] as? Bool - scheduledChargingPending = dictionary["scheduled_charging_pending"] as! Bool - if let timestamp = dictionary["scheduled_charging_start_time"] as? Double { - scheduledChargingStartTime = Date(timeIntervalSince1970: timestamp) - } else { - scheduledChargingStartTime = nil - } - timeToFullCharge = dictionary["time_to_full_charge"] as! Double - timestamp = Date(timeIntervalSince1970: dictionary["timestamp"] as! Double) - tripCharging = dictionary["trip_charging"] as? Bool - usableBatteryLevel = dictionary["usable_battery_level"] as! Int - userChargeEnableRequest = dictionary["user_charge_enable_request"] as? Bool + private enum CodingKeys: String, CodingKey { + case batteryHeaterOn = "battery_heater_on" + case batteryLevel = "battery_level" + case batteryRange = "battery_range" + case chargeCurrentRequest = "charge_current_request" + case chargeCurrentRequestMax = "charge_current_request_max" + case chargeEnableRequest = "charge_enable_request" + case chargeEnergyAdded = "charge_energy_added" + case chargeLimitSoc = "charge_limit_soc" + case chargeLimitSocMax = "charge_limit_soc_max" + case chargeLimitSocMin = "charge_limit_soc_min" + case chargeLimitSocStd = "charge_limit_soc_std" + case chargeMilesAddedIdeal = "charge_miles_added_ideal" + case chargeMilesAddedRated = "charge_miles_added_rated" + case chargePortDoorOpen = "charge_port_door_open" + case chargePortLatch = "charge_port_latch" + case chargeRate = "charge_rate" + case chargeToMaxRange = "charge_to_max_range" + case chargerActualCurrent = "charger_actual_current" + case chargerPhases = "charger_phases" + case chargerPilotCurrent = "charger_pilot_current" + case chargerPower = "charger_power" + case chargerVoltage = "charger_voltage" + // case chargingState = "charging_state" + case estBatteryRange = "est_battery_range" + case euVehicle = "eu_vehicle" + case fastChargerPresent = "fast_charger_present" + case fastChargerType = "fast_charger_type" + case idealBatteryRange = "ideal_battery_range" + case managedChargingActive = "managed_charging_active" + case managedChargingStartTime = "managed_charging_start_time" + case managedChargingUserCanceled = "managed_charging_user_canceled" + case maxRangeChargeCounter = "max_range_charge_counter" + case motorizedChargePort = "motorized_charge_port" + case notEnoughPowerToHeat = "not_enough_power_to_heat" + case scheduledChargingPending = "scheduled_charging_pending" + case scheduledChargingStartTime = "scheduled_charging_start_time" + case timeToFullCharge = "time_to_full_charge" + case timestamp = "timestamp" + case tripCharging = "trip_charging" + case usableBatteryLevel = "usable_battery_level" + case userChargeEnableRequest = "user_charge_enable_request" } } diff --git a/Source/Models/Token.swift b/Source/Models/Token.swift index 817de5c..0023479 100644 --- a/Source/Models/Token.swift +++ b/Source/Models/Token.swift @@ -1,24 +1,21 @@ import Foundation -public class Token { +public class TokenResponse: Codable { + let response: Token +} + +public class Token: Codable { public let accessToken: String public let type: String public let expires: Date public let created: Date public let refreshToken: String - init(dictionary: [String: AnyObject]) throws { - guard let accessToken = dictionary["access_token"] as? String, - let type = dictionary["token_type"] as? String, - let expiresInteger = dictionary["expires_in"] as? Double, - let createdInteger = dictionary["created_at"] as? Double, - let refreshToken = dictionary["refresh_token"] as? String else { - throw APIError() - } - self.accessToken = accessToken - self.type = type - self.expires = Date(timeIntervalSince1970: expiresInteger + createdInteger) - self.created = Date(timeIntervalSince1970: createdInteger) - self.refreshToken = refreshToken + private enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case type = "token_type" + case expires = "expires_in" + case created = "created_at" + case refreshToken = "refresh_token" } } diff --git a/Source/Models/Vehicle.swift b/Source/Models/Vehicle.swift index 0d181d6..7790dbb 100644 --- a/Source/Models/Vehicle.swift +++ b/Source/Models/Vehicle.swift @@ -1,35 +1,31 @@ import Foundation -public class Vehicle { - public enum State: String { - case online - } +public class VehicleResponse: Codable { + let response: [Vehicle] +} + +public class Vehicle: Codable { +// public enum State: String { +// case online +// } public let color: String? public let displayName: String? - public let identifier: String - public let optionCodes: [String] + public let identifier: Int + public let optionCodes: String public let vehicleIdentifier: Int public let vin: String public let tokens: [String] - public let state: State +// public let state: State - init?(dictionary: [String: AnyObject]) { - guard let identifierNumber = dictionary["id"] as? NSNumber, - let optionCodes = dictionary["option_codes"] as? String, - let vehicleIdentifier = dictionary["vehicle_id"] as? Int, - let vin = dictionary["vin"] as? String, - let tokens = dictionary["tokens"] as? [String], - let stateString = dictionary["state"] as? String, - let state = State(rawValue: stateString) else { - return nil - } - self.color = dictionary["color"] as? String - self.displayName = dictionary["display_name"] as? String - self.identifier = identifierNumber.stringValue - self.optionCodes = optionCodes.components(separatedBy: ",") - self.vehicleIdentifier = vehicleIdentifier - self.vin = vin - self.tokens = tokens - self.state = state + private enum CodingKeys: String, CodingKey { + case color = "color" + case displayName = "display_name" + case identifier = "id" + case optionCodes = "option_codes" + case vehicleIdentifier = "vehicle_id" + case vin + case tokens = "tokens" +// case state } } + diff --git a/Source/Models/VehicleState.swift b/Source/Models/VehicleState.swift new file mode 100644 index 0000000..8372e52 --- /dev/null +++ b/Source/Models/VehicleState.swift @@ -0,0 +1,93 @@ +import Foundation + +public struct VehicleStateResponse: Codable { + let response: VehicleState +} + +public struct VehicleState: Codable { + let parsedCalendarSupported: Double + let spoilerType: String + let rt: Double + let carVersion: String + let rearSeatType: Double + let df: Double + let locked: Double + let thirdRowSeats: String + let sunRoofState: String + let carType: String + let remoteStartSupported: Double + let hasSpoiler: Double + let pf: Double + let odometer: Double + let lastAutoparkError: String + let darkRims: Double + let autoparkState: String + let sunRoofInstalled: Double + let sunRoofPercentOpen: Double + let exteriorColor: String + let timestamp: Double + let vehicleName: String + let homelinkNearby: Double + let valetPinNeeded: Double + let notificationsSupported: Double + let pr: Double + let rearSeatHeaters: Double + let valetMode: Double + let calendarSupported: Double + let dr: Double + let centerDisplayState: Double + let apiVersion: Double + let remoteStart: Double + let ft: Double + let autoparkStateV2: String + let wheelType: String + let perfConfig: String + let autoparkStyle: String + let roofColor: String + let rhd: Double + let seatType: Double + + private enum CodingKeys: String, CodingKey { + case parsedCalendarSupported = "parsed_calendar_supported" + case spoilerType = "spoiler_type" + case rt + case carVersion = "car_version" + case rearSeatType = "rear_seat_type" + case df + case locked + case thirdRowSeats = "third_row_seats" + case sunRoofState = "sun_roof_state" + case carType = "car_type" + case remoteStartSupported = "remote_start_supported" + case hasSpoiler = "has_spoiler" + case pf + case odometer + case lastAutoparkError = "last_autopark_error" + case darkRims = "dark_rims" + case autoparkState = "autopark_state" + case sunRoofInstalled = "sun_roof_installed" + case sunRoofPercentOpen = "sun_roof_percent_open" + case exteriorColor = "exterior_color" + case timestamp + case vehicleName = "vehicle_name" + case homelinkNearby = "homelink_nearby" + case valetPinNeeded = "valet_pin_needed" + case notificationsSupported = "notifications_supported" + case pr + case rearSeatHeaters = "rear_seat_heaters" + case valetMode = "valet_mode" + case calendarSupported = "calendar_supported" + case dr + case centerDisplayState = "center_display_state" + case apiVersion = "api_version" + case remoteStart = "remote_start" + case ft + case autoparkStateV2 = "autopark_state_v2" + case wheelType = "wheel_type" + case perfConfig = "perf_config" + case autoparkStyle = "autopark_style" + case roofColor = "roof_color" + case rhd + case seatType = "seat_type" + } +} diff --git a/Source/Requests/AuthenticateRequest.swift b/Source/Requests/AuthenticateRequest.swift index 3757d41..13c1d7a 100644 --- a/Source/Requests/AuthenticateRequest.swift +++ b/Source/Requests/AuthenticateRequest.swift @@ -26,22 +26,16 @@ public struct AuthenticateRequest: RequestProtocol { WebRequest.request( path: path, method: method, - params: params) { response, error in + params: params) { data, error in if let error = error { DispatchQueue.main.async { completion(Result.failure(error)) } - } else { - guard let responseDictionary = response as? [String: AnyObject] else { - DispatchQueue.main.async { - completion(Result.failure(APIError())) - } - return - } + } else if let data = data { do { - let result = try Result.success(Token(dictionary: responseDictionary)) + let token = try JSONDecoder().decode(Token.self, from: data) DispatchQueue.main.async { - completion(result) + completion(Result.success(token)) } } catch let error { DispatchQueue.main.async { diff --git a/Source/Requests/ChargeStateRequest.swift b/Source/Requests/ChargeStateRequest.swift index e54d724..0e7515a 100644 --- a/Source/Requests/ChargeStateRequest.swift +++ b/Source/Requests/ChargeStateRequest.swift @@ -18,19 +18,21 @@ public struct ChargeStateRequest: RequestProtocol { WebRequest.request( path: path, method: method, - accessToken: accessToken) { response, error in + accessToken: accessToken) { data, error in if let error = error { DispatchQueue.main.async { completion(Result.failure(error)) } - } else if let response = response as? [String: [String: Any]], - let dictionary = response["response"] { - DispatchQueue.main.async { - completion(Result.success(ChargeState(dictionary: dictionary))) - } - } else { - DispatchQueue.main.async { - completion(Result.failure(APIError())) + } else if let data = data { + do { + let chargeStateResponse = try JSONDecoder().decode(ChargeStateResponse.self, from: data) + DispatchQueue.main.async { + completion(Result.success(chargeStateResponse.response)) + } + } catch let error { + DispatchQueue.main.async { + completion(Result.failure(error)) + } } } } diff --git a/Source/Requests/ListVehiclesRequest.swift b/Source/Requests/ListVehiclesRequest.swift index 74cebe6..04c7da0 100644 --- a/Source/Requests/ListVehiclesRequest.swift +++ b/Source/Requests/ListVehiclesRequest.swift @@ -14,21 +14,21 @@ public struct ListVehiclesRequest: RequestProtocol { WebRequest.request( path: path, method: method, - accessToken: accessToken) { response, error in + accessToken: accessToken) { data, error in if let error = error { DispatchQueue.main.async { completion(Result.failure(error)) } - } else { - guard let responseArray = response?["response"] as? [[String: AnyObject]] else { + } else if let data = data { + do { + let vehicleResponse = try JSONDecoder().decode(VehicleResponse.self, from: data) DispatchQueue.main.async { - completion(Result.failure(APIError())) + completion(Result.success(vehicleResponse.response)) + } + } catch let error { + DispatchQueue.main.async { + completion(Result.failure(error)) } - return - } - let vehicles = responseArray.flatMap { return Vehicle(dictionary: $0) } - DispatchQueue.main.async { - completion(Result.success(vehicles)) } } } diff --git a/Source/Requests/LockRequest.swift b/Source/Requests/LockRequest.swift index 1c44f24..5cddcee 100644 --- a/Source/Requests/LockRequest.swift +++ b/Source/Requests/LockRequest.swift @@ -29,19 +29,21 @@ public struct LockRequest: RequestProtocol { WebRequest.request( path: path, method: method, - accessToken: accessToken) { response, error in + accessToken: accessToken) { data, error in if let error = error { DispatchQueue.main.async { completion(Result.failure(error)) } - } else if let response = response as? [String: [String: Any]], - let resultBool = response["response"]?["result"] as? Bool { - DispatchQueue.main.async { - completion(Result.success(resultBool)) - } - } else { - DispatchQueue.main.async { - completion(Result.failure(APIError())) + } else if let data = data { + do { + let resultResponse = try JSONDecoder().decode(ResultResponse.self, from: data) + DispatchQueue.main.async { + completion(Result.success(resultResponse.response.result)) + } + } catch let error { + DispatchQueue.main.async { + completion(Result.failure(error)) + } } } } diff --git a/Source/Requests/MobileEnabledForVehicleRequest.swift b/Source/Requests/MobileEnabledForVehicleRequest.swift index 9c986b0..baaedcf 100644 --- a/Source/Requests/MobileEnabledForVehicleRequest.swift +++ b/Source/Requests/MobileEnabledForVehicleRequest.swift @@ -9,8 +9,8 @@ struct MobileEnabledForVehicleRequest: RequestProtocol { let method = WebRequest.RequestMethod.get let accessToken: String - init(vehicle: Vehicle, accessToken: String) { - self.vehicleIdentifier = vehicle.identifier + init(vehicleIdentifier: String, accessToken: String) { + self.vehicleIdentifier = vehicleIdentifier self.accessToken = accessToken } @@ -18,19 +18,21 @@ struct MobileEnabledForVehicleRequest: RequestProtocol { WebRequest.request( path: path, method: method, - accessToken: accessToken) { response, error in + accessToken: accessToken) { data, error in if let error = error { DispatchQueue.main.async { completion(Result.failure(error)) } - } else if let response = response as? [String: Bool], - let responseBool = response["response"] { - DispatchQueue.main.async { - completion(Result.success(responseBool)) - } - } else { - DispatchQueue.main.async { - completion(Result.failure(APIError())) + } else if let data = data { + do { + let boolResponse = try JSONDecoder().decode(BoolResponse.self, from: data) + DispatchQueue.main.async { + completion(Result.success(boolResponse.response)) + } + } catch let error { + DispatchQueue.main.async { + completion(Result.failure(error)) + } } } } diff --git a/Source/Requests/OpenChargePortRequest.swift b/Source/Requests/OpenChargePortRequest.swift index b2804d1..c687222 100644 --- a/Source/Requests/OpenChargePortRequest.swift +++ b/Source/Requests/OpenChargePortRequest.swift @@ -18,19 +18,21 @@ public struct OpenChargePortRequest: RequestProtocol { WebRequest.request( path: path, method: method, - accessToken: accessToken) { response, error in + accessToken: accessToken) { data, error in if let error = error { DispatchQueue.main.async { completion(Result.failure(error)) } - } else if let response = response as? [String: [String: Any]], - let resultBool = response["response"]?["result"] as? Bool { - DispatchQueue.main.async { - completion(Result.success(resultBool)) - } - } else { - DispatchQueue.main.async { - completion(Result.failure(APIError())) + } else if let data = data { + do { + let result = try JSONDecoder().decode(ResultResponse.self, from: data) + DispatchQueue.main.async { + completion(Result.success(result.response.result)) + } + } catch let error { + DispatchQueue.main.async { + completion(Result.failure(error)) + } } } } diff --git a/Source/Requests/VehicleStateRequest.swift b/Source/Requests/VehicleStateRequest.swift new file mode 100644 index 0000000..1931c4e --- /dev/null +++ b/Source/Requests/VehicleStateRequest.swift @@ -0,0 +1,185 @@ +import Foundation + +public struct VehicleStateRequest: RequestProtocol { + typealias CompletionType = VehicleState + var path: String { + return "/api/1/vehicles/\(vehicleIdentifier)/data_request/vehicle_state" + } + let method = WebRequest.RequestMethod.get + let accessToken: String + let vehicleIdentifier: String + + public func execute(completion: @escaping (Result) -> Void) { + WebRequest.request( + path: path, + method: method, + accessToken: accessToken) { data, error in + if let error = error { + DispatchQueue.main.async { + completion(Result.failure(error)) + } + } else if let data = data { + // VehicleStateRequest.printStruct(dict: response) + do { + let decoder = JSONDecoder() + let vehicleStateResponse = try decoder.decode(VehicleStateResponse.self, from: data) + DispatchQueue.main.async { + completion(Result.success(vehicleStateResponse.response)) + } + } catch (let error) { + DispatchQueue.main.async { + completion(Result.failure(error)) + } + } + } else { + DispatchQueue.main.async { + completion(Result.failure(APIError())) + } + } + } + + WebRequest.request( + path: path, + method: method, + accessToken: accessToken) { data, error in + + } + } + + private static func printStruct(dict: [String: [String: Any]]) { + let response = dict["response"]! + print("\n\nproperties:") + for (key, value) in response { + var type: String? = nil + if value is Double { + type = "Double" + } else if value is String { + type = "String" + } else { + type = "Unknown" + } + if type == nil { + continue + } + print("let " + key.camelCased() + ": \(type!)") + } + + print("\n\nCoding keys:") + for (key, value) in response { + var type: String! = nil + if value is Bool { + type = "Bool" + } else if value is Double { + type = "Int" + } else if value is String { + type = "String" + } else { + type = "Unknown" + } + if type == nil { + continue + } + + if key.camelCased() == key { + print("case " + key.camelCased()) + } else { + print("case " + key.camelCased() + " = \"\(key)\"" ) + } + } + + print("\n\n") + } + + func isKnownType(value: Any) -> Bool { + return value is String || value is Double + } +} + +public enum StringCaseFormat { + + public enum SnakeCase { + case lower + case upper + case capitalized + } + + public enum CamelCase { + case `default` + case capitalized + } +} + +public extension String { + + public func caseSplit() -> [String] { + var res: [String] = [] + let trim = self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + let alphanumerics = CharacterSet.alphanumerics + let uppercaseLetters = CharacterSet.uppercaseLetters + let lowercaseLetters = CharacterSet.lowercaseLetters + trim.split(separator: " ").forEach { str in + var previousCase = 0 + var currentCase = 0 + var caseChange = false + var scalars = UnicodeScalarView() + for scalar in str.unicodeScalars { + if alphanumerics.contains(scalar) { + if uppercaseLetters.contains(scalar) { + currentCase = 1 + } else if lowercaseLetters.contains(scalar) { + currentCase = 2 + } else { + currentCase = 0 + } + let letterInWord = scalars.count + if !caseChange && letterInWord > 0 { + if currentCase != previousCase { + if previousCase == 1 { + if letterInWord > 1 { + caseChange = true + } + } else { + caseChange = true + } + } + } + if caseChange { + res.append(String(scalars)) + scalars.removeAll() + } + scalars.append(scalar) + caseChange = false + previousCase = currentCase + } else { + caseChange = true + } + } + if scalars.count > 0 { + res.append(String(scalars)) + } + } + return res + } + + public func snakeCased(_ format: StringCaseFormat.SnakeCase = .lower) -> String { + let split = self.caseSplit() + if format == .lower { + return split.map { $0.lowercased() }.joined(separator: "_") + } else if format == .upper { + return split.map { $0.uppercased() }.joined(separator: "_") + } + return split.map { $0.capitalized }.joined(separator: "_") + } + + public func camelCased(_ format: StringCaseFormat.CamelCase = .default) -> String { + var res: [String] = [] + for (i, str) in self.caseSplit().enumerated() { + if i == 0 && format == .default { + res.append(str.lowercased()) + continue + } + res.append(str.capitalized) + } + return res.joined() + } +} diff --git a/Source/WebRequest.swift b/Source/WebRequest.swift index 7d4e2e0..76a071e 100644 --- a/Source/WebRequest.swift +++ b/Source/WebRequest.swift @@ -45,7 +45,7 @@ class WebRequest { path: String, method: RequestMethod, params: [String: AnyObject]? = nil, accessToken: String? = nil, - completion: @escaping (_ response: AnyObject?, _ error: Error?) -> Void) { + completion: @escaping (_ response: Data?, _ error: Error?) -> Void) { dataTask( request: clientURLRequest( path: path, @@ -58,22 +58,13 @@ class WebRequest { static private func dataTask( request: URLRequest, method: RequestMethod, - completion: @escaping (_ response: AnyObject?, _ error: Error?) -> Void) { + completion: @escaping (_ response: Data?, _ error: Error?) -> Void) { var request = request request.httpMethod = method.rawValue let session = URLSession(configuration: URLSessionConfiguration.default) let task = session.dataTask(with: request as URLRequest) { data, response, error -> Void in - if let error = error { - completion(nil, error) - return - } - do { - let json = try JSONSerialization.jsonObject(with: data!, options: []) - completion(json as AnyObject, nil) - } catch let error { - completion(nil, error) - } + completion(data, error) } task.resume() } diff --git a/Tesla-API.xcodeproj/project.pbxproj b/Tesla-API.xcodeproj/project.pbxproj index 5176192..98f8060 100644 --- a/Tesla-API.xcodeproj/project.pbxproj +++ b/Tesla-API.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + FC0B57441FBF977B00AE4D0B /* BoolResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC0B57431FBF977B00AE4D0B /* BoolResponses.swift */; }; + FC0B57451FBF977B00AE4D0B /* BoolResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC0B57431FBF977B00AE4D0B /* BoolResponses.swift */; }; + FC0B57461FBF977B00AE4D0B /* BoolResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC0B57431FBF977B00AE4D0B /* BoolResponses.swift */; }; + FC0B57471FBF977B00AE4D0B /* BoolResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC0B57431FBF977B00AE4D0B /* BoolResponses.swift */; }; FC1B80971F49883500D24310 /* MobileEnabledForVehicleRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1B80961F49883500D24310 /* MobileEnabledForVehicleRequest.swift */; }; FC1B80981F498A2E00D24310 /* MobileEnabledForVehicleRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1B80961F49883500D24310 /* MobileEnabledForVehicleRequest.swift */; }; FC1B80991F498A2F00D24310 /* MobileEnabledForVehicleRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1B80961F49883500D24310 /* MobileEnabledForVehicleRequest.swift */; }; @@ -74,6 +78,17 @@ FC99DFDA1F1FBBE100013FE9 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC99DFD71F1FBBE100013FE9 /* Error.swift */; }; FC99DFDB1F1FBBE100013FE9 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC99DFD71F1FBBE100013FE9 /* Error.swift */; }; FCD161971F1E79F0003FE381 /* Tesla-API.h in Headers */ = {isa = PBXBuildFile; fileRef = FCD161901F1E793F003FE381 /* Tesla-API.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FCDDDAB41FBF32440076D3ED /* VehicleStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDAB31FBF32440076D3ED /* VehicleStateRequest.swift */; }; + FCDDDAB51FBF32440076D3ED /* VehicleStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDAB31FBF32440076D3ED /* VehicleStateRequest.swift */; }; + FCDDDAB61FBF32440076D3ED /* VehicleStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDAB31FBF32440076D3ED /* VehicleStateRequest.swift */; }; + FCDDDAB71FBF32440076D3ED /* VehicleStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDAB31FBF32440076D3ED /* VehicleStateRequest.swift */; }; + FCDDDAB91FBF32B70076D3ED /* VehicleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDAB81FBF32B70076D3ED /* VehicleState.swift */; }; + FCDDDABA1FBF32B70076D3ED /* VehicleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDAB81FBF32B70076D3ED /* VehicleState.swift */; }; + FCDDDABB1FBF32B70076D3ED /* VehicleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDAB81FBF32B70076D3ED /* VehicleState.swift */; }; + FCDDDABC1FBF32B70076D3ED /* VehicleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDAB81FBF32B70076D3ED /* VehicleState.swift */; }; + FCDDDABE1FBF342F0076D3ED /* VehicleStateRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDABD1FBF342F0076D3ED /* VehicleStateRequestTests.swift */; }; + FCDDDABF1FBF342F0076D3ED /* VehicleStateRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDABD1FBF342F0076D3ED /* VehicleStateRequestTests.swift */; }; + FCDDDAC01FBF342F0076D3ED /* VehicleStateRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDDDABD1FBF342F0076D3ED /* VehicleStateRequestTests.swift */; }; FCE825FC1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */; }; FCE825FD1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */; }; FCE825FE1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */; }; @@ -117,6 +132,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + FC0B57431FBF977B00AE4D0B /* BoolResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoolResponses.swift; sourceTree = ""; }; FC1B80961F49883500D24310 /* MobileEnabledForVehicleRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MobileEnabledForVehicleRequest.swift; sourceTree = ""; }; FC27C9791F4993E10064E1AE /* ModelMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelMocks.swift; sourceTree = ""; }; FC27C97E1F4995240064E1AE /* ListVehiclesRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListVehiclesRequestTests.swift; sourceTree = ""; }; @@ -143,6 +159,9 @@ FCD161921F1E793F003FE381 /* WebRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRequest.swift; sourceTree = ""; }; FCD161941F1E79C3003FE381 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FCD161951F1E79C3003FE381 /* TeslaAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeslaAPITests.swift; sourceTree = ""; }; + FCDDDAB31FBF32440076D3ED /* VehicleStateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleStateRequest.swift; sourceTree = ""; }; + FCDDDAB81FBF32B70076D3ED /* VehicleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleState.swift; sourceTree = ""; }; + FCDDDABD1FBF342F0076D3ED /* VehicleStateRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleStateRequestTests.swift; sourceTree = ""; }; FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChargeStateRequest.swift; sourceTree = ""; }; FCE826001F8C075C00B72C08 /* ChargeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChargeState.swift; sourceTree = ""; }; FCE826051F8C092C00B72C08 /* ChargeStateRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChargeStateRequestTests.swift; sourceTree = ""; }; @@ -214,6 +233,7 @@ FC27C9801F4995240064E1AE /* MobileEnabledForRequestTests.swift */, FC27C9811F4995240064E1AE /* OpenChargePortRequestTests.swift */, FCE826051F8C092C00B72C08 /* ChargeStateRequestTests.swift */, + FCDDDABD1FBF342F0076D3ED /* VehicleStateRequestTests.swift */, ); path = "Request Tests"; sourceTree = ""; @@ -253,11 +273,12 @@ isa = PBXGroup; children = ( FCE946461F2CF10E004913FC /* AuthenticateRequest.swift */, + FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */, FC80D5011F2CE6FD0013F6DF /* ListVehiclesRequest.swift */, FC75EC521F2E4AD7007ED697 /* LockRequest.swift */, FC1B80961F49883500D24310 /* MobileEnabledForVehicleRequest.swift */, FC75EC571F2E613A007ED697 /* OpenChargePortRequest.swift */, - FCE825FB1F8C068F00B72C08 /* ChargeStateRequest.swift */, + FCDDDAB31FBF32440076D3ED /* VehicleStateRequest.swift */, ); path = Requests; sourceTree = ""; @@ -283,6 +304,8 @@ FC99DFD71F1FBBE100013FE9 /* Error.swift */, FC99DFD21F1FB41400013FE9 /* Token.swift */, FCD1618F1F1E793F003FE381 /* Vehicle.swift */, + FCDDDAB81FBF32B70076D3ED /* VehicleState.swift */, + FC0B57431FBF977B00AE4D0B /* BoolResponses.swift */, ); path = Models; sourceTree = ""; @@ -586,6 +609,7 @@ buildActionMask = 2147483647; files = ( FC27C98B1F4995240064E1AE /* OpenChargePortRequestTests.swift in Sources */, + FCDDDABE1FBF342F0076D3ED /* VehicleStateRequestTests.swift in Sources */, FC27C9821F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, FCF2A1291FA32CE900BA5C3F /* Credentials.swift in Sources */, FCE826061F8C092C00B72C08 /* ChargeStateRequestTests.swift in Sources */, @@ -605,13 +629,16 @@ FC48F9A61F1E7FB600D2400B /* TeslaAPI.swift in Sources */, FCE825FD1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, FC75EC4C1F2E068E007ED697 /* RequestProtocol.swift in Sources */, + FC0B57451FBF977B00AE4D0B /* BoolResponses.swift in Sources */, FC75EC541F2E4F2F007ED697 /* LockRequest.swift in Sources */, FCE946481F2CF10E004913FC /* AuthenticateRequest.swift in Sources */, FC1B80981F498A2E00D24310 /* MobileEnabledForVehicleRequest.swift in Sources */, + FCDDDABA1FBF32B70076D3ED /* VehicleState.swift in Sources */, FCE826021F8C075C00B72C08 /* ChargeState.swift in Sources */, FC80D5031F2CE6FD0013F6DF /* ListVehiclesRequest.swift in Sources */, FC99DFD91F1FBBE100013FE9 /* Error.swift in Sources */, FC75EC591F2E6219007ED697 /* OpenChargePortRequest.swift in Sources */, + FCDDDAB51FBF32440076D3ED /* VehicleStateRequest.swift in Sources */, FC99DFD61F1FB65A00013FE9 /* Token.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -625,13 +652,16 @@ FC48F9A51F1E7FB500D2400B /* TeslaAPI.swift in Sources */, FCE825FE1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, FC75EC4A1F2E068C007ED697 /* RequestProtocol.swift in Sources */, + FC0B57461FBF977B00AE4D0B /* BoolResponses.swift in Sources */, FC75EC551F2E4F30007ED697 /* LockRequest.swift in Sources */, FCE946491F2CF10E004913FC /* AuthenticateRequest.swift in Sources */, FC1B80991F498A2F00D24310 /* MobileEnabledForVehicleRequest.swift in Sources */, + FCDDDABB1FBF32B70076D3ED /* VehicleState.swift in Sources */, FCE826031F8C075C00B72C08 /* ChargeState.swift in Sources */, FC80D5041F2CE6FD0013F6DF /* ListVehiclesRequest.swift in Sources */, FC99DFDA1F1FBBE100013FE9 /* Error.swift in Sources */, FC75EC5A1F2E621A007ED697 /* OpenChargePortRequest.swift in Sources */, + FCDDDAB61FBF32440076D3ED /* VehicleStateRequest.swift in Sources */, FC99DFD51F1FB65A00013FE9 /* Token.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -645,13 +675,16 @@ FC48F9A41F1E7FB400D2400B /* TeslaAPI.swift in Sources */, FCE825FF1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, FC75EC491F2E068C007ED697 /* RequestProtocol.swift in Sources */, + FC0B57471FBF977B00AE4D0B /* BoolResponses.swift in Sources */, FC75EC561F2E4F30007ED697 /* LockRequest.swift in Sources */, FCE9464A1F2CF10E004913FC /* AuthenticateRequest.swift in Sources */, FC1B809A1F498A3000D24310 /* MobileEnabledForVehicleRequest.swift in Sources */, + FCDDDABC1FBF32B70076D3ED /* VehicleState.swift in Sources */, FCE826041F8C075C00B72C08 /* ChargeState.swift in Sources */, FC80D5051F2CE6FD0013F6DF /* ListVehiclesRequest.swift in Sources */, FC99DFDB1F1FBBE100013FE9 /* Error.swift in Sources */, FC75EC5B1F2E621A007ED697 /* OpenChargePortRequest.swift in Sources */, + FCDDDAB71FBF32440076D3ED /* VehicleStateRequest.swift in Sources */, FC99DFD41F1FB65A00013FE9 /* Token.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -661,6 +694,7 @@ buildActionMask = 2147483647; files = ( FC27C98C1F4995240064E1AE /* OpenChargePortRequestTests.swift in Sources */, + FCDDDABF1FBF342F0076D3ED /* VehicleStateRequestTests.swift in Sources */, FC27C9831F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, FCF2A12A1FA32CE900BA5C3F /* Credentials.swift in Sources */, FC6F72E61FA12BB3009BF1C4 /* ChargeStateRequestTests.swift in Sources */, @@ -676,6 +710,7 @@ buildActionMask = 2147483647; files = ( FC27C98D1F4995250064E1AE /* OpenChargePortRequestTests.swift in Sources */, + FCDDDAC01FBF342F0076D3ED /* VehicleStateRequestTests.swift in Sources */, FC27C9841F4995240064E1AE /* ListVehiclesRequestTests.swift in Sources */, FCF2A12B1FA32CE900BA5C3F /* Credentials.swift in Sources */, FC6F72E71FA12BB4009BF1C4 /* ChargeStateRequestTests.swift in Sources */, @@ -695,13 +730,16 @@ FC48F9AB1F1E7FBB00D2400B /* WebRequest.swift in Sources */, FCE825FC1F8C068F00B72C08 /* ChargeStateRequest.swift in Sources */, FC75EC4B1F2E068D007ED697 /* RequestProtocol.swift in Sources */, + FC0B57441FBF977B00AE4D0B /* BoolResponses.swift in Sources */, FCE946471F2CF10E004913FC /* AuthenticateRequest.swift in Sources */, FC75EC531F2E4AD7007ED697 /* LockRequest.swift in Sources */, FC1B80971F49883500D24310 /* MobileEnabledForVehicleRequest.swift in Sources */, + FCDDDAB91FBF32B70076D3ED /* VehicleState.swift in Sources */, FCE826011F8C075C00B72C08 /* ChargeState.swift in Sources */, FC80D5021F2CE6FD0013F6DF /* ListVehiclesRequest.swift in Sources */, FC99DFD81F1FBBE100013FE9 /* Error.swift in Sources */, FC75EC581F2E613A007ED697 /* OpenChargePortRequest.swift in Sources */, + FCDDDAB41FBF32440076D3ED /* VehicleStateRequest.swift in Sources */, FC48F9A71F1E7FB600D2400B /* TeslaAPI.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -735,7 +773,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "se.jagcesar.TeslaAPI-iOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.0; }; name = Debug; @@ -748,7 +785,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "se.jagcesar.TeslaAPI-iOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.0; }; name = Release; @@ -1072,7 +1108,6 @@ PRODUCT_NAME = TeslaAPI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.0; }; name = Debug; @@ -1093,7 +1128,6 @@ PRODUCT_NAME = TeslaAPI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.0; }; name = Release; diff --git a/Tests/ModelMocks.swift b/Tests/ModelMocks.swift index c46352f..43984cc 100644 --- a/Tests/ModelMocks.swift +++ b/Tests/ModelMocks.swift @@ -2,25 +2,25 @@ class ModelMocks { // swiftlint:disable force_try - static let token = try! Token( - // swiftlint:enable force_try - dictionary: [ - "access_token": "" as AnyObject, - "token_type": "" as AnyObject, - "expires_in": 0.0 as AnyObject, - "created_at": 0.0 as AnyObject, - "refresh_token": "" as AnyObject - ]) - - static let vehicle = Vehicle( - dictionary: [ - "color": "" as AnyObject, - "id": 0 as AnyObject, - "option_codes": "" as AnyObject, - "vehicle_id": 0 as AnyObject, - "vin": "" as AnyObject, - "tokens": [""] as AnyObject, - "state": "online" as AnyObject, - "display_name": "Zeus" as AnyObject, - ])! +// static let token = try! Token( +// // swiftlint:enable force_try +// dictionary: [ +// "access_token": "" as AnyObject, +// "token_type": "" as AnyObject, +// "expires_in": 0.0 as AnyObject, +// "created_at": 0.0 as AnyObject, +// "refresh_token": "" as AnyObject +// ]) +// +// static let vehicle = Vehicle( +// dictionary: [ +// "color": "" as AnyObject, +// "id": 0 as AnyObject, +// "option_codes": "" as AnyObject, +// "vehicle_id": 0 as AnyObject, +// "vin": "" as AnyObject, +// "tokens": [""] as AnyObject, +// "state": "online" as AnyObject, +// "display_name": "Zeus" as AnyObject, +// ])! } diff --git a/Tests/Request Tests/ChargeStateRequestTests.swift b/Tests/Request Tests/ChargeStateRequestTests.swift index f1cf2ee..34e087b 100644 --- a/Tests/Request Tests/ChargeStateRequestTests.swift +++ b/Tests/Request Tests/ChargeStateRequestTests.swift @@ -8,9 +8,11 @@ extension TeslaAPITests { accessToken: accessToken(), vehicleIdentifier: vehicleIdentifier()).execute { result in switch result { - case .success(_): + case .success(let chargeState): + print(chargeState) waitExpectation.fulfill() - case .failure(_): + case .failure(let error): + print(error) XCTFail() } } diff --git a/Tests/Request Tests/ListVehiclesRequestTests.swift b/Tests/Request Tests/ListVehiclesRequestTests.swift index 7fe59cd..3b313d4 100644 --- a/Tests/Request Tests/ListVehiclesRequestTests.swift +++ b/Tests/Request Tests/ListVehiclesRequestTests.swift @@ -9,9 +9,11 @@ extension TeslaAPITests { accessToken: accessToken()).execute { result in XCTAssert(Thread.isMainThread) switch result { - case .success(_): + case .success(let vehicles): + print(vehicles) waitExpectation.fulfill() - case .failure(_): + case .failure(let error): + print(error) XCTFail() } } diff --git a/Tests/Request Tests/LockRequestTests.swift b/Tests/Request Tests/LockRequestTests.swift index 2a58f65..d166eae 100644 --- a/Tests/Request Tests/LockRequestTests.swift +++ b/Tests/Request Tests/LockRequestTests.swift @@ -11,9 +11,11 @@ extension TeslaAPITests { state: .lock).execute { result in XCTAssert(Thread.isMainThread) switch result { - case .success(_): + case .success(let result): + print(result) waitExpectation.fulfill() - case .failure(_): + case .failure(let error): + print(error) XCTFail() } } @@ -29,9 +31,11 @@ extension TeslaAPITests { state: .unlock).execute { result in XCTAssert(Thread.isMainThread) switch result { - case .success(_): + case .success(let result): + print(result) waitExpectation.fulfill() - case .failure(_): + case .failure(let error): + print(error) XCTFail() } } diff --git a/Tests/Request Tests/MobileEnabledForRequestTests.swift b/Tests/Request Tests/MobileEnabledForRequestTests.swift index c303353..eee314c 100644 --- a/Tests/Request Tests/MobileEnabledForRequestTests.swift +++ b/Tests/Request Tests/MobileEnabledForRequestTests.swift @@ -6,13 +6,15 @@ extension TeslaAPITests { let waitExpectation = expectation(description: "Check if mobile is enabled on vehicle") MobileEnabledForVehicleRequest( - vehicle: ModelMocks.vehicle, + vehicleIdentifier: vehicleIdentifier(), accessToken: accessToken()).execute { result in XCTAssert(Thread.isMainThread) switch result { - case .success(_): + case .success(let result): + print(result) waitExpectation.fulfill() - case .failure(_): + case .failure(let error): + print(error) XCTFail() } } diff --git a/Tests/Request Tests/OpenChargePortRequestTests.swift b/Tests/Request Tests/OpenChargePortRequestTests.swift index a708c60..1837894 100644 --- a/Tests/Request Tests/OpenChargePortRequestTests.swift +++ b/Tests/Request Tests/OpenChargePortRequestTests.swift @@ -10,9 +10,11 @@ extension TeslaAPITests { vehicleIdentifier: vehicleIdentifier()).execute { result in XCTAssert(Thread.isMainThread) switch result { - case .success(_): + case .success(let result): + print(result) waitExpectation.fulfill() - case .failure(_): + case .failure(let error): + print(error) XCTFail() } } diff --git a/Tests/Request Tests/VehicleStateRequestTests.swift b/Tests/Request Tests/VehicleStateRequestTests.swift new file mode 100644 index 0000000..8e547f8 --- /dev/null +++ b/Tests/Request Tests/VehicleStateRequestTests.swift @@ -0,0 +1,22 @@ +import XCTest +@testable import TeslaAPI + +extension TeslaAPITests { + func testVehicleState() { + let waitExpectation = expectation(description: "Vehicle state") + VehicleStateRequest( + accessToken: accessToken(), + vehicleIdentifier: vehicleIdentifier()) + .execute { result in + switch result { + case .failure(let error): + print(error) + XCTFail() + case .success(let vehicleState): + print(vehicleState) + waitExpectation.fulfill() + } + } + waitForExpectations(timeout: 30, handler: nil) + } +} diff --git a/Tests/TeslaAPITests.swift b/Tests/TeslaAPITests.swift index d780ce2..3dac3c6 100644 --- a/Tests/TeslaAPITests.swift +++ b/Tests/TeslaAPITests.swift @@ -10,9 +10,11 @@ class TeslaAPITests: XCTestCase { password: password()).execute { result in XCTAssert(Thread.isMainThread) switch result { - case .success(_): + case .success(let token): + print(token) waitExpectation.fulfill() - case .failure(_): + case .failure(let error): + print(error) XCTFail() } } From d6d68287ce78665f63eae0df712a511a0ac24a12 Mon Sep 17 00:00:00 2001 From: Simon Westerlund Date: Fri, 30 Mar 2018 00:35:40 +0200 Subject: [PATCH 2/2] Implement Swift 4.1 snake case decoder --- Source/Models/ChargeState.swift | 59 +++---------------- Source/Models/Token.swift | 10 +--- Source/Models/Vehicle.swift | 24 +++----- Source/Requests/AuthenticateRequest.swift | 4 +- Source/Requests/ChargeStateRequest.swift | 4 +- Source/Requests/ListVehiclesRequest.swift | 4 +- Source/Requests/LockRequest.swift | 4 +- .../MobileEnabledForVehicleRequest.swift | 4 +- Source/Requests/OpenChargePortRequest.swift | 4 +- Source/Requests/VehicleStateRequest.swift | 1 + 10 files changed, 35 insertions(+), 83 deletions(-) diff --git a/Source/Models/ChargeState.swift b/Source/Models/ChargeState.swift index dfd5b7a..22dc567 100644 --- a/Source/Models/ChargeState.swift +++ b/Source/Models/ChargeState.swift @@ -4,12 +4,13 @@ public class ChargeStateResponse: Codable { let response: ChargeState } -public class ChargeState: Codable { -// public enum ChargingState: String { -// case disconnected = "Disconnected" -// case charging = "Charging" -// case complete = "Complete" -// } +public struct ChargeState: Codable { + + public enum ChargingState: String, Codable { + case disconnected = "Disconnected" + case charging = "Charging" + case complete = "Complete" + } public let batteryHeaterOn: Bool? public let batteryLevel: Int @@ -33,7 +34,7 @@ public class ChargeState: Codable { public let chargerPilotCurrent: Int? public let chargerPower: Int public let chargerVoltage: Int -// public let chargingState: ChargingState + public let chargingState: ChargingState public let estBatteryRange: Double public let euVehicle: Bool public let fastChargerPresent: Bool @@ -52,48 +53,4 @@ public class ChargeState: Codable { public let tripCharging: Bool? public let usableBatteryLevel: Int public let userChargeEnableRequest: Bool? - - private enum CodingKeys: String, CodingKey { - case batteryHeaterOn = "battery_heater_on" - case batteryLevel = "battery_level" - case batteryRange = "battery_range" - case chargeCurrentRequest = "charge_current_request" - case chargeCurrentRequestMax = "charge_current_request_max" - case chargeEnableRequest = "charge_enable_request" - case chargeEnergyAdded = "charge_energy_added" - case chargeLimitSoc = "charge_limit_soc" - case chargeLimitSocMax = "charge_limit_soc_max" - case chargeLimitSocMin = "charge_limit_soc_min" - case chargeLimitSocStd = "charge_limit_soc_std" - case chargeMilesAddedIdeal = "charge_miles_added_ideal" - case chargeMilesAddedRated = "charge_miles_added_rated" - case chargePortDoorOpen = "charge_port_door_open" - case chargePortLatch = "charge_port_latch" - case chargeRate = "charge_rate" - case chargeToMaxRange = "charge_to_max_range" - case chargerActualCurrent = "charger_actual_current" - case chargerPhases = "charger_phases" - case chargerPilotCurrent = "charger_pilot_current" - case chargerPower = "charger_power" - case chargerVoltage = "charger_voltage" - // case chargingState = "charging_state" - case estBatteryRange = "est_battery_range" - case euVehicle = "eu_vehicle" - case fastChargerPresent = "fast_charger_present" - case fastChargerType = "fast_charger_type" - case idealBatteryRange = "ideal_battery_range" - case managedChargingActive = "managed_charging_active" - case managedChargingStartTime = "managed_charging_start_time" - case managedChargingUserCanceled = "managed_charging_user_canceled" - case maxRangeChargeCounter = "max_range_charge_counter" - case motorizedChargePort = "motorized_charge_port" - case notEnoughPowerToHeat = "not_enough_power_to_heat" - case scheduledChargingPending = "scheduled_charging_pending" - case scheduledChargingStartTime = "scheduled_charging_start_time" - case timeToFullCharge = "time_to_full_charge" - case timestamp = "timestamp" - case tripCharging = "trip_charging" - case usableBatteryLevel = "usable_battery_level" - case userChargeEnableRequest = "user_charge_enable_request" - } } diff --git a/Source/Models/Token.swift b/Source/Models/Token.swift index 0023479..b6d023d 100644 --- a/Source/Models/Token.swift +++ b/Source/Models/Token.swift @@ -4,18 +4,10 @@ public class TokenResponse: Codable { let response: Token } -public class Token: Codable { +public struct Token: Codable { public let accessToken: String public let type: String public let expires: Date public let created: Date public let refreshToken: String - - private enum CodingKeys: String, CodingKey { - case accessToken = "access_token" - case type = "token_type" - case expires = "expires_in" - case created = "created_at" - case refreshToken = "refresh_token" - } } diff --git a/Source/Models/Vehicle.swift b/Source/Models/Vehicle.swift index 7790dbb..512ad14 100644 --- a/Source/Models/Vehicle.swift +++ b/Source/Models/Vehicle.swift @@ -4,10 +4,12 @@ public class VehicleResponse: Codable { let response: [Vehicle] } -public class Vehicle: Codable { -// public enum State: String { -// case online -// } +public struct Vehicle: Codable { + + public enum State: String, Codable { + case online + } + public let color: String? public let displayName: String? public let identifier: Int @@ -15,17 +17,5 @@ public class Vehicle: Codable { public let vehicleIdentifier: Int public let vin: String public let tokens: [String] -// public let state: State - - private enum CodingKeys: String, CodingKey { - case color = "color" - case displayName = "display_name" - case identifier = "id" - case optionCodes = "option_codes" - case vehicleIdentifier = "vehicle_id" - case vin - case tokens = "tokens" -// case state - } + public let state: State } - diff --git a/Source/Requests/AuthenticateRequest.swift b/Source/Requests/AuthenticateRequest.swift index 13c1d7a..cfad6e2 100644 --- a/Source/Requests/AuthenticateRequest.swift +++ b/Source/Requests/AuthenticateRequest.swift @@ -33,7 +33,9 @@ public struct AuthenticateRequest: RequestProtocol { } } else if let data = data { do { - let token = try JSONDecoder().decode(Token.self, from: data) + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let token = try decoder.decode(Token.self, from: data) DispatchQueue.main.async { completion(Result.success(token)) } diff --git a/Source/Requests/ChargeStateRequest.swift b/Source/Requests/ChargeStateRequest.swift index 0e7515a..0daaf32 100644 --- a/Source/Requests/ChargeStateRequest.swift +++ b/Source/Requests/ChargeStateRequest.swift @@ -25,7 +25,9 @@ public struct ChargeStateRequest: RequestProtocol { } } else if let data = data { do { - let chargeStateResponse = try JSONDecoder().decode(ChargeStateResponse.self, from: data) + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let chargeStateResponse = try decoder.decode(ChargeStateResponse.self, from: data) DispatchQueue.main.async { completion(Result.success(chargeStateResponse.response)) } diff --git a/Source/Requests/ListVehiclesRequest.swift b/Source/Requests/ListVehiclesRequest.swift index 04c7da0..28e1c20 100644 --- a/Source/Requests/ListVehiclesRequest.swift +++ b/Source/Requests/ListVehiclesRequest.swift @@ -21,7 +21,9 @@ public struct ListVehiclesRequest: RequestProtocol { } } else if let data = data { do { - let vehicleResponse = try JSONDecoder().decode(VehicleResponse.self, from: data) + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let vehicleResponse = try decoder.decode(VehicleResponse.self, from: data) DispatchQueue.main.async { completion(Result.success(vehicleResponse.response)) } diff --git a/Source/Requests/LockRequest.swift b/Source/Requests/LockRequest.swift index 5cddcee..b701a7e 100644 --- a/Source/Requests/LockRequest.swift +++ b/Source/Requests/LockRequest.swift @@ -36,7 +36,9 @@ public struct LockRequest: RequestProtocol { } } else if let data = data { do { - let resultResponse = try JSONDecoder().decode(ResultResponse.self, from: data) + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let resultResponse = try decoder.decode(ResultResponse.self, from: data) DispatchQueue.main.async { completion(Result.success(resultResponse.response.result)) } diff --git a/Source/Requests/MobileEnabledForVehicleRequest.swift b/Source/Requests/MobileEnabledForVehicleRequest.swift index baaedcf..6d3dccf 100644 --- a/Source/Requests/MobileEnabledForVehicleRequest.swift +++ b/Source/Requests/MobileEnabledForVehicleRequest.swift @@ -25,7 +25,9 @@ struct MobileEnabledForVehicleRequest: RequestProtocol { } } else if let data = data { do { - let boolResponse = try JSONDecoder().decode(BoolResponse.self, from: data) + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let boolResponse = try decoder.decode(BoolResponse.self, from: data) DispatchQueue.main.async { completion(Result.success(boolResponse.response)) } diff --git a/Source/Requests/OpenChargePortRequest.swift b/Source/Requests/OpenChargePortRequest.swift index c687222..986e6a9 100644 --- a/Source/Requests/OpenChargePortRequest.swift +++ b/Source/Requests/OpenChargePortRequest.swift @@ -25,7 +25,9 @@ public struct OpenChargePortRequest: RequestProtocol { } } else if let data = data { do { - let result = try JSONDecoder().decode(ResultResponse.self, from: data) + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let result = try decoder.decode(ResultResponse.self, from: data) DispatchQueue.main.async { completion(Result.success(result.response.result)) } diff --git a/Source/Requests/VehicleStateRequest.swift b/Source/Requests/VehicleStateRequest.swift index 1931c4e..b339e1c 100644 --- a/Source/Requests/VehicleStateRequest.swift +++ b/Source/Requests/VehicleStateRequest.swift @@ -22,6 +22,7 @@ public struct VehicleStateRequest: RequestProtocol { // VehicleStateRequest.printStruct(dict: response) do { let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase let vehicleStateResponse = try decoder.decode(VehicleStateResponse.self, from: data) DispatchQueue.main.async { completion(Result.success(vehicleStateResponse.response))