Skip to content

Commit

Permalink
Fixed parsing of the rate limit error response
Browse files Browse the repository at this point in the history
  • Loading branch information
1ec5 committed Feb 20, 2017
1 parent d38763b commit 29c900c
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 9 deletions.
47 changes: 39 additions & 8 deletions MapboxStatic/Snapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -525,23 +525,22 @@ open class Snapshot: NSObject {
/**
Returns an error that supplements the given underlying error with additional information from the an HTTP response’s body or headers.
*/
fileprivate static func descriptiveError(_ json: JSONDictionary, response: URLResponse?, underlyingError error: NSError?) -> NSError {
static func descriptiveError(_ json: JSONDictionary, response: URLResponse?, underlyingError error: NSError?) -> NSError {
var userInfo = error?.userInfo ?? [:]
if let response = response as? HTTPURLResponse {
var failureReason: String? = nil
var recoverySuggestion: String? = nil
switch response.statusCode {
case 429:
if let timeInterval = response.allHeaderFields["x-rate-limit-interval"] as? TimeInterval, let maximumCountOfRequests = response.allHeaderFields["x-rate-limit-limit"] as? UInt {
if let timeInterval = response.rateLimitInterval, let maximumCountOfRequests = response.rateLimit {
let intervalFormatter = DateComponentsFormatter()
intervalFormatter.unitsStyle = .full
let formattedInterval = intervalFormatter.string(from: timeInterval)
let formattedCount = NumberFormatter.localizedString(from: NSNumber(value: maximumCountOfRequests), number: .decimal)
let formattedInterval = intervalFormatter.string(from: timeInterval) ?? "\(timeInterval) seconds"
let formattedCount = NumberFormatter.localizedString(from: maximumCountOfRequests as NSNumber, number: .decimal)
failureReason = "More than \(formattedCount) requests have been made with this access token within a period of \(formattedInterval)."
}
if let rolloverTimestamp = response.allHeaderFields["x-rate-limit-reset"] as? Double {
let date = Date(timeIntervalSince1970: rolloverTimestamp)
let formattedDate = DateFormatter.localizedString(from: date, dateStyle: .long, timeStyle: .full)
if let rolloverTime = response.rateLimitResetTime {
let formattedDate = DateFormatter.localizedString(from: rolloverTime, dateStyle: .long, timeStyle: .full)
recoverySuggestion = "Wait until \(formattedDate) before retrying."
}
default:
Expand All @@ -550,7 +549,39 @@ open class Snapshot: NSObject {
userInfo[NSLocalizedFailureReasonErrorKey] = failureReason ?? userInfo[NSLocalizedFailureReasonErrorKey] ?? HTTPURLResponse.localizedString(forStatusCode: error?.code ?? -1)
userInfo[NSLocalizedRecoverySuggestionErrorKey] = recoverySuggestion ?? userInfo[NSLocalizedRecoverySuggestionErrorKey]
}
userInfo[NSUnderlyingErrorKey] = error
if let error = error {
userInfo[NSUnderlyingErrorKey] = error
}
return NSError(domain: error?.domain ?? MBStaticErrorDomain, code: error?.code ?? -1, userInfo: userInfo)
}
}

extension HTTPURLResponse {
static let rateLimitIntervalHeaderKey = "X-Rate-Limit-Interval"
static let rateLimitLimitHeaderKey = "X-Rate-Limit-Limit"
static let rateLimitResetHeaderKey = "X-Rate-Limit-Reset"

var rateLimit: UInt? {
guard let limit = allHeaderFields[HTTPURLResponse.rateLimitLimitHeaderKey] as? String else {
return nil
}
return UInt(limit)
}

var rateLimitInterval: TimeInterval? {
guard let interval = allHeaderFields[HTTPURLResponse.rateLimitIntervalHeaderKey] as? String else {
return nil
}
return TimeInterval(interval)
}

var rateLimitResetTime: Date? {
guard let resetTime = allHeaderFields[HTTPURLResponse.rateLimitResetHeaderKey] as? String else {
return nil
}
guard let resetTimeNumber = Double(resetTime) else {
return nil
}
return Date(timeIntervalSince1970: resetTimeNumber)
}
}
20 changes: 19 additions & 1 deletion MapboxStaticTests/MapboxStaticTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import XCTest
import OHHTTPStubs
import CoreLocation
import Foundation
import MapboxStatic
@testable import MapboxStatic

#if os(OSX)
typealias Color = NSColor
Expand All @@ -18,6 +18,9 @@ class MapboxStaticTests: XCTestCase {
override func setUp() {
super.setUp()
OHHTTPStubs.removeAllStubs()

// Make sure tests run in all time zones
NSTimeZone.default = TimeZone(secondsFromGMT: 0)!
}

fileprivate func parseQueryString(_ request: URLRequest) -> Dictionary<String, String> {
Expand Down Expand Up @@ -475,4 +478,19 @@ class MapboxStaticTests: XCTestCase {

waitForExpectations(timeout: 1, handler: nil)
}

func testRateLimitErrorParsing() {
let json = ["message" : "Hit rate limit"]

let url = URL(string: "https://api.mapbox.com")!
let headerFields = ["X-Rate-Limit-Interval" : "60", "X-Rate-Limit-Limit" : "600", "X-Rate-Limit-Reset" : "1479460584"]
let response = HTTPURLResponse(url: url, statusCode: 429, httpVersion: nil, headerFields: headerFields)

let error: NSError? = nil

let resultError = Snapshot.descriptiveError(json, response: response, underlyingError: error)

XCTAssertEqual(resultError.localizedFailureReason, "More than 600 requests have been made with this access token within a period of 1 minute.")
XCTAssertEqual(resultError.localizedRecoverySuggestion, "Wait until November 18, 2016 at 9:16:24 AM GMT before retrying.")
}
}

0 comments on commit 29c900c

Please sign in to comment.