Skip to content

Commit

Permalink
0.45.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dankinsoid committed Mar 23, 2024
1 parent 901927b commit 2b67ea4
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 74 deletions.
6 changes: 3 additions & 3 deletions Example/Sources/PetStore/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public enum PetStatus: String, Codable {

public struct Tokens: Codable {

public var accessToken: String
public var refreshToken: String
public var expiryDate: Date
public var accessToken: String
public var refreshToken: String
public var expiryDate: Date
}
22 changes: 11 additions & 11 deletions Example/Sources/PetStore/PetStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ public struct PetStore {
.fileIDLine(fileID: fileID, line: line)
.bodyDecoder(PetStoreDecoder())
.tokenRefresher { refreshToken, client, _ in
guard let refreshToken else {
throw Errors.noRefreshToken
}
let tokens: Tokens = try await client("auth", "token")
.body(["refresh_token": refreshToken])
.post()
return (tokens.accessToken, tokens.refreshToken, tokens.expiryDate)
guard let refreshToken else {
throw Errors.noRefreshToken
}
let tokens: Tokens = try await client("auth", "token")
.body(["refresh_token": refreshToken])
.post()
return (tokens.accessToken, tokens.refreshToken, tokens.expiryDate)
}
}

public enum Errors: Error {
case noRefreshToken
}
public enum Errors: Error {

case noRefreshToken
}
}

