Skip to content

Commit

Permalink
Package init
Browse files Browse the repository at this point in the history
  • Loading branch information
satish committed May 7, 2023
1 parent 4ca5c08 commit c5c7454
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 23 deletions.
40 changes: 28 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -35,7 +43,7 @@ extension MyApiTarget: NetworkTargetType {
var path: String {
"/todos/1"
}
var method: Networking.HTTPMethod {
var method: NetworkKing.HTTPMethod {
.get
}
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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<UserService>.init(requestInterceptor: interceptor)
```

Expand All @@ -91,9 +109,7 @@ struct MyCustomDataResponseValidator: DataResponseValidator {
return .success(Void())
}
}

// With builtin default validator
let myProvider1 = NetworkProvider<UserService>.init(whiteHatResponseValidator: .default)

// With custom validator
let myProvider2 = NetworkProvider<UserService>.init(dataResponseValidator: MyCustomDataResponseValidator())
```
Expand Down Expand Up @@ -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
8 changes: 5 additions & 3 deletions Sources/NetworkKing/Interceptor/RequestInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
8 changes: 5 additions & 3 deletions Sources/NetworkKing/NetworkProvider/NetworkProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,25 @@ 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()
// Return data & response
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
Expand Down
23 changes: 23 additions & 0 deletions Tests/NetworkKingTests/Interceptor/RequestInterceptorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
6 changes: 3 additions & 3 deletions Tests/NetworkKingTests/Mocks/MockLoginEndpoints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extension MockLoginEndpoints: NetworkTargetType {
}
}

var method: Networking.HTTPMethod {
var method: HTTPMethod {
switch self {
case .login:
return .post
Expand All @@ -49,8 +49,8 @@ extension MockLoginEndpoints: NetworkTargetType {
}

struct MockDataResponseValidator: DataResponseValidator {
let block: () -> Result<Void, Networking.NetworkError>
func validate(_: Data, response _: URLResponse) -> Result<Void, Networking.NetworkError> {
let block: () -> Result<Void, NetworkError>
func validate(_: Data, response _: URLResponse) -> Result<Void, NetworkError> {
block()
}
}
Expand Down
19 changes: 19 additions & 0 deletions Tests/NetworkKingTests/Mocks/MockRequestRetrier.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
2 changes: 1 addition & 1 deletion Tests/NetworkKingTests/Mocks/MockTargetType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ enum MockTargetType: NetworkTargetType {
""
}

var method: Networking.HTTPMethod {
var method: HTTPMethod {
.get
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ final class NetworkTargetTypeDefaultImplTest: XCTestCase {
""
}

var method: Networking.HTTPMethod {
var method: HTTPMethod {
.get
}
}
Expand Down

0 comments on commit c5c7454

Please sign in to comment.