Skip to content

Commit

Permalink
Merge pull request #4 from NobodyNada/feature/apiresponse-class
Browse files Browse the repository at this point in the history
Feature/apiresponse class
  • Loading branch information
NobodyNada authored Dec 17, 2016
2 parents c23f66f + 09239c6 commit 0818ad9
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 35 deletions.
25 changes: 17 additions & 8 deletions Sources/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ open class APIClient: NSObject, URLSessionDataDelegate {
///Which API filter to use if none is specified.
open var defaultFilter: String?

///Whether to use a secure connection to communicate with the API.
open var useSSL: Bool = true

///Which site to use if none is specified.
open var defaultSite: String = "stackoverflow"

Expand Down Expand Up @@ -91,11 +94,11 @@ open class APIClient: NSObject, URLSessionDataDelegate {
///
///- parameter request: The request to make, for example `users/{ids}/answers`.
///- parameter parameters: Parameters to be URLEncoded into the request.
open func performAPIRequest(
open func performAPIRequest<T>(
_ request: String,
_ parameters: [String:String] = [:],
backoffBehavior: BackoffBehavior = .wait
) throws -> [Any] {
) throws -> APIResponse<T> {

var params = parameters

Expand All @@ -111,9 +114,12 @@ open class APIClient: NSObject, URLSessionDataDelegate {
if params["site"] == nil {
params["site"] = defaultSite
}
else if params["site"] == "" {
params["site"] = nil
}

//Build the URL.
var url = "https://api.stackexchange.com/2.2"
var url = "\(useSSL ? "http" : "https")://api.stackexchange.com/2.2"

let prefixedRequest = (request.hasPrefix("/") ? request : "/" + request)
url += prefixedRequest
Expand Down Expand Up @@ -151,19 +157,22 @@ open class APIClient: NSObject, URLSessionDataDelegate {
throw APIError.notDictionary(response: response)
}

if let backoff = json["backoff"] as? Int {
let apiResponse = APIResponse<T>(dictionary: json)

if let backoff = apiResponse.backoff {
backoffs[backoffName] = Date().addingTimeInterval(TimeInterval(backoff))
}
cleanBackoffs()

guard json["error_id"] == nil, json["error_message"] == nil else {
guard apiResponse.error_id == nil, apiResponse.error_message == nil else {
throw APIError.apiError(id: json["error_id"] as? Int, message: json["error_message"] as? String)
}

maxQuota = (json["quota_max"] as? Int) ?? maxQuota
quota = (json["quota_remaining"] as? Int) ?? quota

return (json["items"] as? [Any]) ?? []
maxQuota = apiResponse.quota_max ?? maxQuota
quota = apiResponse.quota_remaining ?? quota

return apiResponse
}

internal func wait(until date: Date) {
Expand Down
123 changes: 123 additions & 0 deletions Sources/APIResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// APIResponse.swift
// SwiftStack
//
// Created by FelixSFD on 11.12.16.
//
//

import Foundation

/**
The common wrapper object that is returned by the StackExchange API.

- authors: FelixSFD, NobodyNada

- seealso: [StackExchange API](https://api.stackexchange.com/docs/wrapper)
*/
public class APIResponse<T: JsonConvertible>: JsonConvertible {

// - MARK: Items

/**
The items that are returned of the generic type `T`.

- note: It's always an array. Even if only a single item was requested.

- author: FelixSFD
*/
public var items: [T]?


// - MARK: Initializers

/**
Basic initializer without default values
*/
public init() {

}

public required convenience init?(jsonString json: String) {
do {
guard let dictionary = try JSONSerialization.jsonObject(with: json.data(using: String.Encoding.utf8)!) as? [String: Any] else {
return nil
}

self.init(dictionary: dictionary)
} catch {
return nil
}
}

public required init(dictionary: [String: Any]) {
self.backoff = dictionary["backoff"] as? Int
self.error_id = dictionary["error_id"] as? Int
self.error_message = dictionary["error_message"] as? String
self.error_name = dictionary["error_name"] as? String
self.has_more = dictionary["has_more"] as? Bool
self.page = dictionary["page"] as? Int
self.page_size = dictionary["page_size"] as? Int
self.quota_remaining = dictionary["quota_remaining"] as? Int
self.quota_max = dictionary["quota_max"] as? Int
self.total = dictionary["total"] as? Int
self.type = dictionary["type"] as? String


//items
if let array = dictionary["items"] as? [[String: Any]] {
items = array.map { T(dictionary: $0) }
}

}

// - MARK: JsonConvertible

public var dictionary: [String: Any] {
var dict = [String: Any]()

dict["backoff"] = backoff
dict["error_id"] = error_id
dict["error_message"] = error_message
dict["error_name"] = error_name
dict["has_more"] = has_more
dict["page"] = page
dict["page_size"] = page_size
dict["quota_max"] = quota_max
dict["quota_remaining"] = quota_remaining
dict["total"] = total
dict["type"] = type

return dict
}

public var jsonString: String? {
return (try? JsonHelper.jsonString(from: self)) ?? nil
}


// - MARK: Fields

public var backoff: Int?

public var error_id: Int?

public var error_message: String?

public var error_name: String?

public var has_more: Bool?

public var page: Int?

public var page_size: Int?

public var quota_max: Int?

public var quota_remaining: Int?

public var total: Int?

public var type: String?

}
6 changes: 1 addition & 5 deletions Sources/BadgeCount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,9 @@ public struct BadgeCount: JsonConvertible, CustomStringConvertible {

- author: FelixSFD
*/
public init?(dictionary: [String: Any]) {
public init(dictionary: [String: Any]) {
self.bronze = dictionary["bronze"] as? Int
self.silver = dictionary["silver"] as? Int
self.gold = dictionary["gold"] as? Int

if bronze == nil, silver == nil, gold == nil {
return nil
}
}
}
2 changes: 1 addition & 1 deletion Sources/DictionaryConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public protocol DictionaryConvertible {
/**
Initializes the object from a dictionary
*/
init?(dictionary: [String: Any])
init(dictionary: [String: Any])

/**
Returns the dictionary-representation of the object
Expand Down
2 changes: 1 addition & 1 deletion Sources/Question.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public class Question: Post {
}
}

public init?(dictionary: [String : Any]) {
public init(dictionary: [String : Any]) {
if let timestamp = dictionary["on_date"] as? Double {
self.on_date = Date(timeIntervalSince1970: timestamp)
}
Expand Down
70 changes: 70 additions & 0 deletions Sources/RequestsSites.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// RequestsSites.swift
// SwiftStack
//
// Created by FelixSFD on 11.12.16.
//
//

import Foundation
import Dispatch

/**
This extension contains all requests in the SITES section of the StackExchange API Documentation.

- authors: FelixSFD, NobodyNada
*/
public extension APIClient {

// - MARK: /sites
/**
Fetches all `Sites` in the Stack Exchange network synchronously.

- parameter parameters: The dictionary of parameters used in the request

- parameter backoffBehavior: The behavior when an APIRequest has a backoff

- returns: The list of sites as `APIResponse<Site>`

- author: NobodyNada
*/
public func fetchSites(
_ parameters: [String:String] = [:],
backoffBehavior: BackoffBehavior = .wait) throws -> APIResponse<Site> {


var params = parameters
params["site"] = ""

return try performAPIRequest(
"sites",
params,
backoffBehavior: backoffBehavior
)
}

/**
Fetches all `Sites` in the Stack Exchange network asynchronously.

- parameter parameters: The dictionary of parameters used in the request

- parameter backoffBehavior: The behavior when an APIRequest has a backoff

- author: FelixSFD
*/
public func fetchSites(_ parameters: [String: String] = [:], backoffBehavior: BackoffBehavior = .wait, completionHandler: @escaping (APIResponse<Site>?, Error?) -> ()) {

queue.async {
var params = parameters
params["site"] = ""

do {
let response: APIResponse<Site> = try self.performAPIRequest("sites", params, backoffBehavior: backoffBehavior)
completionHandler(response, nil)
} catch {
completionHandler(nil, error)
}
}
}

}
10 changes: 4 additions & 6 deletions Sources/Site.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class Site: JsonConvertible {

// - MARK: Initializers

public init?(dictionary: [String : Any]) {
public init(dictionary: [String : Any]) {
self.api_site_parameter = dictionary["api_site_parameter"] as? String
self.name = dictionary["name"] as? String

Expand Down Expand Up @@ -140,7 +140,7 @@ public class Site: JsonConvertible {

// - MARK: Initializers

public init?(dictionary: [String : Any]) {
public init(dictionary: [String : Any]) {
self.link_color = dictionary["link_color"] as? String
self.tag_background_color = dictionary["tag_background_color"] as? String
self.tag_foreground_color = dictionary["tag_foreground_color"] as? String
Expand Down Expand Up @@ -203,7 +203,7 @@ public class Site: JsonConvertible {

}

public required init?(dictionary: [String : Any]) {
public required init(dictionary: [String : Any]) {
self.aliases = dictionary["aliases"] as? [String]
self.api_site_parameter = dictionary["api_site_parameter"] as? String
self.audience = dictionary["audience"] as? String
Expand Down Expand Up @@ -243,9 +243,7 @@ public class Site: JsonConvertible {
var relatedArray = [Related]()

for related in relatedSites {
if let tmp = Related(dictionary: related) {
relatedArray.append(tmp)
}
relatedArray.append(Related(dictionary: related))
}

if relatedArray.count > 0 {
Expand Down
4 changes: 1 addition & 3 deletions Sources/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,7 @@ public class User: JsonConvertible, CustomStringConvertible {
self.answer_count = dictionary["answer_count"] as? Int

if let badgeCounts = dictionary["badge_counts"] as? [String: Any] {
if let badges = BadgeCount(dictionary: badgeCounts) {
self.badge_counts = badges
}
self.badge_counts = BadgeCount(dictionary: badgeCounts)
}

if let creationTimestamp = dictionary["creation_date"] as? Double {
Expand Down
Loading

0 comments on commit 0818ad9

Please sign in to comment.