From c5c74546596707435ccf9bcb05e945f9cf0c29b5 Mon Sep 17 00:00:00 2001 From: satish Date: Sat, 29 Apr 2023 23:57:17 +0100 Subject: [PATCH] Package init --- README.md | 40 +++++++++++++------ .../Interceptor/RequestInterceptor.swift | 8 ++-- .../NetworkProvider/NetworkProvider.swift | 8 ++-- .../Interceptor/RequestInterceptorTests.swift | 23 +++++++++++ .../Mocks/MockLoginEndpoints.swift | 6 +-- .../Mocks/MockRequestRetrier.swift | 19 +++++++++ .../Mocks/MockTargetType.swift | 2 +- .../NetworkTargetType+DefaultImplTests.swift | 2 +- 8 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 Tests/NetworkKingTests/Mocks/MockRequestRetrier.swift diff --git a/README.md b/README.md index 5c5745f..7bc5b82 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,35 @@ -# NetworkKing +# NetworkKing 👑 ![Swift](https://img.shields.io/badge/Swift-5.8-orange?style=flat-square) ![Swift Package Manager](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square) ![Platforms](https://img.shields.io/badge/Platforms-iOS-yellowgreen?style=flat-square) -Networking is a network abstraction layer written in Swift. It does not implement its own HTTP networking functionality. Instead it builds on top of URLSession. +NetworkKing is a network abstraction layer written in Swift. It does not implement its own HTTP networking functionality. Instead it builds on top of URLSession. -##Features +## Features ✅ Compile-time checking for correct API endpoint accesses. + ✅ Lets you define a clear usage of different endpoints with associated enum values. + ✅ Swift's concurrency support + ✅ Inspection and mutation support for each request before being start + +✅ Request retrying + ✅ Response validation + ✅ Errors handling + ✅ Comprehensive Unit test coverage -##Usage +## Usage __Routing__: So how do you use this module? Well it's really simple. First set up `enum` with all your api targets. You can include information as part of your enum. For example first create a new enum MyApiTarget: ```Swift -import Networking +import NetworkKing enum MyApiTarget { case getMyData @@ -35,7 +43,7 @@ extension MyApiTarget: NetworkTargetType { var path: String { "/todos/1" } - var method: Networking.HTTPMethod { + var method: NetworkKing.HTTPMethod { .get } } @@ -64,7 +72,7 @@ Task { ``` -__RequestInterceptor__: Networking module can mutate or inspect each url request before its being made. What you needs to do is create a type that confirm to the `RequestAdapter` and pass an instance of that type into NetworkProvider's init like below: +__RequestInterceptor__: NetworkKing module can mutate or inspect each url request before its being made. What you needs to do is create a type that confirm to the `RequestAdapter` and pass an instance of that type into NetworkProvider's init like below: ```Swift struct NetworkEventMonitor: RequestAdapter { @@ -74,8 +82,18 @@ struct NetworkEventMonitor: RequestAdapter { } } +// Request retrying +struct MyRequestRetrier: RequestRetrier { + public func retry(_ request: URLRequest,for target: NetworkTargetType,dueTo error: Error) async throws -> RetryResult { + if error == "254" { + // e.g. Perform refresh token and then return + return .retry // This will trigger rebuild of URLRequest + } + return .doNotRetry + } +} -let interceptor = RequestInterceptor(adapters: [NetworkEventMonitor(), ...]) +let interceptor = RequestInterceptor(adapters: [NetworkEventMonitor(), ...], retriers: [MyRequestRetrier(), ...]) let myProvider = NetworkProvider.init(requestInterceptor: interceptor) ``` @@ -91,9 +109,7 @@ struct MyCustomDataResponseValidator: DataResponseValidator { return .success(Void()) } } - -// With builtin default validator -let myProvider1 = NetworkProvider.init(whiteHatResponseValidator: .default) + // With custom validator let myProvider2 = NetworkProvider.init(dataResponseValidator: MyCustomDataResponseValidator()) ``` @@ -122,7 +138,7 @@ Task { } ``` -##References +## References Alamofire routing: https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#routing-requests Moya: https://github.com/Moya/Moya diff --git a/Sources/NetworkKing/Interceptor/RequestInterceptor.swift b/Sources/NetworkKing/Interceptor/RequestInterceptor.swift index 2b29c2f..3e7900e 100644 --- a/Sources/NetworkKing/Interceptor/RequestInterceptor.swift +++ b/Sources/NetworkKing/Interceptor/RequestInterceptor.swift @@ -28,12 +28,14 @@ public struct RequestInterceptor: RequestAdapter, RequestRetrier { } public func retry(_ request: URLRequest, for target: NetworkTargetType, dueTo error: Error) async throws -> RetryResult { + var finalResult = RetryResult.doNotRetry + for retrier in retriers { - let result = try await retrier.retry(request, for: target, dueTo: error) - if result == .doNotRetry { + finalResult = try await retrier.retry(request, for: target, dueTo: error) + if finalResult == .doNotRetry { break } } - return .doNotRetry + return finalResult } } diff --git a/Sources/NetworkKing/NetworkProvider/NetworkProvider.swift b/Sources/NetworkKing/NetworkProvider/NetworkProvider.swift index 654e412..d8f8256 100644 --- a/Sources/NetworkKing/NetworkProvider/NetworkProvider.swift +++ b/Sources/NetworkKing/NetworkProvider/NetworkProvider.swift @@ -79,10 +79,10 @@ extension NetworkProvider { ) async throws -> (Data, URLResponse, URLRequest) { // 1. Build url request var request = try target.toURLRequest() - // 2. Invoke request interceptor. e.g set access-token or monitor request etc. - request = try await requestInterceptor.adapt(request, for: target) - // 3. Perform network data request do { + // 2. Invoke request interceptor. e.g set access-token or monitor request etc. + request = try await requestInterceptor.adapt(request, for: target) + // 3. Perform network data request let (data, urlResponse) = try await session.data(for: request) // 4. Perform response validation if available. try dataResponseValidator?.validate(data, response: urlResponse).get() @@ -90,12 +90,14 @@ extension NetworkProvider { return (data, urlResponse, request) } catch { if isRetrying { + /// End recursion throw error } /// Check for retry let retryResult = try await requestInterceptor.retry(request, for: target, dueTo: error) switch retryResult { case .retry: + /// Start recursion return try await performDataRequest(target: target, isRetrying: true) case .doNotRetry: throw error diff --git a/Tests/NetworkKingTests/Interceptor/RequestInterceptorTests.swift b/Tests/NetworkKingTests/Interceptor/RequestInterceptorTests.swift index cb56b4f..f897499 100644 --- a/Tests/NetworkKingTests/Interceptor/RequestInterceptorTests.swift +++ b/Tests/NetworkKingTests/Interceptor/RequestInterceptorTests.swift @@ -29,4 +29,27 @@ final class RequestInterceptorTests: XCTestCase { let newHeaders = newRequest.allHTTPHeaderFields ?? [:] XCTAssertTrue(newHeaders[header.key] == header.value) } + + func testRetry() async throws { + let originalRequest = URLRequest(url: URL(string: "http://test.com/abc")!) + let originalTarget = MockTargetType.example1 + let originalError = NSError(domain: "test", code: 11) + var result = RetryResult.doNotRetry + + let retrier = MockRequestRetrier { request, target, error in + XCTAssertEqual(request, originalRequest) + XCTAssertEqual(target as! MockTargetType, originalTarget) + XCTAssertEqual(error as NSError, originalError) + return result + } + let interceptor = RequestInterceptor(retriers: [retrier]) + + let finalResult1 = try await interceptor.retry(originalRequest, for: originalTarget, dueTo: originalError) + XCTAssertEqual(finalResult1, .doNotRetry) + + result = .retry + let finalResult2 = try await interceptor.retry(originalRequest, for: originalTarget, dueTo: originalError) + + XCTAssertEqual(finalResult2, .retry) + } } diff --git a/Tests/NetworkKingTests/Mocks/MockLoginEndpoints.swift b/Tests/NetworkKingTests/Mocks/MockLoginEndpoints.swift index 5ad61e4..983ad37 100644 --- a/Tests/NetworkKingTests/Mocks/MockLoginEndpoints.swift +++ b/Tests/NetworkKingTests/Mocks/MockLoginEndpoints.swift @@ -29,7 +29,7 @@ extension MockLoginEndpoints: NetworkTargetType { } } - var method: Networking.HTTPMethod { + var method: HTTPMethod { switch self { case .login: return .post @@ -49,8 +49,8 @@ extension MockLoginEndpoints: NetworkTargetType { } struct MockDataResponseValidator: DataResponseValidator { - let block: () -> Result - func validate(_: Data, response _: URLResponse) -> Result { + let block: () -> Result + func validate(_: Data, response _: URLResponse) -> Result { block() } } diff --git a/Tests/NetworkKingTests/Mocks/MockRequestRetrier.swift b/Tests/NetworkKingTests/Mocks/MockRequestRetrier.swift new file mode 100644 index 0000000..02275d4 --- /dev/null +++ b/Tests/NetworkKingTests/Mocks/MockRequestRetrier.swift @@ -0,0 +1,19 @@ +// +// MockRequestRetrier.swift +// +// +// Created by Satish Vekariya on 29/04/2023. +// + +import Foundation +import NetworkKing + +struct MockRequestRetrier: RequestRetrier { + typealias Block = (_ request: URLRequest, _ target: NetworkTargetType, _ error: Error) async throws -> RetryResult + + let block: Block + + func retry(_ request: URLRequest, for target: NetworkTargetType, dueTo error: Error) async throws -> RetryResult { + try await block(request, target, error) + } +} diff --git a/Tests/NetworkKingTests/Mocks/MockTargetType.swift b/Tests/NetworkKingTests/Mocks/MockTargetType.swift index d0069c1..afd2044 100644 --- a/Tests/NetworkKingTests/Mocks/MockTargetType.swift +++ b/Tests/NetworkKingTests/Mocks/MockTargetType.swift @@ -19,7 +19,7 @@ enum MockTargetType: NetworkTargetType { "" } - var method: Networking.HTTPMethod { + var method: HTTPMethod { .get } } diff --git a/Tests/NetworkKingTests/TargetType/NetworkTargetType+DefaultImplTests.swift b/Tests/NetworkKingTests/TargetType/NetworkTargetType+DefaultImplTests.swift index 9e4037e..1fc4069 100644 --- a/Tests/NetworkKingTests/TargetType/NetworkTargetType+DefaultImplTests.swift +++ b/Tests/NetworkKingTests/TargetType/NetworkTargetType+DefaultImplTests.swift @@ -38,7 +38,7 @@ final class NetworkTargetTypeDefaultImplTest: XCTestCase { "" } - var method: Networking.HTTPMethod { + var method: HTTPMethod { .get } }