Skip to content

Commit 0ef509e

Browse files
committed
Added Codable support for Result.
1 parent a6f552f commit 0ef509e

2 files changed

Lines changed: 61 additions & 188 deletions

File tree

Sources/Service.swift

Lines changed: 59 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -30,158 +30,120 @@ open class Service
3030
{
3131
/// Returned when the `URLComponents` structure fails to initialize, most likely because of the `query` parameter.
3232
case invalidURL
33-
3433
/// Return when the `URLComponents` structure fails to create a valid `URL`.
3534
case malformedURL
36-
3735
/// Returned when the request data is invalid.
3836
case invalidRequestData
39-
4037
/// Returned when the server response contained invalid data.
4138
case invalidResponseData
4239
}
4340

4441
/// This is the status type returned by IPAPI.
45-
public enum Status: String
42+
public enum Status: String, Codable
4643
{
4744
/// Returned when the server is unable to process the request.
4845
case fail
49-
5046
/// Returned when the request has succeeded.
5147
case success
5248
}
5349

54-
/// This is the field type used by the IPAPI service to filter out unnecessary data.
55-
public enum Field: String
56-
{
57-
/// AS number and name.
58-
case `as` = "as"
59-
60-
/// City.
61-
case city
62-
63-
/// Country code short.
64-
case countryCode
65-
66-
/// Country name.
67-
case countryName = "country"
68-
69-
/// IP.
70-
case ip = "query"
71-
72-
/// Internet Service Provider name.
73-
case isp
74-
75-
/// Latitude.
76-
case latitude = "lat"
77-
78-
/// Longitude.
79-
case longitude = "lon"
80-
81-
/// Error message.
82-
case message
83-
84-
/// Mobile (cellular) connection.
85-
case mobile
86-
87-
/// Organization name.
88-
case organization = "org"
89-
90-
/// Proxy (anonymous).
91-
case proxy
92-
93-
/// Region/State code short.
94-
case regionCode = "region"
95-
96-
/// Region/State name.
97-
case regionName
98-
99-
/// Reverse DNS of the IP.
100-
case reverse
101-
102-
/// Status.
103-
case status
104-
105-
/// Timezone.
106-
case timezone
107-
108-
/// Zip code.
109-
case zipCode = "zip"
110-
111-
/// Returns an `Array` with all the fields.
112-
public static var all: [Field] {
113-
return [.`as`, .city, .countryCode, .countryName, .ip, .isp, .latitude, .longitude, .message, .mobile,
114-
.organization, .proxy, .regionCode, .regionName, .reverse, .status, .timezone, .zipCode]
115-
}
116-
}
117-
11850
/// This is the result type returned by IPAPI.
119-
public struct Result
51+
public struct Result: Codable
12052
{
12153
/// AS number and name, separated by space. Example: `"AS15169 Google Inc."`
12254
public var `as`: String?
123-
12455
/// City. Example: `"Mountain View"`
12556
public var city: String?
126-
12757
/// Country code short. Example: `"US"`
12858
public var countryCode: String?
129-
13059
/// Country name. Example: `"United States"`
13160
public var countryName: String?
132-
13361
/// IP used for the query. Example: `"173.194.67.94"`
13462
public var ip: String?
135-
13663
/// Internet Service Provider name. Example: `"Google"`
13764
public var isp: String?
138-
13965
/// Latitude. Example: `37.4192`
14066
public var latitude: Double?
141-
14267
/// Longitude. Example: `-122.0574`
14368
public var longitude: Double?
144-
14569
/// Error message. Example: `"reserved range"`
14670
public var message: String?
147-
14871
/// Mobile (cellular) connection. Example: `true`
14972
public var mobile: Bool?
150-
15173
/// Organization name. Example: `"Google"`
15274
public var organization: String?
153-
15475
/// Proxy (anonymous). Example: `true`
15576
public var proxy: Bool?
156-
15777
/// Region/State code short. Example: `"CA"` or `"10"`
15878
public var regionCode: String?
159-
16079
/// Region/State name. Example: `"California"`
16180
public var regionName: String?
162-
16381
/// Reverse DNS of the IP. Example: `"wi-in-f94.1e100.net"`
16482
public var reverse: String?
165-
16683
/// Status. Example: `success`
16784
public var status: Status?
168-
16985
/// Timezone. Example: `"America/Los_Angeles"`
17086
public var timezone: String?
171-
17287
/// Zip code. Example: `"94043"`
17388
public var zipCode: String?
89+
90+
/// This is the field type used by the IPAPI service to filter out unnecessary data.
91+
public enum CodingKeys: String, CodingKey {
92+
/// AS number and name.
93+
case `as` = "as"
94+
/// City.
95+
case city
96+
/// Country code short.
97+
case countryCode
98+
/// Country name.
99+
case countryName = "country"
100+
/// IP.
101+
case ip = "query"
102+
/// Internet Service Provider name.
103+
case isp
104+
/// Latitude.
105+
case latitude = "lat"
106+
/// Longitude.
107+
case longitude = "lon"
108+
/// Error message.
109+
case message
110+
/// Mobile (cellular) connection.
111+
case mobile
112+
/// Organization name.
113+
case organization = "org"
114+
/// Proxy (anonymous).
115+
case proxy
116+
/// Region/State code short.
117+
case regionCode = "region"
118+
/// Region/State name.
119+
case regionName
120+
/// Reverse DNS of the IP.
121+
case reverse
122+
/// Status.
123+
case status
124+
/// Timezone.
125+
case timezone
126+
/// Zip code.
127+
case zipCode = "zip"
128+
129+
/// Returns an `Array` with all the fields.
130+
public static var all: [CodingKeys] {
131+
return [.`as`, .city, .countryCode, .countryName, .ip, .isp, .latitude, .longitude, .message, .mobile,
132+
.organization, .proxy, .regionCode, .regionName, .reverse, .status, .timezone, .zipCode]
133+
}
134+
}
135+
136+
/// This is the typical JSON type used by webservices.
137+
public typealias Field = CodingKeys
174138
}
175139

