Skip to content

Commit

Permalink
NetworkKit SwiftPackage created
Browse files Browse the repository at this point in the history
  • Loading branch information
rushisangani committed Jan 17, 2024
1 parent bfc5b59 commit 44c4bdf
Show file tree
Hide file tree
Showing 18 changed files with 1,112 additions and 15 deletions.
78 changes: 78 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/NetworkKit.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "NetworkKit"
BuildableName = "NetworkKit"
BlueprintName = "NetworkKit"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "NetworkKitTests"
BuildableName = "NetworkKitTests"
BlueprintName = "NetworkKitTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "NetworkKit"
BuildableName = "NetworkKit"
BlueprintName = "NetworkKit"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
10 changes: 9 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import PackageDescription

let package = Package(
name: "NetworkKit",
platforms: [
.iOS(.v15),
.macOS(.v12),
.watchOS(.v8)
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
Expand All @@ -18,6 +23,9 @@ let package = Package(
name: "NetworkKit"),
.testTarget(
name: "NetworkKitTests",
dependencies: ["NetworkKit"]),
dependencies: ["NetworkKit"],
resources: [
.process("Resources"),
]),
]
)
73 changes: 73 additions & 0 deletions Sources/NetworkKit/APIRequestHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// APIRequestHandler.swift
// MVVMDemo
//
// Created by Rushi Sangani on 30/11/2023.
//

import Foundation
import Combine

// MARK: - RequestHandler

public protocol RequestHandler {
func sendRequest(_ request: Request) async throws -> Data
func sendRequest(_ request: Request) -> AnyPublisher<Data, RequestError>
}

// MARK: - APIRequestHandler

public struct APIRequestHandler: RequestHandler {
let session: URLSession

init(session: URLSession = .shared) {
self.session = session
}

public func sendRequest(_ request: Request) async throws -> Data {
let urlRequest = try request.createURLRequest()
let (data, response) = try await session.data(for: urlRequest)

guard let response = response as? HTTPURLResponse else {
throw RequestError.failed(description: "Request Failed.")
}
switch response.statusCode {
case 200...299:
return data
case 401:
throw RequestError.unauthorized
default:
throw RequestError.unexpectedStatusCode(description: "Status Code: \(response.statusCode)")
}
}

public func sendRequest(_ request: Request) -> AnyPublisher<Data, RequestError> {
guard let urlRequest = try? request.createURLRequest() else {
return Fail(error: RequestError.invalidURL)
.eraseToAnyPublisher()
}
return session
.dataTaskPublisher(for: urlRequest)
.subscribe(on: DispatchQueue.global())
.tryMap { (data, response) -> Data in
guard let httpResponse = response as? HTTPURLResponse else {
throw RequestError.failed(description: "Request Failed.")
}
switch httpResponse.statusCode {
case 200...299:
return data
case 401:
throw RequestError.unauthorized
default:
throw RequestError.unexpectedStatusCode(description: "Status Code: \(httpResponse.statusCode)")
}
}
.mapError { error -> RequestError in
if let requestError = error as? RequestError {
return requestError
}
return RequestError.unknown
}
.eraseToAnyPublisher()
}
}
33 changes: 33 additions & 0 deletions Sources/NetworkKit/APIResponseHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// APIResponseHandler.swift
// MVVMDemo
//
// Created by Rushi Sangani on 30/11/2023.
//

import Foundation
import Combine

// MARK: - ResponseHandler

protocol ResponseHandler {
func getResponse<T: Decodable>(from response: Data) throws -> T
}

// MARK: - APIResponseHandler

struct APIResponseHandler: ResponseHandler {
let decoder: JSONDecoder

init(decoder: JSONDecoder = JSONDecoder()) {
self.decoder = decoder
}

func getResponse<T: Decodable>(from data: Data) throws -> T {
do {
return try decoder.decode(T.self, from: data)
} catch {
throw RequestError.decode(description: error.localizedDescription)
}
}
}
2 changes: 0 additions & 2 deletions Sources/NetworkKit/NetworkKit.swift

This file was deleted.

47 changes: 47 additions & 0 deletions Sources/NetworkKit/NetworkManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// File.swift
//
//
// Created by Rushi Sangani on 17/01/2024.
//

import Foundation
import Combine

// MARK: - NetworkHandler

public protocol NetworkHandler {
func fetch<T: Decodable>(request: Request) async throws -> T
func fetch<T: Decodable>(request: Request) -> AnyPublisher<T, RequestError>
}

// MARK: - NetworkManager

public final class NetworkManager: NetworkHandler {
var requestHandler: RequestHandler
var responseHandler: ResponseHandler

init(requestHandler: RequestHandler = APIRequestHandler(),
responseHandler: ResponseHandler = APIResponseHandler()
) {
self.requestHandler = requestHandler
self.responseHandler = responseHandler
}

public func fetch<T>(request: Request) async throws -> T where T : Decodable {
let data = try await requestHandler.sendRequest(request)
return try responseHandler.getResponse(from: data)
}

public func fetch<T>(request: Request) -> AnyPublisher<T, RequestError> where T : Decodable {
return requestHandler
.sendRequest(request)
.tryMap { data -> T in
return try self.responseHandler.getResponse(from: data)
}
.mapError { error -> RequestError in
return RequestError.decode(description: error.localizedDescription)
}
.eraseToAnyPublisher()
}
}
62 changes: 62 additions & 0 deletions Sources/NetworkKit/Request.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// Request.swift
// MVVMDemo
//
// Created by Rushi Sangani on 30/11/2023.
//

import Foundation

// MARK: - Request

public protocol Request {
var host: String { get }
var scheme: String { get }
var path: String { get }
var requestType: RequestType { get }
var headers: [String: String] { get }
var params: [String: Any] { get }
}

extension Request {
var scheme: String {
"https"
}

var headers: [String: String] {
[:]
}

var params: [String: Any] {
[:]
}

var requestType: RequestType {
.get
}

func createURLRequest() throws -> URLRequest {
var components = URLComponents()
components.scheme = scheme
components.host = host
components.path = path

guard let url = components.url else {
throw RequestError.invalidURL
}

var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = requestType.rawValue

if !headers.isEmpty {
urlRequest.allHTTPHeaderFields = headers
}
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

if !params.isEmpty {
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: params)
}

return urlRequest
}
}
37 changes: 37 additions & 0 deletions Sources/NetworkKit/RequestError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// RequestError.swift
// MVVMDemo
//
// Created by Rushi Sangani on 30/11/2023.
//

import Foundation

public enum RequestError: Error {
case invalidURL
case failed(description: String)
case noData
case decode(description: String)
case unauthorized
case unexpectedStatusCode(description: String)
case unknown

var customDescription: String {
switch self {
case .invalidURL:
return "Invalid URL"
case .noData:
return "No Data Found"
case .failed(let description):
return "Request Failed: \(description)"
case .decode(let description):
return "Decoding Error: \(description)"
case .unauthorized:
return "Session Expired"
case .unexpectedStatusCode(let description):
return "Unknown Error: \(description)"
default:
return "Unknown Error"
}
}
}
16 changes: 16 additions & 0 deletions Sources/NetworkKit/RequestType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// File.swift
//
//
// Created by Rushi Sangani on 17/01/2024.
//

import Foundation

public enum RequestType: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
}
Loading

0 comments on commit 44c4bdf

Please sign in to comment.