// MARK: - "pet" path
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ import PackageDescription
let package = Package(
name: "SomeProject",
dependencies: [
.package(url: "https://github.com/dankinsoid/swift-api-client.git", from: "0.44.1")
.package(url: "https://github.com/dankinsoid/swift-api-client.git", from: "0.45.0")
],
targets: [
.target(
Expand Down
25 changes: 0 additions & 25 deletions Sources/SwiftAPIClient/Clients/NetworkClientCaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,6 @@ public extension APIClient {
do {
return try withRequest { request, configs in
let fileIDLine = configs.fileIDLine ?? FileIDLine(fileID: fileID, line: line)
var request = request
try configs.beforeCall(&request, configs)

if !configs.loggingComponents.isEmpty {
let message = configs.loggingComponents.requestMessage(for: request, uuid: uuid, fileIDLine: fileIDLine)
Expand Down Expand Up @@ -187,27 +185,4 @@ public extension APIClient {
throw error
}
}

/// Sets a closure to be executed before making a network call.
///
/// - Parameters:
/// - closure: The closure to be executed before making a network call. It takes in an `inout URLRequest` and `APIClient.Configs` as parameters and can modify the request.
/// - Returns: The `APIClient` instance.
func beforeCall(_ closure: @escaping (inout URLRequest, APIClient.Configs) throws -> Void) -> APIClient {
configs {
let beforeCall = $0.beforeCall
$0.beforeCall = { request, configs in
try beforeCall(&request, configs)
try closure(&request, configs)
}
}
}
}

public extension APIClient.Configs {

var beforeCall: (inout URLRequest, APIClient.Configs) throws -> Void {
get { self[\.beforeCall] ?? { _, _ in } }
set { self[\.beforeCall] = newValue }
}
}
63 changes: 41 additions & 22 deletions Sources/SwiftAPIClient/Modifiers/RequestCompression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,51 @@ public extension APIClient {
duplicateHeaderBehavior: DuplicateHeaderBehavior = .skip,
shouldCompressBodyData: @escaping (_ bodyData: Data) -> Bool = { _ in true }
) -> APIClient {
beforeCall { urlRequest, _ in
// No need to compress unless we have body data. No support for compressing streams.
guard let bodyData = urlRequest.httpBody else {
return
}
httpClientMiddleware(
CompressionMiddleware(
duplicateHeaderBehavior: duplicateHeaderBehavior,
shouldCompressBodyData: shouldCompressBodyData
)
)
}
}

guard shouldCompressBodyData(bodyData) else {
return
}
private struct CompressionMiddleware: HTTPClientMiddleware {

let contentEncodingKey = HTTPHeader.Key.contentEncoding.rawValue
if urlRequest.value(forHTTPHeaderField: contentEncodingKey) != nil {
switch duplicateHeaderBehavior {
case .error:
throw Errors.duplicateHeader(.contentEncoding)
case .replace:
// Header will be replaced once the body data is compressed.
break
case .skip:
return
}
}
let duplicateHeaderBehavior: DuplicateHeaderBehavior
let shouldCompressBodyData: (_ bodyData: Data) -> Bool

urlRequest.httpBody = try deflate(bodyData)
urlRequest.setValue("deflate", forHTTPHeaderField: contentEncodingKey)
func execute<T>(
request: URLRequest,
configs: APIClient.Configs,
next: (URLRequest, APIClient.Configs) async throws -> (T, HTTPURLResponse)
) async throws -> (T, HTTPURLResponse) {
// No need to compress unless we have body data. No support for compressing streams.
guard let bodyData = request.httpBody else {
return try await next(request, configs)
}

guard shouldCompressBodyData(bodyData) else {
return try await next(request, configs)
}

let contentEncodingKey = HTTPHeader.Key.contentEncoding.rawValue
if request.value(forHTTPHeaderField: contentEncodingKey) != nil {
switch duplicateHeaderBehavior {
case .error:
throw Errors.duplicateHeader(.contentEncoding)
case .replace:
// Header will be replaced once the body data is compressed.
break
case .skip:
return try await next(request, configs)
}
}

var urlRequest = request
urlRequest.httpBody = try deflate(bodyData)
urlRequest.setValue("deflate", forHTTPHeaderField: contentEncodingKey)
return try await next(urlRequest, configs)
}
}

Expand Down
18 changes: 15 additions & 3 deletions Sources/SwiftAPIClient/Modifiers/RequestValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,20 @@ public extension APIClient {
/// - Parameter validator: The `RequestValidator` to be used for validating `URLRequest` instances.
/// - Returns: An instance of `APIClient` configured with the specified request validator.
func requestValidator(_ validator: RequestValidator) -> APIClient {
beforeCall {
try validator.validate($0, $1)
}
httpClientMiddleware(RequestValidatorMiddleware(validator: validator))
}
}

private struct RequestValidatorMiddleware: HTTPClientMiddleware {

let validator: RequestValidator

func execute<T>(
request: URLRequest,
configs: APIClient.Configs,
next: (URLRequest, APIClient.Configs) async throws -> (T, HTTPURLResponse)
) async throws -> (T, HTTPURLResponse) {
try validator.validate(request, configs)
return try await next(request, configs)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public extension APIClient {
/// - request: The closure to use for requesting a new token and refresh token first time. Set to `nil` if you want to request and cache tokens manually.
/// - refresh: The closure to use for refreshing a new token with refresh token.
/// - auth: The closure that creates an `AuthModifier` for the new token. Default to `.bearer(token:)`.
///
/// - Warning: Don't use this modifier with `.auth(_ modifier:)` as it will be override it.
///
/// - Warning: Don't use this modifier with `.auth(_ modifier:)` as it will be override it.
func tokenRefresher(
cacheService: SecureCacheService = valueFor(live: .keychain, test: .mock),
expiredStatusCodes: Set<HTTPStatusCode> = [.unauthorized],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ import XCTest
final class APIClientCompressionTests: XCTestCase {

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
func testThatRequestCompressorProperlyCalculatesAdler32() throws {
func testThatRequestCompressorProperlyCalculatesAdler32() async throws {
let client = APIClient(baseURL: URL(string: "https://example.com")!).compressRequest()
let request = try client.body(Data("Wikipedia".utf8)).request()
let body: Data = try await client
.body(Data([0]))
.httpTest { request, _ in
request.httpBody!
}
// From https://en.wikipedia.org/wiki/Adler-32
XCTAssertEqual(request.httpBody, Data([87, 105, 107, 105, 112, 101, 100, 105, 97]))
XCTAssertEqual(body, Data([0x78, 0x5E, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01]))
}
}
#endif
11 changes: 7 additions & 4 deletions Tests/SwiftAPIClientTests/TestUtils/TestHTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,31 @@ private struct Unimplemented: Error {}

extension APIClient {

@discardableResult
func httpTest(
test: @escaping (URLRequest, APIClient.Configs) throws -> Void = { _, _ in }
) async throws {
) async throws -> Data {
try await httpTest {
try test($0, $1)
return Data()
}
}

@discardableResult
func httpTest(
test: @escaping (URLRequest, APIClient.Configs) throws -> (Data, HTTPURLResponse)
) async throws {
) async throws -> Data {
try await configs(\.testHTTPClient) {
try test($0, $1)
}
.httpClient(.test())
.call(.http, as: .void)
.call(.http, as: .identity)
}

@discardableResult
func httpTest(
test: @escaping (URLRequest, APIClient.Configs) throws -> Data
) async throws {
) async throws -> Data {
try await httpTest {
let data = try test($0, $1)
guard let response = HTTPURLResponse(
Expand Down

0 comments on commit 2b67ea4

Please sign in to comment.