176140
/// This is the request type used by the `batch` method.
177141
public struct Request
178142
{
179143
/// The IP address to lookup. This parameter is required.
180144
public var query: String
181-
182145
/// If you don't require all the returned fields use this property to specify which fields to return. *Tip: Disabling* `reverse` *may improve performance*. This parameter is optional.
183-
public var fields: [Field]? = nil
184-
146+
public var fields: [Result.Field]? = nil
185147
/// Localized `city`, `regionName` and `countryName` can be requested by using this property in the `ISO 639` format. This parameter is optional.
186148
public var language: String? = nil
187149

@@ -195,7 +157,7 @@ open class Service
195157
/// - language: The language to use for the city, region and country names.
196158
///
197159
/// - Returns: The new `Request` instance.
198-
init(query: String, fields: [Field]? = nil, language: String? = nil) {
160+
init(query: String, fields: [Result.Field]? = nil, language: String? = nil) {
199161
self.query = query
200162
self.fields = fields
201163
self.language = language
@@ -247,7 +209,7 @@ open class Service
247209
/// - language: Localized `city`, `regionName` and `countryName` can be requested by using this property in the `ISO 639` format.
248210
/// - completion: A closure that will be called upon completion.
249211
/// - Returns: The new `URLSessionDataTask` instance.
250-
@discardableResult open func fetch(query: String? = nil, fields: [Field]? = nil, language: String? = nil, completion: ((_ result: Result?, _ error: Swift.Error?) -> Void)?) -> URLSessionDataTask?
212+
@discardableResult open func fetch(query: String? = nil, fields: [Result.Field]? = nil, language: String? = nil, completion: ((_ result: Result?, _ error: Swift.Error?) -> Void)?) -> URLSessionDataTask?
251213
{
252214
var urlString = "\(type(of: self).baseURLString)/json"
253215
if let query = query {
@@ -283,8 +245,8 @@ open class Service
283245
if let error = error {
284246
completion?(nil, error)
285247
} else {
286-
if let data = data, let object = try? JSONSerialization.jsonObject(with: data, options: []), let json = object as? JSON {
287-
let result = Result(json: json)
248+
let decoder = JSONDecoder()
249+
if let data = data, let result = try? decoder.decode(Result.self, from: data) {
288250
completion?(result, nil)
289251
} else {
290252
completion?(nil, Error.invalidResponseData)
@@ -333,8 +295,8 @@ open class Service
333295
if let error = error {
334296
completion?(nil, error)
335297
} else {
336-
if let data = data, let object = try? JSONSerialization.jsonObject(with: data, options: []), let json = object as? [JSON] {
337-
let result = json.flatMap { Result(json: $0) }
298+
let decoder = JSONDecoder()
299+
if let data = data, let result = try? decoder.decode([Result].self, from: data) {
338300
completion?(result, nil)
339301
} else {
340302
completion?(nil, Error.invalidResponseData)
@@ -348,79 +310,6 @@ open class Service
348310

349311
}
350312

351-
//
352-
// # Result: JSON Extension
353-
//
354-
355-
public extension Service.Result
356-
{
357-
358-
// MARK: - Deserialization -
359-
360-
/// Initializes the `Result` instance with a given JSON object.
361-
///
362-
/// - Parameters:
363-
/// - json: The JSON object.
364-
///
365-
/// - Returns: The new `Result` instance.
366-
init?(json: Service.JSON)
367-
{
368-
let statusString = json[Service.Field.status.rawValue] as? String
369-
370-
self.`as` = json[Service.Field.`as`.rawValue] as? String
371-
self.city = json[Service.Field.city.rawValue] as? String
372-
self.countryCode = json[Service.Field.countryCode.rawValue] as? String
373-
self.countryName = json[Service.Field.countryName.rawValue] as? String
374-
self.ip = json[Service.Field.ip.rawValue] as? String
375-
self.isp = json[Service.Field.isp.rawValue] as? String
376-
self.latitude = json[Service.Field.latitude.rawValue] as? Double
377-
self.longitude = json[Service.Field.longitude.rawValue] as? Double
378-
self.message = json[Service.Field.message.rawValue] as? String
379-
self.mobile = json[Service.Field.mobile.rawValue] as? Bool
380-
self.organization = json[Service.Field.organization.rawValue] as? String
381-
self.proxy = json[Service.Field.proxy.rawValue] as? Bool
382-
self.regionCode = json[Service.Field.regionCode.rawValue] as? String
383-
self.regionName = json[Service.Field.regionName.rawValue] as? String
384-
self.reverse = json[Service.Field.reverse.rawValue] as? String
385-
self.timezone = json[Service.Field.timezone.rawValue] as? String
386-
self.zipCode = json[Service.Field.zipCode.rawValue] as? String
387-
388-
if let statusString = statusString {
389-
self.status = Service.Status(rawValue: statusString)
390-
}
391-
}
392-
393-
// MARK: - Serialization -
394-
395-
/// Serializes the object to the IPAPI format.
396-
func toJSON() -> Service.JSON
397-
{
398-
var ret: Service.JSON = [:]
399-
400-
ret[Service.Field.`as`.rawValue] = self.`as` as AnyObject?
401-
ret[Service.Field.city.rawValue] = self.city as AnyObject?
402-
ret[Service.Field.countryCode.rawValue] = self.countryCode as AnyObject?
403-
ret[Service.Field.countryName.rawValue] = self.countryName as AnyObject?
404-
ret[Service.Field.ip.rawValue] = self.ip as AnyObject?
405-
ret[Service.Field.isp.rawValue] = self.isp as AnyObject?
406-
ret[Service.Field.latitude.rawValue] = self.latitude as AnyObject?
407-
ret[Service.Field.longitude.rawValue] = self.longitude as AnyObject?
408-
ret[Service.Field.message.rawValue] = self.message as AnyObject?
409-
ret[Service.Field.mobile.rawValue] = self.mobile as AnyObject?
410-
ret[Service.Field.organization.rawValue] = self.organization as AnyObject?
411-
ret[Service.Field.proxy.rawValue] = self.proxy as AnyObject?
412-
ret[Service.Field.regionCode.rawValue] = self.regionCode as AnyObject?
413-
ret[Service.Field.regionName.rawValue] = self.regionName as AnyObject?
414-
ret[Service.Field.reverse.rawValue] = self.reverse as AnyObject?
415-
ret[Service.Field.status.rawValue] = self.status as AnyObject?
416-
ret[Service.Field.timezone.rawValue] = self.timezone as AnyObject?
417-
ret[Service.Field.zipCode.rawValue] = self.zipCode as AnyObject?
418-
419-
return ret
420-
}
421-
422-
}
423-
424313
//
425314
// # Request: JSON Extension
426315
//
@@ -434,10 +323,8 @@ public extension Service.Request
434323
{
435324
/// Query.
436325
case query
437-
438326
/// Fields.
439327
case fields
440-
441328
/// Language.
442329
case language = "lang"
443330
}
@@ -458,7 +345,7 @@ public extension Service.Request
458345

459346
if let string = json[Service.Request.JSONKey.fields.rawValue] as? String {
460347
let fields = string.components(separatedBy: ",")
461-
self.fields = fields.flatMap { Service.Field(rawValue: $0) }
348+
self.fields = fields.flatMap { Service.Result.Field(rawValue: $0) }
462349
}
463350
} else {
464351
return nil
@@ -480,17 +367,3 @@ public extension Service.Request
480367
}
481368

482369
}
483-
484-
//
485-
// # Debug Extension
486-
//
487-
488-
public extension Service.Result
489-
{
490-
491-
/// The textual representation of the receiver, in the form of JSON.
492-
var description: String {
493-
return self.toJSON().description
494-
}
495-
496-
}

Tests/IPAPI_Tests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class IPAPI_Tests: XCTestCase
118118
var error: Error?
119119
let responseArrived = self.expectation(description: "Response of async request has arrived")
120120

121-
Service.default.fetch(query: "apple.com", fields: Service.Field.all) { r, e in
121+
Service.default.fetch(query: "apple.com", fields: Service.Result.Field.all) { r, e in
122122
result = r
123123
error = e
124124
responseArrived.fulfill()
@@ -160,7 +160,7 @@ class IPAPI_Tests: XCTestCase
160160
var error: Error?
161161
let responseArrived = self.expectation(description: "Response of async request has arrived")
162162

163-
Service.default.fetch(query: "apple.com", fields: Service.Field.all, language: "es") { r, e in
163+
Service.default.fetch(query: "apple.com", fields: Service.Result.Field.all, language: "es") { r, e in
164164
result = r
165165
error = e
166166
responseArrived.fulfill()

0 commit comments

Comments
 (0)