Skip to content

Commit

Permalink
Merge branch 'codableResponse' of https://github.com/marcelmendesfilh…
Browse files Browse the repository at this point in the history
…o/appstoreconnect-swift-sdk into codableResponse
  • Loading branch information
marcelmendesfilho committed Apr 20, 2023
2 parents d1206d1 + f0ab87d commit 4835615
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 9 deletions.
16 changes: 16 additions & 0 deletions Sources/APIProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import Combine
import Crypto
#if canImport(FoundationNetworking)
import FoundationNetworking
Expand Down Expand Up @@ -131,6 +132,9 @@ public final class APIProvider {

/// The JSON encoder used to encode request parameters.
private let encoder: JSONEncoder

/// Exposes rate limit continously as requests are made.
public let rateLimitPublisher = PassthroughSubject<RateLimit, Never>()

/// Creates a new APIProvider instance which can be used to perform API Requests to the App Store Connect API.
///
Expand Down Expand Up @@ -231,6 +235,10 @@ private extension APIProvider {
func mapResponse<T: Decodable>(_ result: Result<Response<Data>, Swift.Error>) -> Result<T, Swift.Error> {
switch result {
case .success(let response):
if let rateLimit = response.rateLimit {
rateLimitPublisher.send(rateLimit)
}

guard let data = response.data, 200..<300 ~= response.statusCode else {
return .failure(Error.requestFailure(response.statusCode, response.errorResponse, response.requestURL))
}
Expand All @@ -257,6 +265,10 @@ private extension APIProvider {
func mapVoidResponse(_ result: Result<Response<Data>, Swift.Error>) -> Result<Void, Swift.Error> {
switch result {
case .success(let response):
if let rateLimit = response.rateLimit {
rateLimitPublisher.send(rateLimit)
}

guard 200..<300 ~= response.statusCode else {
return .failure(Error.requestFailure(response.statusCode, response.errorResponse, response.requestURL))
}
Expand All @@ -274,6 +286,10 @@ private extension APIProvider {
func mapResponse(_ result: Result<Response<URL>, Swift.Error>) -> Result<URL, Swift.Error> {
switch result {
case .success(let response):
if let rateLimit = response.rateLimit {
rateLimitPublisher.send(rateLimit)
}

guard 200..<300 ~= response.statusCode else {
return .failure(Error.requestFailure(response.statusCode, response.errorResponse, response.requestURL))
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/DefaultRequestExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func mapResponse(data: Data?, urlResponse: URLResponse?, error: Error?) -> Resul
return .failure(DefaultRequestExecutor.Error.unknownResponseType)
}

return .success(.init(requestURL: httpUrlResponse.url, statusCode: httpUrlResponse.statusCode, data: data))
return .success(.init(requestURL: httpUrlResponse.url, statusCode: httpUrlResponse.statusCode, rateLimit: httpUrlResponse.rateLimit, data: data))
}
}

Expand All @@ -83,6 +83,6 @@ func mapResponse(fileUrl: URL?, urlResponse: URLResponse?, error: Error?) -> Res
return .failure(DefaultRequestExecutor.Error.unknownResponseType)
}

return .success(.init(requestURL: httpUrlResponse.url, statusCode: httpUrlResponse.statusCode, data: fileUrl))
return .success(.init(requestURL: httpUrlResponse.url, statusCode: httpUrlResponse.statusCode, rateLimit: httpUrlResponse.rateLimit, data: fileUrl))
}
}
18 changes: 18 additions & 0 deletions Sources/Extensions/HTTPURLResponseExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// HTTPURLResponseExtensions.swift
//
//
// Created by Mathias Emil Mortensen on 10/04/2023.
//

import Foundation

extension HTTPURLResponse {
var rateLimit: RateLimit? {
if let value = value(forHTTPHeaderField: "X-Rate-Limit") {
return RateLimit(value: value, requestURL: url)
} else {
return nil
}
}
}
1 change: 1 addition & 0 deletions Sources/OpenAPI/Generated/Entities/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public struct Device: Codable, Identifiable {
case ipod = "IPOD"
case appleTv = "APPLE_TV"
case mac = "MAC"
case appleSiliconMac = "APPLE_SILICON_MAC"
}

public enum Status: String, Codable, CaseIterable {
Expand Down
3 changes: 2 additions & 1 deletion Sources/OpenAPI/app_store_connect_api_2.3_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -88166,7 +88166,8 @@
"IPHONE",
"IPOD",
"APPLE_TV",
"MAC"
"MAC",
"APPLE_SILICON_MAC"
]
},
"status": {
Expand Down
33 changes: 33 additions & 0 deletions Sources/RateLimit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// RateLimit.swift
//
//
// Created by Mathias Emil Mortensen on 05/04/2023.
//

import Foundation

public struct RateLimit {
/// Rate limit entries
public let entries: [String: Int]

/// The url of the request the rate limit were returned for.
public let requestURL: URL?

init(value: String, requestURL: URL?) {
self.requestURL = requestURL

let items = value.split(separator: ";", omittingEmptySubsequences: true)
entries = items.reduce(into: [:]) { partialResult, item in
guard let colonIdx = item.firstIndex(of: ":") else {
return
}
let key = item[..<colonIdx]
let valueString = item[item.index(after: colonIdx)...]
guard let value = Int(valueString) else {
return
}
partialResult[String(key)] = value
}
}
}
4 changes: 3 additions & 1 deletion Sources/RequestExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ public struct Response<T: Codable>: Codable{
public let statusCode: Int
public let data: T?
public let errorResponse: ErrorResponse?
public let rateLimit: RateLimit?

public init(requestURL: URL?, statusCode: StatusCode, data: T?) {
public init(requestURL: URL?, statusCode: StatusCode, rateLimit: RateLimit?, data: T?) {
self.requestURL = requestURL
self.statusCode = statusCode
self.rateLimit = rateLimit
self.data = data

if let data = data as? Data {
Expand Down
10 changes: 5 additions & 5 deletions Tests/APIProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ final class APIProviderTests: XCTestCase {
// MARK: - Tests

func testRequestExecutionWithVoidResponse() {
let response = Response<Data>(requestURL: nil, statusCode: 200, data: nil)
let response = Response<Data>(requestURL: nil, statusCode: 200, rateLimit: nil, data: nil)
let mockRequestExecutor = MockRequestExecutor(expectedResponse: Result.success(response))
let apiProvider = APIProvider(configuration: configuration, requestExecutor: mockRequestExecutor)

Expand All @@ -75,7 +75,7 @@ final class APIProviderTests: XCTestCase {
)
])
let responseData = try JSONEncoder().encode(errorResponse)
let response = Response<Data>(requestURL: expectedURL, statusCode: 404, data: responseData)
let response = Response<Data>(requestURL: expectedURL, statusCode: 404, rateLimit: nil, data: responseData)
let mockRequestExecutor = MockRequestExecutor(expectedResponse: Result.success(response))
let apiProvider = APIProvider(configuration: configuration, requestExecutor: mockRequestExecutor)

Expand Down Expand Up @@ -103,7 +103,7 @@ final class APIProviderTests: XCTestCase {
}

func testDownloadRequestWithResultSuccess() {
let response = Response(requestURL: nil, statusCode: 200, data: URL(fileURLWithPath: "randompath"))
let response = Response(requestURL: nil, statusCode: 200, rateLimit: nil, data: URL(fileURLWithPath: "randompath"))
let mockRequestExecutor = MockRequestExecutor(expectedResponse: Result.success(response))

let apiProvider = APIProvider(configuration: configuration, requestExecutor: mockRequestExecutor)
Expand All @@ -120,7 +120,7 @@ final class APIProviderTests: XCTestCase {
}

func testDownloadRequestWithProblemOnFileCreation() {
let response = Response<URL>(requestURL: nil, statusCode: 200, data: nil)
let response = Response<URL>(requestURL: nil, statusCode: 200, rateLimit: nil, data: nil)
let mockRequestExecutor = MockRequestExecutor(expectedResponse: Result.success(response))

let apiProvider = APIProvider(configuration: configuration, requestExecutor: mockRequestExecutor)
Expand All @@ -142,7 +142,7 @@ final class APIProviderTests: XCTestCase {
}

func testDownloadRequestWithFailure() {
let response = Response<URL>(requestURL: nil, statusCode: 500, data: nil)
let response = Response<URL>(requestURL: nil, statusCode: 500, rateLimit: nil, data: nil)
let mockRequestExecutor = MockRequestExecutor(expectedResponse: Result.success(response))

let apiProvider = APIProvider(configuration: configuration, requestExecutor: mockRequestExecutor)
Expand Down
50 changes: 50 additions & 0 deletions Tests/RateLimitTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// RateLimitTests.swift
//
//
// Created by Mathias Emil Mortensen on 05/04/2023.
//

import XCTest
@testable import AppStoreConnect_Swift_SDK

final class RateLimitTests: XCTestCase {

func testValidValue() {
let rateLimit = RateLimit(value: "user-hour-lim:3600;user-hour-rem:3545;", requestURL: nil)
XCTAssertEqual(rateLimit.entries.count, 2)
XCTAssertEqual(rateLimit.entries["user-hour-lim"], 3600)
XCTAssertEqual(rateLimit.entries["user-hour-rem"], 3545)
}

func testInvalidValue() {
let rateLimit = RateLimit(value: "user-hour-rem3545", requestURL: nil)
XCTAssertEqual(rateLimit.entries.count, 0)
}

func testPartialInvalidValue() {
let rateLimit = RateLimit(value: "user-hour-rem3545;user-hour-rem:3545;", requestURL: nil)
XCTAssertEqual(rateLimit.entries.count, 1)
XCTAssertEqual(rateLimit.entries["user-hour-rem"], 3545)
}

func testMoreItems() {
let rateLimit = RateLimit(value: "user-hour-lim:3600;user-hour-rem:3598;user-minute-lim:150;user-minute-rem:149;", requestURL: nil)
XCTAssertEqual(rateLimit.entries.count, 4)
XCTAssertEqual(rateLimit.entries["user-hour-lim"], 3600)
XCTAssertEqual(rateLimit.entries["user-hour-rem"], 3598)
XCTAssertEqual(rateLimit.entries["user-minute-lim"], 150)
XCTAssertEqual(rateLimit.entries["user-minute-rem"], 149)
}

func testExtractingHeaderField() {
let httpUrlResponse = HTTPURLResponse(url: URL(string: "https://api.appstoreconnect.apple.com/")!, statusCode: 200, httpVersion: nil, headerFields: ["X-ratE-limiT": "user-hour-lim:3600;user-hour-rem:3545;"])
let rateLimit = httpUrlResponse?.rateLimit
XCTAssertNotNil(rateLimit)
if let rateLimit {
XCTAssertEqual(rateLimit.entries.count, 2)
XCTAssertEqual(rateLimit.entries["user-hour-lim"], 3600)
XCTAssertEqual(rateLimit.entries["user-hour-rem"], 3545)
}
}
}

0 comments on commit 4835615

Please sign in to comment.