From 9f8430f472a4ef4d5957e4bbe51f0ea9b75c6aef Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Wed, 20 Nov 2019 11:58:44 +0530 Subject: [PATCH 01/25] Declarative Network Kit with structure like iOS 13 Publisher - Subscriber. Supports, Map Keypath, Map, Try Map, Catch, Try Catch, Debounce, Assign, Completion, Validation, Replace Error, Decode --- .../Connection/Connection Representable.swift | 26 +++ .../Create Request/Create Request.swift | 63 +++++++ .../Debug Loging/Debug Logging.swift | 42 +++++ .../NetworkKit/Errors/Business Error.swift | 29 +++ Sources/NetworkKit/Errors/HTTP Error.swift | 131 ++++++++++++++ Sources/NetworkKit/Errors/NKError.swift | 135 ++++++++++++++ .../Extensions/Array+Extension.swift | 28 +++ .../Extensions/Data+extension.swift | 16 ++ .../Extensions/Dictionary+Extension.swift | 48 +++++ .../Extensions/JSONDecoder+Extension.swift | 13 ++ .../Extensions/Never+Extension.swift | 14 ++ .../NotificationName+Extension.swift | 14 ++ .../Extensions/Optional+Extension.swift | 23 +++ .../Extensions/Result+Extension.swift | 22 +++ .../Extensions/String+Extension.swift | 16 ++ .../UIImage+NSImage+Extension.swift | 42 +++++ ...ImageView+WKInterfaceImage+Extension.swift | 47 +++++ .../NetworkKit/Extensions/URL+Extension.swift | 19 ++ .../Extensions/URLRequest+Extension.swift | 27 +++ .../Kit/Network Queue/Network Queue.swift | 34 ++++ .../Kit/Network Result/Network Result.swift | 25 +++ Sources/NetworkKit/Kit/NetworkKit.swift | 47 +++++ .../Kit/Publishers/Assign/Assign.swift | 45 +++++ .../Kit/Publishers/Catch/Catch.swift | 96 ++++++++++ .../Kit/Publishers/Catch/Try Catch.swift | 104 +++++++++++ .../Publishers/Completion/Completion.swift | 42 +++++ .../Kit/Publishers/Debounce/Debounce.swift | 48 +++++ .../Kit/Publishers/Decode/Decode.swift | 75 ++++++++ .../Kit/Publishers/Map/Map Keypath 2.swift | 63 +++++++ .../Kit/Publishers/Map/Map Keypath.swift | 53 ++++++ .../NetworkKit/Kit/Publishers/Map/Map.swift | 130 ++++++++++++++ .../Kit/Publishers/Map/Try Map.swift | 64 +++++++ .../Kit/Publishers/Network Publishers.swift | 13 ++ .../Replace Error/Replace Error.swift | 56 ++++++ .../Publishers/Validation/Validation.swift | 159 +++++++++++++++++ Sources/NetworkKit/Models/API Analytics.swift | 17 ++ Sources/NetworkKit/Models/Error Model.swift | 28 +++ Sources/NetworkKit/NetworkKit.swift | 3 - .../Network Configuration+Notification.swift | 28 +++ .../Configuration/Network Configuration.swift | 167 ++++++++++++++++++ .../Image Session Manager.swift | 131 ++++++++++++++ .../ImageSession+ImageType.swift | 30 ++++ .../Networking/Session Manager.swift | 29 +++ .../Operations/Asynchronous Operation.swift | 54 ++++++ .../Operations/Debouce Operation.swift | 26 +++ .../Operations/Fetch Operation.swift | 47 +++++ .../Protocols/Cancellable/Cancellable.swift | 13 ++ .../Protocols/ImageDownloadDelegate.swift | 113 ++++++++++++ .../Protocols/Network Decoder.swift | 16 ++ .../Publisher/Network Publisher.swift | 24 +++ .../Publisher/Publisher+Methods.swift | 164 +++++++++++++++++ .../Protocols/Publisher/Publisher+Queue.swift | 31 ++++ .../NetworkKit/Request/Network Request.swift | 129 ++++++++++++++ .../Resources/HTTP Body Encoding.swift | 164 +++++++++++++++++ .../NetworkKit/Resources/HTTP Method.swift | 21 +++ Sources/NetworkKit/Resources/Scheme.swift | 14 ++ .../NetworkKit/Server/API Representable.swift | 19 ++ Sources/NetworkKit/Server/Environment.swift | 31 ++++ .../Server/Host Representable.swift | 24 +++ Tests/NetworkKitTests/NetworkKitTests.swift | 137 +++++++++++++- 60 files changed, 3260 insertions(+), 9 deletions(-) create mode 100644 Sources/NetworkKit/Connection/Connection Representable.swift create mode 100644 Sources/NetworkKit/Create Request/Create Request.swift create mode 100644 Sources/NetworkKit/Debug Loging/Debug Logging.swift create mode 100644 Sources/NetworkKit/Errors/Business Error.swift create mode 100644 Sources/NetworkKit/Errors/HTTP Error.swift create mode 100644 Sources/NetworkKit/Errors/NKError.swift create mode 100644 Sources/NetworkKit/Extensions/Array+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/Data+extension.swift create mode 100644 Sources/NetworkKit/Extensions/Dictionary+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/Never+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/NotificationName+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/Optional+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/Result+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/String+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/UIImage+NSImage+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/UIImageView+NSImageView+WKInterfaceImage+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/URL+Extension.swift create mode 100644 Sources/NetworkKit/Extensions/URLRequest+Extension.swift create mode 100644 Sources/NetworkKit/Kit/Network Queue/Network Queue.swift create mode 100644 Sources/NetworkKit/Kit/Network Result/Network Result.swift create mode 100644 Sources/NetworkKit/Kit/NetworkKit.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Map.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Network Publishers.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift create mode 100644 Sources/NetworkKit/Models/API Analytics.swift create mode 100644 Sources/NetworkKit/Models/Error Model.swift delete mode 100644 Sources/NetworkKit/NetworkKit.swift create mode 100644 Sources/NetworkKit/Networking/Configuration/Network Configuration+Notification.swift create mode 100644 Sources/NetworkKit/Networking/Configuration/Network Configuration.swift create mode 100644 Sources/NetworkKit/Networking/Image Session Manager/Image Session Manager.swift create mode 100644 Sources/NetworkKit/Networking/Image Session Manager/ImageSession+ImageType.swift create mode 100644 Sources/NetworkKit/Networking/Session Manager.swift create mode 100644 Sources/NetworkKit/Operations/Asynchronous Operation.swift create mode 100644 Sources/NetworkKit/Operations/Debouce Operation.swift create mode 100644 Sources/NetworkKit/Operations/Fetch Operation.swift create mode 100644 Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift create mode 100644 Sources/NetworkKit/Protocols/ImageDownloadDelegate.swift create mode 100644 Sources/NetworkKit/Protocols/Network Decoder.swift create mode 100644 Sources/NetworkKit/Protocols/Publisher/Network Publisher.swift create mode 100644 Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift create mode 100644 Sources/NetworkKit/Protocols/Publisher/Publisher+Queue.swift create mode 100644 Sources/NetworkKit/Request/Network Request.swift create mode 100644 Sources/NetworkKit/Resources/HTTP Body Encoding.swift create mode 100644 Sources/NetworkKit/Resources/HTTP Method.swift create mode 100644 Sources/NetworkKit/Resources/Scheme.swift create mode 100644 Sources/NetworkKit/Server/API Representable.swift create mode 100644 Sources/NetworkKit/Server/Environment.swift create mode 100644 Sources/NetworkKit/Server/Host Representable.swift diff --git a/Sources/NetworkKit/Connection/Connection Representable.swift b/Sources/NetworkKit/Connection/Connection Representable.swift new file mode 100644 index 0000000..99f7b63 --- /dev/null +++ b/Sources/NetworkKit/Connection/Connection Representable.swift @@ -0,0 +1,26 @@ +// +// ConnectionRepresentable.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public protocol ConnectionRepresentable { + var path: String { get } + var name: String? { get } + var method: HTTPMethod { get } + var httpHeaders: HTTPHeaderParameters { get } + var scheme: Scheme { get } + var host: HostRepresentable { get } + var defaultQuery: URLQuery? { get } + var apiVersion: APIRepresentable? { get } +} + +public extension ConnectionRepresentable { + var scheme: Scheme { .https } + + var apiVersion: APIRepresentable? { host.defaultAPIVersion } +} diff --git a/Sources/NetworkKit/Create Request/Create Request.swift b/Sources/NetworkKit/Create Request/Create Request.swift new file mode 100644 index 0000000..fc1c5c9 --- /dev/null +++ b/Sources/NetworkKit/Create Request/Create Request.swift @@ -0,0 +1,63 @@ +// +// CreateRequest.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public typealias URLQuery = [String: String?] +public typealias HTTPHeaderParameters = [String: String] + +final class CreateRequest { + private(set) var request: URLRequest + + init?(with connection: ConnectionRepresentable, query urlQuery: URLQuery?) { + + var components = URLComponents() + components.scheme = connection.scheme.rawValue + + let subURL = connection.apiVersion?.subUrl ?? "" + let endPoint = connection.apiVersion?.endPoint ?? "" + + components.host = (subURL.isEmpty ? subURL : subURL + ".") + connection.host.host + components.path = endPoint + connection.path + + var queryItems: [URLQueryItem] = [] + queryItems.addURLQuery(query: urlQuery) + queryItems.addURLQuery(query: connection.defaultQuery) + + let method = connection.method + + if method == .get { + queryItems.addURLQuery(query: connection.host.defaultUrlQuery) + } + + if !queryItems.isEmpty { + components.queryItems = queryItems + } + + guard let url = components.url else { + return nil + } + + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = method.rawValue + + let defaultHeaderFields = connection.host.defaultHeaders + let connectionHeaderFields = connection.httpHeaders + + let headerFields = defaultHeaderFields.merging(connectionHeaderFields) { (_, new) in new } + + if !headerFields.isEmpty { + urlRequest.allHTTPHeaderFields = headerFields + } + request = urlRequest + + #if DEBUG + DebugPrint.logAPIRequest(request: request, apiName: connection.name) + #endif + } +} diff --git a/Sources/NetworkKit/Debug Loging/Debug Logging.swift b/Sources/NetworkKit/Debug Loging/Debug Logging.swift new file mode 100644 index 0000000..dc32772 --- /dev/null +++ b/Sources/NetworkKit/Debug Loging/Debug Logging.swift @@ -0,0 +1,42 @@ +// +// DebugLogging.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +typealias DebugPrint = PilotDebugLogs + +/// Allows Logs to be Printed in Debug Console. +public struct PilotDebugLogs { + + /// Enabled Network debug logs to be printed in debug console. + /// Default value is `true` + public static var isEnabled = true + + // MARK: - DEBUG LOGS HANDLER + + /// Writes the textual representations of the given items into the standard output. + /// - Parameter value: String to be printed on console. + /// - Parameter flag: If the value should be printed on console + static func print(_ value: String, shouldPrint flag: Bool = true) { + if DebugPrint.isEnabled, flag { + Swift.print(value) + } + } + + static func logAPIRequest(request: URLRequest, apiName: String?) { + print( + """ + ------------------------------------------------------------ + API Call Request for: + Name: \(apiName ?? "nil") + \(request.debugDescription) + + """ + ) + } +} diff --git a/Sources/NetworkKit/Errors/Business Error.swift b/Sources/NetworkKit/Errors/Business Error.swift new file mode 100644 index 0000000..bc8cf66 --- /dev/null +++ b/Sources/NetworkKit/Errors/Business Error.swift @@ -0,0 +1,29 @@ + // +// BusinessError.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +/// Personal / Business / Server Errors +enum BusinessError: LocalizedError { + + case errorModel(ErrorModel, Int) + + var localizedDescription: String { + switch self { + case .errorModel(let model, _): + return model.message ?? "" + } + } + + var errorCode: Int { + switch self { + case .errorModel(let model, let httpStatusCode): + return model.code ?? httpStatusCode + } + } +} diff --git a/Sources/NetworkKit/Errors/HTTP Error.swift b/Sources/NetworkKit/Errors/HTTP Error.swift new file mode 100644 index 0000000..ec751b9 --- /dev/null +++ b/Sources/NetworkKit/Errors/HTTP Error.swift @@ -0,0 +1,131 @@ +// +// HTTPStatusCodes.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +/// HTTP Status Codes, obtained in `HTTPURLResponse` +enum HTTPStatusCode: Int, LocalizedError { + + case `continue` = 100 + case switchingProtocols = 101 + case processing = 102 + case earlyHints = 103 + + + case ok = 200 + case created = 201 + case accepted = 202 + case nonAuthoritativeInformation = 203 + case noContent = 204 + case resetContent = 205 + case partialContent = 206 + case multiStatus = 207 + case alreadyReported = 208 + case imUsed = 226 + + + case multipleChoices = 300 + case movedPermanently = 301 + case found = 302 + case seeOther = 303 + case notModified = 304 + case useProxy = 305 + case temporaryRedirect = 307 + case permanentRedirect = 308 + + + case badRequest = 400 + case unauthorized = 401 + case paymentRequired = 402 + case forbidden = 403 + case notFound = 404 + case methodNotAllowed = 405 + case notAcceptable = 406 + case proxyAuthenticationRequired = 407 + case requestTimeout = 408 + case conflict = 409 + case gone = 410 + case lengthRequired = 411 + case preconditionFailed = 412 + case payloadTooLarge = 413 + case uriTooLong = 414 + case unsupportedMediaType = 415 + case rangeNotSatisfiable = 416 + case expectationFailed = 417 + + case imATeapot = 418 + case misdirectedRequest = 421 + case unprocessableEntity = 422 + case locked = 423 + case failedDependency = 424 + case tooEarly = 425 + case upgradeRequired = 426 + case preconditionRequired = 428 + case tooManyRequests = 429 + case requestHeaderFieldsTooLarge = 431 + case iisLoginTimeout = 440 + case nginxNoResponse = 444 + case iisRetryWith = 449 + case blockedByWindowsParentalControls = 450 + case unavailableForLegalReasons = 451 + case nginxSSLCertificateError = 495 + case nginxSSLCertificateRequired = 496 + + case nginxHTTPToHTTPS = 497 + case tokenExpired = 498 + case nginxClientClosedRequest = 499 + + + case internalServerError = 500 + case notImplemented = 501 + case badGateway = 502 + case serviceUnavailable = 503 + case gatewayTimeout = 504 + case httpVersionNotSupported = 505 + case variantAlsoNegotiates = 506 + case insufficientStorage = 507 + case loopDetected = 508 + case bandwidthLimitExceeded = 509 + case notExtended = 510 + case networkAuthenticationRequired = 511 + case siteIsFrozen = 530 + case networkConnectTimeoutError = 599 + + /// Retrieve the localized description for this error. + var localizedDescription: String { + return HTTPURLResponse.localizedString(forStatusCode: rawValue) + } +} + +extension HTTPStatusCode { + /// Informational - Request received, continuing process. + var isInformational: Bool { + return isIn(range: 100...199) + } + /// Success - The action was successfully received, understood, and accepted. + var isSuccess: Bool { + return isIn(range: 200...299) + } + /// Redirection - Further action must be taken in order to complete the request. + var isRedirection: Bool { + return isIn(range: 300...399) + } + /// Client Error - The request contains bad syntax or cannot be fulfilled. + var isClientError: Bool { + return isIn(range: 400...499) + } + /// Server Error - The server failed to fulfill an apparently valid request. + var isServerError: Bool { + return isIn(range: 500...599) + } + + /// - returns: `true` if the status code is in the provided range, false otherwise. + private func isIn(range: ClosedRange) -> Bool { + return range.contains(rawValue) + } +} diff --git a/Sources/NetworkKit/Errors/NKError.swift b/Sources/NetworkKit/Errors/NKError.swift new file mode 100644 index 0000000..31b8d34 --- /dev/null +++ b/Sources/NetworkKit/Errors/NKError.swift @@ -0,0 +1,135 @@ +// +// Network Error.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public protocol NetworkError: LocalizedError { + var code: Int { get } +} + +public struct NKError: NetworkError { + public let localizedDescription: String + public let errorDescription: String? + + public let code: Int + + init(_ httpError: HTTPStatusCode) { + code = httpError.rawValue + localizedDescription = httpError.localizedDescription + errorDescription = localizedDescription + } + + public init(_ error: NSError) { + localizedDescription = error.localizedDescription + code = error.code + + if error.domain == NSCocoaErrorDomain { + errorDescription = error.userInfo[NSDebugDescriptionErrorKey] as? String + + } else if error.domain == NSURLErrorDomain { + let failingURL = error.userInfo[NSURLErrorFailingURLStringErrorKey] as? String + let description = "Failing URL: \(failingURL ?? "nil"). \(localizedDescription)" + errorDescription = description + + } else { + errorDescription = localizedDescription + } + } + + init(_ businessError: BusinessError) { + code = businessError.errorCode + localizedDescription = businessError.localizedDescription + errorDescription = localizedDescription + } + + init(_ nkError: NKError) { + code = nkError.code + localizedDescription = nkError.localizedDescription + errorDescription = localizedDescription + } + + static func validationCancelled(for url: URL) -> NKError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "Response validation cancelled." + ]) + + return NKError(error) + } + + static func badServerResponse(for url: URL) -> NKError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorBadServerResponse, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "Bad server response for request : \(url.absoluteString)" + ]) + + return NKError(error) + } + + static func resourceUnavailable(for url: URL) -> NKError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorResourceUnavailable, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "A requested resource couldn’t be retrieved from url: \(url.absoluteString)." + ]) + + return NKError(error) + } + + static func unsupportedURL(for url: URL?) -> NKError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnsupportedURL, userInfo: [ + NSURLErrorFailingURLErrorKey: url ?? "nill", + NSLocalizedDescriptionKey: "A requested resource couldn’t be retrieved from url: \(url?.absoluteString ?? "nil")." + ]) + + return NKError(error) + } + + static func zeroByteResource(for url: URL) -> NKError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorZeroByteResource, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "A server reported that a URL has a non-zero content length, but terminated the network connection gracefully without sending any data." + ]) + + return NKError(error) + } + + static func cannotDecodeContentData(for url: URL) -> NKError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotDecodeContentData, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "Content data received during a connection request had an unknown content encoding." + ]) + + return NKError(error) + } + + static func cannotDecodeRawData(for url: URL) -> NKError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotDecodeRawData, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "Content data received during a connection request had an unknown content encoding." + ]) + + return NKError(error) + } + + static func notStarted(for url: URL?) -> NKError { + let error = NSError(domain: NSURLErrorDomain, code: NSUserCancelledError, userInfo: [ + NSURLErrorFailingURLErrorKey: url ?? "nil", + NSLocalizedDescriptionKey: "An asynchronous load has been canceled or not started." + ]) + + return NKError(error) + } + + static func unkown() -> NKError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: [ + NSLocalizedDescriptionKey: "An Unknown Occurred." + ]) + + return NKError(error) + } +} diff --git a/Sources/NetworkKit/Extensions/Array+Extension.swift b/Sources/NetworkKit/Extensions/Array+Extension.swift new file mode 100644 index 0000000..a420376 --- /dev/null +++ b/Sources/NetworkKit/Extensions/Array+Extension.swift @@ -0,0 +1,28 @@ +// +// Array+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension Array where Element == URLQueryItem { + + mutating func addURLQuery(query urlQuery: URLQuery?) { + if let urlQuery = urlQuery { + for query in urlQuery { + let queryItem = URLQueryItem(name: query.key, value: query.value) + if !self.contains(queryItem) { + self.append(queryItem) + } + } + } + } + + var toDictionary: URLQuery { + let params = Dictionary(uniqueKeysWithValues: self.map { ($0.name, $0.value) }) + return params + } +} diff --git a/Sources/NetworkKit/Extensions/Data+extension.swift b/Sources/NetworkKit/Extensions/Data+extension.swift new file mode 100644 index 0000000..db4d4ad --- /dev/null +++ b/Sources/NetworkKit/Extensions/Data+extension.swift @@ -0,0 +1,16 @@ +// +// Data+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension Data { + + var debugDescription: String { + return String(data: self, encoding: .utf8) ?? "nil" + } +} diff --git a/Sources/NetworkKit/Extensions/Dictionary+Extension.swift b/Sources/NetworkKit/Extensions/Dictionary+Extension.swift new file mode 100644 index 0000000..38bfeaf --- /dev/null +++ b/Sources/NetworkKit/Extensions/Dictionary+Extension.swift @@ -0,0 +1,48 @@ +// +// Dictionary+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension Dictionary { + + var prettyPrint: String { + let prefix = isEmpty ? "" : "\n" + var printString = "\(prefix)[" + + for (key, value) in self { + let itemString = "\n\t\(key): \(value)," + printString.append(itemString) + } + + let postfix = isEmpty ? " " : "\n" + printString.append("\(postfix)]") + + return printString + } +} + +extension Dictionary where Value: OptionalDelegate { + + var prettyPrint: String { + let prefix = isEmpty ? "" : "\n" + var printString = "\(prefix)[" + + for (key, value) in self { + let optionalValue = value.unwrappedValue() + let value = optionalValue == nil ? "nil" : "\(optionalValue!)" + + let itemString = "\n\t\(key): \(value)," + printString.append(itemString) + } + + let postfix = isEmpty ? " " : "\n" + printString.append("\(postfix)]") + + return printString + } +} diff --git a/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift b/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift new file mode 100644 index 0000000..a2df0af --- /dev/null +++ b/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift @@ -0,0 +1,13 @@ +// +// JSONDecoder+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension JSONDecoder: NetworkDecoder { + public typealias Input = Data +} diff --git a/Sources/NetworkKit/Extensions/Never+Extension.swift b/Sources/NetworkKit/Extensions/Never+Extension.swift new file mode 100644 index 0000000..3018558 --- /dev/null +++ b/Sources/NetworkKit/Extensions/Never+Extension.swift @@ -0,0 +1,14 @@ +// +// Never+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension Never: NetworkError { + + public var code: Int { -150716 } +} diff --git a/Sources/NetworkKit/Extensions/NotificationName+Extension.swift b/Sources/NetworkKit/Extensions/NotificationName+Extension.swift new file mode 100644 index 0000000..134c125 --- /dev/null +++ b/Sources/NetworkKit/Extensions/NotificationName+Extension.swift @@ -0,0 +1,14 @@ +// +// NotificationName+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension Notification.Name { + + static let logAPIAnalytics = Notification.Name(rawValue: "logAPIAnalytics") +} diff --git a/Sources/NetworkKit/Extensions/Optional+Extension.swift b/Sources/NetworkKit/Extensions/Optional+Extension.swift new file mode 100644 index 0000000..07f1b81 --- /dev/null +++ b/Sources/NetworkKit/Extensions/Optional+Extension.swift @@ -0,0 +1,23 @@ +// +// Optional+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +protocol OptionalDelegate { + + associatedtype Wrapped + + func unwrappedValue() -> Wrapped? +} + +extension Optional: OptionalDelegate { + + func unwrappedValue() -> Wrapped? { + return self + } +} diff --git a/Sources/NetworkKit/Extensions/Result+Extension.swift b/Sources/NetworkKit/Extensions/Result+Extension.swift new file mode 100644 index 0000000..d407ada --- /dev/null +++ b/Sources/NetworkKit/Extensions/Result+Extension.swift @@ -0,0 +1,22 @@ +// +// Result+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension Result where Failure: Error { + + func getError() -> Failure? { + switch self { + case .success: + return nil + + case .failure(let error): + return error + } + } +} diff --git a/Sources/NetworkKit/Extensions/String+Extension.swift b/Sources/NetworkKit/Extensions/String+Extension.swift new file mode 100644 index 0000000..4702dbe --- /dev/null +++ b/Sources/NetworkKit/Extensions/String+Extension.swift @@ -0,0 +1,16 @@ +// +// String+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension String.StringInterpolation { + + mutating func appendInterpolation(_ value: Environment) { + appendInterpolation(value.value) + } +} diff --git a/Sources/NetworkKit/Extensions/UIImage+NSImage+Extension.swift b/Sources/NetworkKit/Extensions/UIImage+NSImage+Extension.swift new file mode 100644 index 0000000..5d30d96 --- /dev/null +++ b/Sources/NetworkKit/Extensions/UIImage+NSImage+Extension.swift @@ -0,0 +1,42 @@ +// +// UIImage+NSImage+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +#if canImport(WatchKit) + +import UIKit.UIImage + +extension UIImage { + + static func initialize(using data: inout Data) -> UIImage? { + UIImage(data: data) + } +} + +#elseif canImport(UIKit) + +import UIKit.UIImage + +extension UIImage { + + static func initialize(using data: inout Data) -> UIImage? { + UIImage(data: data, scale: UIScreen.main.scale) + } +} + +#elseif canImport(AppKit) + +import AppKit.NSImage + +extension NSImage { + + static func initialize(using data: inout Data) -> NSImage? { + NSImage(data: data) + } +} + +#endif diff --git a/Sources/NetworkKit/Extensions/UIImageView+NSImageView+WKInterfaceImage+Extension.swift b/Sources/NetworkKit/Extensions/UIImageView+NSImageView+WKInterfaceImage+Extension.swift new file mode 100644 index 0000000..0a6af02 --- /dev/null +++ b/Sources/NetworkKit/Extensions/UIImageView+NSImageView+WKInterfaceImage+Extension.swift @@ -0,0 +1,47 @@ +// +// UIImageView+NSImageView+WKInterfaceImage+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +#if canImport(WatchKit) + +import WatchKit + +extension WKInterfaceImage: ImageDownloadDelegate { + + public var image: ImageType? { + get { nil } + set { setImage(newValue) } + } + + open func prepareForReuse(_ placeholder: ImageType? = nil) { + setImage(placeholder) + } +} + +#elseif canImport(UIKit) + +import UIKit.UIImage + +extension UIImageView: ImageDownloadDelegate { + + open func prepareForReuse(_ placeholder: ImageType? = nil) { + image = placeholder + } +} + +#elseif canImport(AppKit) + +import AppKit.NSImage + +extension NSImageView: ImageDownloadDelegate { + + open func prepareForReuse(_ placeholder: ImageType? = nil) { + image = placeholder + } +} + +#endif diff --git a/Sources/NetworkKit/Extensions/URL+Extension.swift b/Sources/NetworkKit/Extensions/URL+Extension.swift new file mode 100644 index 0000000..e20dad4 --- /dev/null +++ b/Sources/NetworkKit/Extensions/URL+Extension.swift @@ -0,0 +1,19 @@ +// +// URL+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension URL { + + var parameters: URLQuery { + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { + return [:] + } + return components.queryItems?.toDictionary ?? [:] + } +} diff --git a/Sources/NetworkKit/Extensions/URLRequest+Extension.swift b/Sources/NetworkKit/Extensions/URLRequest+Extension.swift new file mode 100644 index 0000000..b75db4f --- /dev/null +++ b/Sources/NetworkKit/Extensions/URLRequest+Extension.swift @@ -0,0 +1,27 @@ +// +// URLRequest+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension URLRequest { + + var debugDescription: String { + """ + ------------------------------------------------------------ + Request Method: \(httpMethod ?? "nil") + Request URL: \(url?.absoluteString ?? "nil") + + Request Parameters: \((url?.parameters ?? [:]).prettyPrint) + + Request Headers: \((allHTTPHeaderFields ?? [:]).prettyPrint) + + Request HTTPBody: \(httpBody?.debugDescription ?? "nil") + ------------------------------------------------------------ + """ + } +} diff --git a/Sources/NetworkKit/Kit/Network Queue/Network Queue.swift b/Sources/NetworkKit/Kit/Network Queue/Network Queue.swift new file mode 100644 index 0000000..2747fa5 --- /dev/null +++ b/Sources/NetworkKit/Kit/Network Queue/Network Queue.swift @@ -0,0 +1,34 @@ +// +// Network Queue.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public final class NetworkQueue { + + let operationQueue: OperationQueue + var request: URLRequest? + var apiName: String + + var operations: [Operation] { + operationQueue.operations + } + + init(operationQueue: OperationQueue, request: URLRequest?, apiName: String?) { + self.operationQueue = operationQueue + self.request = request + self.apiName = apiName ?? "nil" + } + + func addOperation(_ op: Operation) { + operationQueue.addOperation(op) + } + + func addOperation(_ block: @escaping () -> Void) { + operationQueue.addOperation(block) + } +} diff --git a/Sources/NetworkKit/Kit/Network Result/Network Result.swift b/Sources/NetworkKit/Kit/Network Result/Network Result.swift new file mode 100644 index 0000000..f81f46c --- /dev/null +++ b/Sources/NetworkKit/Kit/Network Result/Network Result.swift @@ -0,0 +1,25 @@ +// +// Network Result.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public final class NetworkResult { + + var result: Result + + var operation: Operation? + + init(result: Result) { + self.result = result + } + + init() { + let error = NKError.notStarted(for: nil) + result = .failure(error as! Failure) + } +} diff --git a/Sources/NetworkKit/Kit/NetworkKit.swift b/Sources/NetworkKit/Kit/NetworkKit.swift new file mode 100644 index 0000000..a5b44f9 --- /dev/null +++ b/Sources/NetworkKit/Kit/NetworkKit.swift @@ -0,0 +1,47 @@ +// +// NetworkKit.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public struct NetworkKit: NetworkPublisher { + + public var result: NetworkResult + + public var queue: NetworkQueue + + public typealias Output = (data: Data, response: HTTPURLResponse) + + public typealias Failure = NKError + + public let request: URLRequest? + + private let operationQueue: OperationQueue = { + let queue = OperationQueue() + queue.maxConcurrentOperationCount = 1 + queue.qualityOfService = .utility + queue.isSuspended = true + return queue + }() + + public init(_ builder: () -> NetworkRequest) { + + let requestBuilder = DispatchQueue.global(qos: .utility).sync { builder() } + request = requestBuilder.request + + result = .init() + queue = .init(operationQueue: operationQueue, + request: requestBuilder.request, + apiName: requestBuilder.apiName) + resume() + } + + private func resume() { + let fetchOperation = FetchOperation(request: request, result: result) + addToQueue(isSuspended: true, fetchOperation) + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift b/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift new file mode 100644 index 0000000..a71a6da --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift @@ -0,0 +1,45 @@ +// +// Assign.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension NetworkPublishers { + + struct Assign: NetworkCancellable where Upstream.Failure == Never { + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + public let root: Root + + public let keyPath: ReferenceWritableKeyPath + + public init(upstream: Upstream, to keyPath: ReferenceWritableKeyPath, on root: Root) { + self.upstream = upstream + self.root = root + self.keyPath = keyPath + assign() + } + + private func assign() { + addToQueue { + let value = try! self.upstream.result.result.get() + self.root[keyPath: self.keyPath] = value + } + } + + private func addToQueue(_ block: @escaping () -> Void) { + let op = BlockOperation(block: block) + upstream.queue.addOperation(op) + } + + public func cancel() { + upstream.queue.operationQueue.cancelAllOperations() + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift new file mode 100644 index 0000000..a08ba1c --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift @@ -0,0 +1,96 @@ +// +// Catch.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublishers { + + struct Catch: NetworkPublisher where Upstream.Output == NewPublisher.Output { + + public var result: NetworkResult + + public var queue: NetworkQueue { + upstream.queue + } + + public typealias Output = Upstream.Output + + public typealias Failure = NewPublisher.Failure + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. + public let handler: (Upstream.Failure) -> NewPublisher + + /// Creates a publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. + /// + /// - Parameters: + /// - upstream: The publisher that this publisher receives elements from. + /// - handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. + public init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher) { + self.upstream = upstream + self.handler = handler + result = .init() + + let operation = CatchOperation(upstream: upstream, handler: handler, result: result) + addToQueue(operation) + } + } +} + + +final class CatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { + + public typealias Output = Upstream.Output + + public typealias Failure = NewPublisher.Failure + + /// The publisher that this publisher receives elements from. + private let upstream: Upstream + + private var result: NetworkResult + + /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. + private let handler: (Upstream.Failure) -> NewPublisher + + init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher, result: NetworkResult) { + self.upstream = upstream + self.handler = handler + self.result = result + } + + override func main() { + switch upstream.result.result { + case .success(let output): + result.result = .success(output) + + case .failure(let error): + let newPublisher = handler(error) + + guard let newOperation = newPublisher.result.operation else { + return + } + + newOperation.completionBlock = { [weak self] in + switch newPublisher.result.result { + case .success(let output): + self?.result.result = .success(output) + + case .failure(let error): + self?.result.result = .failure(error) + } + + newPublisher.queue.operationQueue.cancelAllOperations() + self?.finish() + } + + newOperation.main() + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift new file mode 100644 index 0000000..85136d3 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift @@ -0,0 +1,104 @@ +// +// Try Catch.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublishers { + + struct TryCatch: NetworkPublisher where Upstream.Output == NewPublisher.Output { + + public var result: NetworkResult + + public var queue: NetworkQueue { + upstream.queue + } + + public typealias Output = Upstream.Output + + public typealias Failure = NewPublisher.Failure + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. + public let handler: (Upstream.Failure) throws -> NewPublisher + + /// Creates a publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. + /// + /// - Parameters: + /// - upstream: The publisher that this publisher receives elements from. + /// - handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. + public init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher) { + self.upstream = upstream + self.handler = handler + result = .init() + + let operation = TryCatchOperation(upstream: upstream, handler: handler, result: result) + addToQueue(operation) + } + } +} + +final class TryCatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { + + + public typealias Output = Upstream.Output + + public typealias Failure = NewPublisher.Failure + + /// The publisher that this publisher receives elements from. + private let upstream: Upstream + + private var result: NetworkResult + + /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. + private let handler: (Upstream.Failure) throws -> NewPublisher + + public init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher, result: NetworkResult) { + self.upstream = upstream + self.handler = handler + self.result = result + } + + override func main() { + switch upstream.result.result { + case .success(let output): + result.result = .success(output) + finish() + + case .failure(let error): + do { + let newPublisher = try handler(error) + + guard let newOperation = newPublisher.result.operation else { + result.result = .failure(NKError.unkown() as! NewPublisher.Failure) + return + } + + newOperation.completionBlock = { [weak self] in + switch newPublisher.result.result { + case .success(let output): + self?.result.result = .success(output) + + case .failure(let error): + self?.result.result = .failure(error) + } + + newPublisher.queue.operationQueue.cancelAllOperations() + self?.finish() + } + + newOperation.main() + + } catch let handlerError { + result.result = .failure(NKError(handlerError as NSError) as! NewPublisher.Failure) + finish() + } + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift b/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift new file mode 100644 index 0000000..3496a80 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift @@ -0,0 +1,42 @@ +// +// Completion.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension NetworkPublishers { + + struct Completion: NetworkCancellable { + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + /// A closure that returns the result of the upstream publisher. + public let block: (Result) -> Void + + public init(upstream: Upstream, block: @escaping (Result) -> Void) { + self.upstream = upstream + self.block = block + completion() + } + + private func completion() { + addToQueue { + self.block(self.upstream.result.result) + } + } + + private func addToQueue(_ block: @escaping () -> Void) { + let op = BlockOperation(block: block) + upstream.queue.addOperation(op) + } + + public func cancel() { + upstream.queue.operationQueue.cancelAllOperations() + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift new file mode 100644 index 0000000..570b5a0 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift @@ -0,0 +1,48 @@ +// +// Debounce.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublishers { + + struct Debounce: NetworkPublisher { + + public var result: NetworkResult { + upstream.result + } + + public var queue: NetworkQueue { + upstream.queue + } + + public typealias Output = Upstream.Output + + public typealias Failure = Upstream.Failure + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + public let time: DispatchTime + + public init(upstream: Upstream, time: DispatchTime) { + self.upstream = upstream + self.time = time + perform() + } + + private func perform() { + let operation = DebounceOperation(time: time) + queue.operations.first?.addDependency(operation) + addToQueue(operation) + } + + public func cancel() { + upstream.queue.operationQueue.cancelAllOperations() + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift new file mode 100644 index 0000000..88bed23 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift @@ -0,0 +1,75 @@ +// +// Decode.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublishers { + + struct Decode: NetworkPublisher where Upstream.Output == Decoder.Input { + + public var result: NetworkResult + + public var queue: NetworkQueue { + upstream.queue + } + + public typealias Output = Item + + public typealias Failure = NKError + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + public let decoder: Decoder + + public init(upstream: Upstream, decoder: Decoder) { + self.upstream = upstream + self.decoder = decoder + result = .init() + perform() + } + + public init(upstream: Upstream, jsonKeyDecodingStrategy: JSONDecoder.KeyDecodingStrategy) { + self.upstream = upstream + + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = jsonKeyDecodingStrategy + self.decoder = decoder as! Decoder + + result = .init() + perform() + } + + private func perform() { + addToQueue { + self.doDecoding() + } + } + + private func doDecoding() { + let upstreamResult = upstream.result.result + + switch upstreamResult { + case .success(let data): + + do { + let output = try decoder.decode(Item.self, from: data) + self.result.result = .success(output) + + } catch { + let nkError = NKError(error as NSError) + result.result = .failure(nkError) + } + + case .failure(let error): + let nkError = NKError(error as NSError) + result.result = .failure(nkError) + } + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift new file mode 100644 index 0000000..2de5fe7 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift @@ -0,0 +1,63 @@ +// +// Map KeyPath 2.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension NetworkPublishers { + + /// A publisher that publishes the values of two key paths as a tuple. + public struct MapKeyPath2: NetworkPublisher { + + public var result: NetworkResult<(Output0, Output1), Upstream.Failure> + + public var queue: NetworkQueue { + upstream.queue + } + + public typealias Output = (Output0, Output1) + + public typealias Failure = Upstream.Failure + + /// The publisher from which this publisher receives elements. + public let upstream: Upstream + + /// The key path of a property to publish. + public let keyPath0: KeyPath + + /// The key path of a second property to publish. + public let keyPath1: KeyPath + + public init(upstream: Upstream, keyPath0: KeyPath, keyPath1: KeyPath) { + self.upstream = upstream + self.keyPath0 = keyPath0 + self.keyPath1 = keyPath1 + + result = .init() + } + + private func perform() { + addToQueue { + self.map() + } + } + + private func map() { + switch upstream.result.result { + case .success(let output): + let output1 = output[keyPath: keyPath0] + let output2 = output[keyPath: keyPath1] + + result.result = .success((output1, output2)) + + case .failure(let error): + result.result = .failure(error) + } + } + } + +} diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift new file mode 100644 index 0000000..1b7b6d2 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift @@ -0,0 +1,53 @@ +// +// Map KeyPath.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublishers { + + /// A publisher that publishes the value of a key path. + struct MapKeyPath: NetworkPublisher { + + public var result: NetworkResult + + public var queue: NetworkQueue { + upstream.queue + } + + public typealias Failure = Upstream.Failure + + /// The publisher from which this publisher receives elements. + public let upstream: Upstream + + /// The key path of a property to publish. + public let keyPath: KeyPath + + public init(upstream: Upstream, keyPath: KeyPath) { + self.upstream = upstream + self.keyPath = keyPath + result = .init() + } + + private func perform() { + addToQueue { + self.map() + } + } + + private func map() { + switch upstream.result.result { + case .success(let output): + result.result = .success(output[keyPath: keyPath]) + + case .failure(let error): + result.result = .failure(error) + } + } + } + +} diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift new file mode 100644 index 0000000..c68f360 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift @@ -0,0 +1,130 @@ +// +// Map.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublishers { + + struct Map: NetworkPublisher { + + public var result: NetworkResult + + public var queue: NetworkQueue { + upstream.queue + } + + public typealias Output = MapOutput + + public typealias Failure = Upstream.Failure + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + /// The closure that transforms elements from the upstream publisher. + public let transform: (Upstream.Output) -> Output + + public init(upstream: Upstream, transform: @escaping (Upstream.Output) -> Output) { + self.upstream = upstream + self.transform = transform + result = .init() + perform() + } + + private func perform() { + addToQueue { + self.doTransform() + } + } + + private func doTransform() { + let upstreamResult = upstream.result.result + + switch upstreamResult { + case .success(let output): + let newOutput = transform(output) + result.result = .success(newOutput) + + case .failure(let error): + result.result = .failure(error) + + } + } + } +} + + + +//@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +//extension Publishers { +// +// /// A publisher that republishes all non-`nil` results of calling a closure with each received element. +// public struct CompactMap : Publisher where Upstream : Publisher { +// +// /// The kind of errors this publisher might publish. +// /// +// /// Use `Never` if this `Publisher` does not publish errors. +// public typealias Failure = Upstream.Failure +// +// /// The publisher from which this publisher receives elements. +// public let upstream: Upstream +// +// /// A closure that receives values from the upstream publisher and returns optional values. +// public let transform: (Upstream.Output) -> Output? +// +// public init(upstream: Upstream, transform: @escaping (Upstream.Output) -> Output?) +// +// /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)` +// /// +// /// - SeeAlso: `subscribe(_:)` +// /// - Parameters: +// /// - subscriber: The subscriber to attach to this `Publisher`. +// /// once attached it can begin to receive values. +// public func receive(subscriber: S) where Output == S.Input, S : Subscriber, Upstream.Failure == S.Failure +// } +// +// /// A publisher that republishes all non-`nil` results of calling an error-throwing closure with each received element. +// public struct TryCompactMap : Publisher where Upstream : Publisher { +// +// /// The kind of errors this publisher might publish. +// /// +// /// Use `Never` if this `Publisher` does not publish errors. +// public typealias Failure = Error +// +// /// The publisher from which this publisher receives elements. +// public let upstream: Upstream +// +// /// An error-throwing closure that receives values from the upstream publisher and returns optional values. +// /// +// /// If this closure throws an error, the publisher fails. +// public let transform: (Upstream.Output) throws -> Output? +// +// public init(upstream: Upstream, transform: @escaping (Upstream.Output) throws -> Output?) +// +// /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)` +// /// +// /// - SeeAlso: `subscribe(_:)` +// /// - Parameters: +// /// - subscriber: The subscriber to attach to this `Publisher`. +// /// once attached it can begin to receive values. +// public func receive(subscriber: S) where Output == S.Input, S : Subscriber, S.Failure == Publishers.TryCompactMap.Failure +// } +//} + + +///// Calls a closure with each received element and publishes any returned optional that has a value. +///// +///// - Parameter transform: A closure that receives a value and returns an optional value. +///// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure. +//public func compactMap(_ transform: @escaping (Self.Output) -> T?) -> Publishers.CompactMap +// +///// Calls an error-throwing closure with each received element and publishes any returned optional that has a value. +///// +///// If the closure throws an error, the publisher cancels the upstream and sends the thrown error to the downstream receiver as a `Failure`. +///// - Parameter transform: an error-throwing closure that receives a value and returns an optional value. +///// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure. +//public func tryCompactMap(_ transform: @escaping (Self.Output) throws -> T?) -> Publishers.TryCompactMap diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift new file mode 100644 index 0000000..253886c --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift @@ -0,0 +1,64 @@ +// +// Try Map.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublishers { + + struct TryMap: NetworkPublisher { + + public var result: NetworkResult + + public var queue: NetworkQueue { + upstream.queue + } + + public typealias Output = MapOutput + + public typealias Failure = Upstream.Failure + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + /// The closure that transforms elements from the upstream publisher. + public let transform: (Upstream.Output) throws -> Output + + public init(upstream: Upstream, transform: @escaping (Upstream.Output) throws -> Output) { + self.upstream = upstream + self.transform = transform + result = .init() + perform() + } + + private func perform() { + addToQueue { + self.doTransform() + } + } + + private func doTransform() { + let upstreamResult = upstream.result.result + + switch upstreamResult { + case .success(let output): + + do { + let newOutput = try transform(output) + result.result = .success(newOutput) + + } catch let tranformError { + let error = tranformError as NSError + result.result = .failure(NKError(error) as! Upstream.Failure) + } + + case .failure(let error): + result.result = .failure(error) + } + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Network Publishers.swift b/Sources/NetworkKit/Kit/Publishers/Network Publishers.swift new file mode 100644 index 0000000..1e07742 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Network Publishers.swift @@ -0,0 +1,13 @@ +// +// Network Publishers.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + + +import Foundation + +public enum NetworkPublishers { +} diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift new file mode 100644 index 0000000..1032dbb --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift @@ -0,0 +1,56 @@ +// +// Replace Error.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublishers { + + struct ReplaceError: NetworkPublisher { + + public var result: NetworkResult + + public var queue: NetworkQueue { + upstream.queue + } + + public typealias Output = Upstream.Output + + public typealias Failure = Never + + /// The element with which to replace errors from the upstream publisher. + public let output: Upstream.Output + + /// The publisher from which this publisher receives elements. + public let upstream: Upstream + + public init(upstream: Upstream, output: NetworkPublishers.ReplaceError.Output) { + self.upstream = upstream + self.output = output + result = .init(result: .success(output)) + perform() + } + + private func perform() { + addToQueue { + self.doReplace() + } + } + + private func doReplace() { + let upstreamResult = upstream.result.result + + switch upstreamResult { + case .success(let output): + result.result = .success(output) + + case .failure: + result.result = .success(output) + } + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift new file mode 100644 index 0000000..b8d6809 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift @@ -0,0 +1,159 @@ +// +// Validation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublishers { + + struct Validate: NetworkPublisher where Upstream.Output == NetworkKit.Output, Upstream.Failure == NetworkKit.Failure { + + public var queue: NetworkQueue { + upstream.queue + } + + public var result: NetworkResult + + public typealias Output = NetworkKit.Output + + public typealias Failure = NetworkKit.Failure + + /// The publisher from which this publisher receives elements. + public let upstream: Upstream + + /// Check for any business error model on failure. + public let shouldCheckForErrorModel: Bool + + /// Acceptable HTTP Status codes for the network call. + public let acceptableStatusCodes: [Int] + + public init(upstream: Upstream, shouldCheckForErrorModel: Bool, acceptableStatusCodes: [Int]) { + self.upstream = upstream + self.shouldCheckForErrorModel = shouldCheckForErrorModel + + let sessionCodes = SessionManager.shared.acceptableStatusCodes + let codes = acceptableStatusCodes.isEmpty ? sessionCodes : acceptableStatusCodes + + self.acceptableStatusCodes = codes + + result = .init() + perform() + } + + private func perform() { + addToQueue(isSuspended: true) { + self.doValidation() + } + } + + private func doValidation() { + guard let url = queue.request?.url else { + result.result = .failure(.unsupportedURL(for: nil)) + return + } + + guard let (data, response) = try? upstream.result.result.get() else { + result.result = upstream.result.result + return + } + + if acceptableStatusCodes.contains(response.statusCode) { + + guard !data.isEmpty else { + result.result = .failure(.zeroByteResource(for: url)) + return + } + + var acceptableContentTypes: [String] { + if let accept = queue.request?.value(forHTTPHeaderField: "Accept") { + return accept.components(separatedBy: ",") + } + + return ["*/*"] + } + + guard let responseContentType = response.mimeType, let responseMIMEType = MIMEType(responseContentType) else { + for contentType in acceptableContentTypes { + if let mimeType = MIMEType(contentType), mimeType.isWildcard { + result.result = .success((data, response)) + return + } + } + + result.result = .failure(.cannotDecodeContentData(for: url)) + return + } + + for contentType in acceptableContentTypes { + if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) { + result.result = .success((data, response)) + return + } + } + + result.result = .failure(.cannotDecodeContentData(for: url)) + + } else { + // On Failure it checks if it a business error. + + if shouldCheckForErrorModel, !data.isEmpty { + + let model = try? JSONDecoder().decode(ErrorModel.self, from: data) + if let errorModel = model { + let error = BusinessError.errorModel(errorModel, response.statusCode) + result.result = .failure(NKError(error)) + + return + } + + // else throw http or url error + if let code = HTTPStatusCode(rawValue: response.statusCode) { + result.result = .failure(.init(code)) + } else { + result.result = .failure(.badServerResponse(for: url)) + } + } + } + } + } +} + + +private extension NetworkPublishers.Validate { + + /// ACCEPTABLE CONTENT TYPE CHECK + struct MIMEType { + let type: String + let subtype: String + + var isWildcard: Bool { return type == "*" && subtype == "*" } + + init?(_ string: String) { + let components: [String] = { + let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines) + let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)] + return split.components(separatedBy: "/") + }() + + if let type = components.first, let subtype = components.last { + self.type = type + self.subtype = subtype + } else { + return nil + } + } + + func matches(_ mime: MIMEType) -> Bool { + switch (type, subtype) { + case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"): + return true + default: + return false + } + } + } +} diff --git a/Sources/NetworkKit/Models/API Analytics.swift b/Sources/NetworkKit/Models/API Analytics.swift new file mode 100644 index 0000000..a452636 --- /dev/null +++ b/Sources/NetworkKit/Models/API Analytics.swift @@ -0,0 +1,17 @@ +// +// APIAnalytics.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public struct APIAnalytics { + public let apiName: String + public let urlString: String + public let totalTime: TimeInterval + public let errorCode: Int? + public let errorMessage: String? +} diff --git a/Sources/NetworkKit/Models/Error Model.swift b/Sources/NetworkKit/Models/Error Model.swift new file mode 100644 index 0000000..e0517c9 --- /dev/null +++ b/Sources/NetworkKit/Models/Error Model.swift @@ -0,0 +1,28 @@ +// +// ErrorModel.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +// MARK: - ErrorModel +public struct ErrorModel: Codable { + public let businessCode: Int? + public let errorCode: Int? + public let message: String? + public let status: String? + + public var code: Int? { + return businessCode ?? errorCode + } + + public enum CodingKeys: String, CodingKey { + case businessCode = "business_error_code" + case errorCode = "error_code" + case message + case status + } +} diff --git a/Sources/NetworkKit/NetworkKit.swift b/Sources/NetworkKit/NetworkKit.swift deleted file mode 100644 index 4361043..0000000 --- a/Sources/NetworkKit/NetworkKit.swift +++ /dev/null @@ -1,3 +0,0 @@ -struct NetworkKit { - var text = "Hello, World!" -} diff --git a/Sources/NetworkKit/Networking/Configuration/Network Configuration+Notification.swift b/Sources/NetworkKit/Networking/Configuration/Network Configuration+Notification.swift new file mode 100644 index 0000000..c3eec3c --- /dev/null +++ b/Sources/NetworkKit/Networking/Configuration/Network Configuration+Notification.swift @@ -0,0 +1,28 @@ +// +// Network Configuration+Notification.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +#if canImport(AppKit) +import AppKit.NSApplication + +// MARK: - NOTIFICATION OBSERVERS +extension NetworkConfiguration { + var notification: Notification.Name { NSApplication.willTerminateNotification } + +} + +#elseif canImport(WatchKit) + +#elseif canImport(UIKit) +import UIKit.UIApplication + +// MARK: - NOTIFICATION OBSERVERS +extension NetworkConfiguration { + var notification: Notification.Name { UIApplication.willTerminateNotification } +} + +#endif diff --git a/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift b/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift new file mode 100644 index 0000000..e07ff23 --- /dev/null +++ b/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift @@ -0,0 +1,167 @@ +// +// Network Configuration.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +// MARK: - NETWORKING + +/// A Class that coordinates a group of related network data transfer tasks. +open class NetworkConfiguration { + + /// Allows Logs to be Printed in Debug Console. + /// Default value is `true` + open var debugPrint: Bool + + /// Should Empty URL Cache Before Application Terminates. + /// Default value is `true`. + open var emptyCacheOnAppTerminate: Bool + + /// Should Empty URL Cache Before Application Terminates. + /// Default value is `false`. + public static var emptyCacheOnAppTerminateOnAllSessions: Bool = false + + /// URL Cache for a URLSession + public let urlCache: URLCache? + + /// An object that coordinates a group of related network data transfer tasks. + public let session: URLSession + + /// ACCEPTABLE STATUS CODES + open var acceptableStatusCodes: [Int] = Array(200 ..< 300) + + /// A default session configuration object. + public static var defaultConfiguration: URLSessionConfiguration = { + let configuration: URLSessionConfiguration = .default + + configuration.requestCachePolicy = .useProtocolCachePolicy + if #available(iOS 11.0, watchOS 4.0, tvOS 11.0, macOS 10.13, *) { + configuration.waitsForConnectivity = false + } + configuration.networkServiceType = .responsiveData + configuration.timeoutIntervalForRequest = TimeInterval(integerLiteral: 0) + configuration.timeoutIntervalForResource = TimeInterval(integerLiteral: 0) + + return configuration + }() + + /// Inititalises Manager with `defaultURLSessionConfiguration` Configuration. + public init(useDefaultCache: Bool, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, cacheDiskPath diskPath: String? = nil) { + let configuration = NetworkConfiguration.defaultConfiguration + configuration.requestCachePolicy = requestCachePolicy + + if useDefaultCache { + if #available(iOS 13.0, watchOS 6.0, tvOS 13.0, macOS 10.15, *) { + let cache = URLCache(memoryCapacity: 5242880, diskCapacity: 52428800) + urlCache = cache + } else { + let cache = URLCache(memoryCapacity: 5242880, diskCapacity: 52428800, diskPath: diskPath) + urlCache = cache + } + configuration.urlCache = urlCache + } else { + urlCache = nil + } + + session = URLSession(configuration: configuration) + debugPrint = true + emptyCacheOnAppTerminate = true + setNotificationObservers() + } + + public init(urlCache cache: URLCache? = nil, configuration config: URLSessionConfiguration) { + urlCache = cache + session = URLSession(configuration: config) + debugPrint = true + emptyCacheOnAppTerminate = true + setNotificationObservers() + } + + deinit { + NotificationCenter.default.removeObserver(self) + session.invalidateAndCancel() + } + + // MARK: - NOTIFICATION OBSERVERS + private func setNotificationObservers() { + #if !canImport(WatchKit) + NotificationCenter.default.addObserver(forName: notification, object: nil, queue: .init()) { [weak self] (_) in + if let `self` = self, self.emptyCacheOnAppTerminate || NetworkConfiguration.emptyCacheOnAppTerminateOnAllSessions { + self.removeAllCachedResponses() + } + } + #endif + } +} + + +// MARK: - URL CACHE MANAGER +extension NetworkConfiguration { + + /// Remove All Cached Responses from this session. + open func removeAllCachedResponses() { + session.configuration.urlCache?.removeAllCachedResponses() + } + + #if DEBUG + public func printURLCacheDetails() { + guard let cache = session.configuration.urlCache else { + print( + """ + + --------------------------------------------- + Cannot Print Cache Memory And Disk Capacity And Usage + Error - No URL Cache Found + --------------------------------------------- + + """ + ) + return + } + let byteToMb: Double = 1048576 + + let memoryCapacity = Double(cache.memoryCapacity) / byteToMb + let memoryUsage = Double(cache.currentMemoryUsage) / byteToMb + + let diskCapacity = Double(cache.diskCapacity) / byteToMb + let diskUsage = Double(cache.currentDiskUsage) / byteToMb + + print( + """ + + --------------------------------------------- + Current URL Cache Memory And Disk Capacity And Usage + Memory Capacity: \(String(format: "%.2f", memoryCapacity)) Mb + Memory Usage: \(String(format: "%.3f", memoryUsage)) Mb + + Disk Capacity: \(String(format: "%.2f", diskCapacity)) Mb + Disk Usage: \(String(format: "%.3f", diskUsage)) Mb + --------------------------------------------- + + """ + ) + } + #endif +} + +// MARK: - SERVER ENVIRONMENT +public extension NetworkConfiguration { + + /// Changes Server Environment. + /// - Parameter newEnvironment: New Environment type to be set. + @discardableResult + static func changeEnvironment(_ newEnvironment: Environment) -> Bool { + Environment.current = newEnvironment + UserDefaults.standard.set(newEnvironment.value, forKey: "api_environment") + return true + } + + /// Returns Current Server Environment set. + static var currentEnvironment: Environment? { + return Environment.current + } +} diff --git a/Sources/NetworkKit/Networking/Image Session Manager/Image Session Manager.swift b/Sources/NetworkKit/Networking/Image Session Manager/Image Session Manager.swift new file mode 100644 index 0000000..8ca441b --- /dev/null +++ b/Sources/NetworkKit/Networking/Image Session Manager/Image Session Manager.swift @@ -0,0 +1,131 @@ +// +// ImageDownloader.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public final class ImageSessionManager: NetworkConfiguration { + private typealias ImageValidationResult = (response: HTTPURLResponse, data: Data, image: ImageType) + + public static let shared = ImageSessionManager() + + public init(useCache: Bool = true, cacheDiskPath: String? = "cachedImages") { + let requestCachePolicy: NSURLRequest.CachePolicy = useCache ? .returnCacheDataElseLoad : .useProtocolCachePolicy + super.init(useDefaultCache: useCache, requestCachePolicy: requestCachePolicy, cacheDiskPath: cacheDiskPath) + emptyCacheOnAppTerminate = false + } +} + +public extension ImageSessionManager { + + /// Creates a task that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion. + /// - Parameter url: URL from where image has to be fetched. + /// - Parameter useCache: Flag which allows Response and Data to be cached. + /// - Parameter completion: The completion handler to call when the load request is complete. This handler is executed on the main queue. This completion handler takes the Result as parameter. On Success, it returns the image. On Failure, returns URLError. + /// - Returns: **URLSessionTask** for further operations. + @discardableResult + func fetch(from url: URL, cacheImage useCache: Bool = true, completion: @escaping (Result) -> ()) -> URLSessionDataTask { + + let requestCachePolicy: NSURLRequest.CachePolicy = useCache ? .returnCacheDataElseLoad : .reloadIgnoringLocalCacheData + let request = URLRequest(url: url, cachePolicy: requestCachePolicy, timeoutInterval: session.configuration.timeoutIntervalForRequest) + + let task = session.dataTask(with: request) { [weak self] (data, response, error) in + + guard let `self` = self else { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil) + completion(.failure(.init(error))) + return + } + + if let error = error as NSError? { + + #if DEBUG + DebugPrint.print( + """ + + --------------------------------------------- + Cannot Fetch Image From: + URL: \(url.absoluteString) + Error: \(error) + --------------------------------------------- + + """ + , shouldPrint: self.debugPrint) + #endif + + DispatchQueue.main.async { + completion(.failure(.init(error))) + } + return + } + + let result = self.validateImageResponse(url: url, response: response, data: data) + + switch result { + case .success(let value): + DispatchQueue.main.async { + completion(.success(value.image)) + } + + case .failure(let networkError): + + #if DEBUG + DebugPrint.print( + """ + + --------------------------------------------- + Cannot Fetch Image From: + URL: \(url.absoluteString) + Error: \(networkError.localizedDescription) + --------------------------------------------- + + """ + , shouldPrint: self.debugPrint) + #endif + + DispatchQueue.main.async { + completion(.failure(networkError)) + } + } + } + + task.resume() + return task + } +} + +private extension ImageSessionManager { + + /// Validates URL Request's HTTP Response. + /// - Parameter response: HTTP URL Response for the provided request. + /// - Parameter data: Response Data containing Image Data sent by server. + /// - Returns: Result Containing Image on success or URL Error if validation fails. + private func validateImageResponse(url: URL, response: URLResponse?, data: Data?) -> Result { + + guard let httpURLResponse = response as? HTTPURLResponse, acceptableStatusCodes.contains(httpURLResponse.statusCode), + let mimeType = httpURLResponse.mimeType, mimeType.hasPrefix("image") else { + return .failure(.badServerResponse(for: url)) + } + + guard var data = data, !data.isEmpty else { + return .failure(.zeroByteResource(for: url)) + } + + guard let image = getImage(from: &data) else { + return .failure(.cannotDecodeRawData(for: url)) + } + + return .success((httpURLResponse, data, image)) + } + + /// Initializes and returns the image object with the specified data and scale factor. + /// - Parameter data: The data object containing the image data. + /// - Returns: An initialized UIImage object, or nil if the method could not initialize the image from the specified data. + private func getImage(from data: inout Data) -> ImageType? { + return ImageType.initialize(using: &data) + } +} diff --git a/Sources/NetworkKit/Networking/Image Session Manager/ImageSession+ImageType.swift b/Sources/NetworkKit/Networking/Image Session Manager/ImageSession+ImageType.swift new file mode 100644 index 0000000..ba15a47 --- /dev/null +++ b/Sources/NetworkKit/Networking/Image Session Manager/ImageSession+ImageType.swift @@ -0,0 +1,30 @@ +// +// ImageSession+AppKit.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +#if canImport(AppKit) +import AppKit.NSImage + +public extension ImageSessionManager { + typealias ImageType = NSImage +} + +#elseif canImport(WatchKit) +import UIKit.UIImage + +public extension ImageSessionManager { + typealias ImageType = UIImage +} + +#elseif canImport(UIKit) +import UIKit.UIImage + +public extension ImageSessionManager { + typealias ImageType = UIImage +} + +#endif diff --git a/Sources/NetworkKit/Networking/Session Manager.swift b/Sources/NetworkKit/Networking/Session Manager.swift new file mode 100644 index 0000000..0cf9c6d --- /dev/null +++ b/Sources/NetworkKit/Networking/Session Manager.swift @@ -0,0 +1,29 @@ +// +// APIManager.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +// MARK: - Session Manager +final class SessionManager: NetworkConfiguration { + + static let shared = SessionManager() + + private init() { + super.init(configuration: NetworkConfiguration.defaultConfiguration) + + #if DEBUG + if let environmentValue = UserDefaults.standard.value(forKey: "api_environment") as? String, !environmentValue.isEmpty { + Environment.current = Environment(value: environmentValue) + } else { + Environment.current = .none + } + #else + Environment.current = .none + #endif + } +} diff --git a/Sources/NetworkKit/Operations/Asynchronous Operation.swift b/Sources/NetworkKit/Operations/Asynchronous Operation.swift new file mode 100644 index 0000000..908b9e8 --- /dev/null +++ b/Sources/NetworkKit/Operations/Asynchronous Operation.swift @@ -0,0 +1,54 @@ +// +// Asynchronous Operation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +class AsynchronousOperation: Operation { + override var isAsynchronous: Bool { + return true + } + + private var _isFinished: Bool = false + override var isFinished: Bool { + get { + return _isFinished + } set { + willChangeValue(forKey: "isFinished") + _isFinished = newValue + didChangeValue(forKey: "isFinished") + } + } + + private var _isExecuting: Bool = false + override var isExecuting: Bool { + get { + return _isExecuting + } set { + willChangeValue(forKey: "isExecuting") + _isExecuting = newValue + didChangeValue(forKey: "isExecuting") + } + } + + override func start() { + guard !isCancelled else { + return + } + isExecuting = true + main() + } + + override func main() { + fatalError("Implement in sublcass to perform task") + } + + func finish() { + isExecuting = false + isFinished = true + } +} diff --git a/Sources/NetworkKit/Operations/Debouce Operation.swift b/Sources/NetworkKit/Operations/Debouce Operation.swift new file mode 100644 index 0000000..df7cb4c --- /dev/null +++ b/Sources/NetworkKit/Operations/Debouce Operation.swift @@ -0,0 +1,26 @@ +// +// Debounce Operation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +class DebounceOperation: AsynchronousOperation { + + let time: DispatchTime + + init(time: DispatchTime) { + self.time = time + super.init() + queuePriority = .veryHigh + } + + override func main() { + DispatchQueue.global(qos: .utility).asyncAfter(deadline: time) { [weak self] in + self?.finish() + } + } +} diff --git a/Sources/NetworkKit/Operations/Fetch Operation.swift b/Sources/NetworkKit/Operations/Fetch Operation.swift new file mode 100644 index 0000000..85d2c7b --- /dev/null +++ b/Sources/NetworkKit/Operations/Fetch Operation.swift @@ -0,0 +1,47 @@ +// +// Fetch Operation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +class FetchOperation: AsynchronousOperation { + + private let result: NetworkResult? + private let request: URLRequest? + + var task: URLSessionDataTask? + + init(request: URLRequest?, result: NetworkResult?) { + self.request = request + self.result = result + super.init() + + queuePriority = .high + } + + override func main() { + let session = SessionManager.shared.session + + guard let request = request else { + result?.result = .failure(NKError.unsupportedURL(for: nil)) + finish() + return + } + + task = session.dataTask(with: request) { [weak self] (data, response, error) in + if let error = error as NSError? { + self?.result?.result = .failure(NKError(error)) + } else if let response = response as? HTTPURLResponse, let data = data { + self?.result?.result = .success((data, response)) + } + + self?.finish() + } + + task?.resume() + } +} diff --git a/Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift b/Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift new file mode 100644 index 0000000..b46b9ce --- /dev/null +++ b/Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift @@ -0,0 +1,13 @@ +// +// Cancellable.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public protocol NetworkCancellable { + func cancel() +} diff --git a/Sources/NetworkKit/Protocols/ImageDownloadDelegate.swift b/Sources/NetworkKit/Protocols/ImageDownloadDelegate.swift new file mode 100644 index 0000000..e5009ef --- /dev/null +++ b/Sources/NetworkKit/Protocols/ImageDownloadDelegate.swift @@ -0,0 +1,113 @@ +// +// ImageDownloadDelegate.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// +import Foundation + +public protocol ImageDownloadDelegate: class { + + typealias ImageType = ImageSessionManager.ImageType + + var image: ImageType? { get set } + + @discardableResult + func fetch(from url: URL, setImageAutomatically flag: Bool, placeholder: ImageType?, completion: ((ImageType?) -> ())?) -> URLSessionDataTask + + @discardableResult + func fetch(fromUrlString urlString: String?, setImageAutomatically flag: Bool, placeholder: ImageType?, completion: ((ImageType?) -> ())?) -> URLSessionDataTask? + + func prepareForReuse(_ placeholder: ImageType?) +} + +public extension ImageDownloadDelegate { + + + /// Fetches Image from provided URL String and sets it on this UIImageView. + /// - Parameter urlString: URL String from where image has to be fetched. + /// - Parameter flag: Bool to set image automatically after downloading. Default value is `true`. + /// - Parameter placeholder: Place holder image to be displayed until image is downloaded. Default value is `nil`. + /// - Parameter completion: Completion Block which provides image if downloaded successfully. + /// - Returns: URLSessionDataTask if URL is correctly formed else returns `nil`. + @discardableResult + func fetch(from url: URL, setImageAutomatically flag: Bool, placeholder: ImageType?, completion: ((ImageType?) -> ())?) -> URLSessionDataTask { + _fetch(from: url, setImageAutomatically: flag, placeholder: placeholder, completion: completion) + } + + + /// Fetches Image from provided URL and sets it on this UIImageView. + /// - Parameter url: URL from where image has to be fetched. + /// - Parameter flag: Bool to set image automatically after downloading. Default value is `true`. + /// - Parameter placeholder: Place holder image to be displayed until image is downloaded. Default value is `nil`. + /// - Parameter completion: Completion Block which provides image if downloaded successfully. + /// - Returns: URLSessionDataTask. + @discardableResult + func fetch(fromUrlString urlString: String?, setImageAutomatically flag: Bool, placeholder: ImageType?, completion: ((ImageType?) -> ())?) -> URLSessionDataTask? { + _fetch(fromUrlString: urlString, setImageAutomatically: flag, placeholder: placeholder, completion: completion) + } +} + + +private extension ImageDownloadDelegate { + + @inline(__always) + func _fetch(fromUrlString urlString: String?, + setImageAutomatically flag: Bool = true, placeholder: ImageType? = nil, + completion: ((ImageType?) -> ())? = nil) -> URLSessionDataTask? { + + if let placeholder = placeholder { + image = placeholder + } + + guard let urlStringValue = urlString, let url = URL(string: urlStringValue) else { + #if DEBUG + print(""" + + --------------------------------------------- + Cannot Fetch Image From: + URL: \(String(describing: urlString)) + Error: \(URLError(.badURL).localizedDescription) + --------------------------------------------- + + """) + #endif + + if flag { + image = nil + } + + completion?(nil) + return nil + } + + return _fetch(from: url, setImageAutomatically: flag, completion: completion) + } + + @inline(__always) + func _fetch(from url: URL, + setImageAutomatically flag: Bool = true, placeholder: ImageType? = nil, + completion: ((ImageType?) -> ())? = nil) -> URLSessionDataTask { + + if let placeholder = placeholder { + image = placeholder + } + + return ImageSessionManager.shared.fetch(from: url) { [weak self] (result) in + switch result { + case .success(let newImage): + if flag { + self?.image = newImage + } + completion?(newImage) + + case .failure: + if flag { + self?.image = nil + } + completion?(nil) + } + } + } +} diff --git a/Sources/NetworkKit/Protocols/Network Decoder.swift b/Sources/NetworkKit/Protocols/Network Decoder.swift new file mode 100644 index 0000000..36d4301 --- /dev/null +++ b/Sources/NetworkKit/Protocols/Network Decoder.swift @@ -0,0 +1,16 @@ +// +// Network Decoder.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public protocol NetworkDecoder { + + associatedtype Input + + func decode(_ type: T.Type, from: Self.Input) throws -> T +} diff --git a/Sources/NetworkKit/Protocols/Publisher/Network Publisher.swift b/Sources/NetworkKit/Protocols/Publisher/Network Publisher.swift new file mode 100644 index 0000000..64748bf --- /dev/null +++ b/Sources/NetworkKit/Protocols/Publisher/Network Publisher.swift @@ -0,0 +1,24 @@ +// +// Network Publisher.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public protocol NetworkPublisher { + + /// The kind of values published by this publisher. + associatedtype Output + + /// The kind of errors this publisher might publish. + /// + /// Use `Never` if this `Publisher` does not publish errors. + associatedtype Failure: NetworkError + + var result: NetworkResult { get } + + var queue: NetworkQueue { get } +} diff --git a/Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift new file mode 100644 index 0000000..e444cd6 --- /dev/null +++ b/Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift @@ -0,0 +1,164 @@ +// +// Publisher+Methods.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NetworkPublisher { + + /// Decodes the output from upstream using a specified `NetworkDecoder`. + /// For example, use `JSONDecoder`. + /// - Parameter type: Type to decode into. + /// - Parameter decoder: `NetworkDecoder` for decoding output. + func decode(type: Item.Type, decoder: Decoder) -> NetworkPublishers.Decode { + NetworkPublishers.Decode(upstream: self, decoder: decoder) + } + + /// Decodes the output from upstream using a specified `JSONDecoder`. + /// - Parameter type: Type to decode into. + /// - Parameter jsonKeyDecodingStrategy: JSON Key Decoding Strategy. + func decode(type: Item.Type, jsonKeyDecodingStrategy: JSONDecoder.KeyDecodingStrategy) -> NetworkPublishers.Decode { + NetworkPublishers.Decode(upstream: self, jsonKeyDecodingStrategy: jsonKeyDecodingStrategy) + } + + /// Transforms all elements from the upstream publisher with a provided closure. + /// + /// - Parameter transform: A closure that takes one element as its parameter and returns a new element. + /// - Returns: A publisher that uses the provided closure to map elements from the upstream publisher to new elements that it then publishes. + func map(_ transform: @escaping (Output) -> T) -> NetworkPublishers.Map { + NetworkPublishers.Map(upstream: self, transform: transform) + } + + /// Transforms all elements from the upstream publisher with a provided error-throwing closure. + /// + /// If the `transform` closure throws an error, the publisher fails with the thrown error. + /// - Parameter transform: A closure that takes one element as its parameter and returns a new element. + /// - Returns: A publisher that uses the provided closure to map elements from the upstream publisher to new elements that it then publishes. + func tryMap(_ transform: @escaping (Output) throws -> T) -> NetworkPublishers.TryMap { + NetworkPublishers.TryMap(upstream: self, transform: transform) + } + + /// Replaces any errors in the stream with the provided element. + /// + /// If the upstream publisher fails with an error, this publisher emits the provided element, then finishes normally. + /// - Parameter output: An element to emit when the upstream publisher fails. + /// - Returns: A publisher that replaces an error from the upstream publisher with the provided output element. + func replaceError(with output: Output) -> NetworkPublishers.ReplaceError { + NetworkPublishers.ReplaceError(upstream: self, output: output) + } + + /// Publishes elements only after a specified time interval elapses between events. + /// + /// Use this operator when you want to wait for a pause in the delivery of events from the upstream publisher. For example, call `debounce` on the publisher from a text field to only receive elements when the user pauses or stops typing. When they start typing again, the `debounce` holds event delivery until the next pause. + /// - Parameters: + /// - dueTime: The time the publisher should wait before publishing an element. + /// - scheduler: The scheduler on which this publisher delivers elements + /// - options: Scheduler options that customize this publisher’s delivery of elements. + /// - Returns: A publisher that publishes events only after a specified time elapses. + func debounce(_ time: DispatchTimeInterval) -> NetworkPublishers.Debounce { + NetworkPublishers.Debounce(upstream: self, time: .now() + time) + } + + /// Publishes elements only after a specified time interval elapses between events. + /// + /// Use this operator when you want to wait for a pause in the delivery of events from the upstream publisher. For example, call `debounce` on the publisher from a text field to only receive elements when the user pauses or stops typing. When they start typing again, the `debounce` holds event delivery until the next pause. + /// - Parameters: + /// - dueTime: The time the publisher should wait before publishing an element. + /// - scheduler: The scheduler on which this publisher delivers elements + /// - options: Scheduler options that customize this publisher’s delivery of elements. + /// - Returns: A publisher that publishes events only after a specified time elapses. + func debounce(_ time: DispatchTime) -> NetworkPublishers.Debounce { + NetworkPublishers.Debounce(upstream: self, time: time) + } + + /// Returns a publisher that publishes the value of a key path. + /// + /// - Parameter keyPath: The key path of a property on `Output` + /// - Returns: A publisher that publishes the value of the key path. + func map(_ keyPath: KeyPath) -> NetworkPublishers.MapKeyPath { + NetworkPublishers.MapKeyPath(upstream: self, keyPath: keyPath) + } + + /// Returns a publisher that publishes the values of two key paths as a tuple. + /// + /// - Parameters: + /// - keyPath0: The key path of a property on `Output` + /// - keyPath1: The key path of another property on `Output` + /// - Returns: A publisher that publishes the values of two key paths as a tuple. + func map(_ keyPath0: KeyPath, _ keyPath1: KeyPath) -> NetworkPublishers.MapKeyPath2 { + NetworkPublishers.MapKeyPath2(upstream: self, keyPath0: keyPath0, keyPath1: keyPath1) + } + + /// Handles errors from an upstream publisher by replacing it with another publisher. + /// + /// The following example replaces any error from the upstream publisher and replaces the upstream with a `Just` publisher. This continues the stream by publishing a single value and completing normally. + /// ``` + /// enum SimpleError: Error { case error } + /// let errorPublisher = (0..<10).publisher.tryMap { v -> Int in + /// if v < 5 { + /// return v + /// } else { + /// throw SimpleError.error + /// } + /// } + /// + /// let noErrorPublisher = errorPublisher.catch { _ in + /// return Just(100) + /// } + /// ``` + /// Backpressure note: This publisher passes through `request` and `cancel` to the upstream. After receiving an error, the publisher sends sends any unfulfilled demand to the new `Publisher`. + /// - Parameter handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. + /// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. + func `catch`(_ handler: @escaping (Self.Failure) -> P) -> NetworkPublishers.Catch where Self.Output == P.Output { + NetworkPublishers.Catch(upstream: self, handler: handler) + } + + /// Handles errors from an upstream publisher by either replacing it with another publisher or `throw`ing a new error. + /// + /// - Parameter handler: A `throw`ing closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher or if an error is thrown will send the error downstream. + /// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. + func tryCatch(_ handler: @escaping (Self.Failure) throws -> P) -> NetworkPublishers.TryCatch where Self.Output == P.Output { + NetworkPublishers.TryCatch(upstream: self, handler: handler) + } +} + +public extension NetworkPublisher where Self.Output == NetworkKit.Output, Self.Failure == NetworkKit.Failure { + + /// <#Description#> + /// - Parameter acceptableStatusCodes: <#acceptableStatusCodes description#> + /// - Parameter checkForErrorModel: <#checkForErrorModel description#> + /// - Returns: <#checkForErrorModel description#> + func validate(acceptableStatusCodes: [Int] = [], checkForErrorModel: Bool = true) -> NetworkPublishers.Validate { + NetworkPublishers.Validate(upstream: self, shouldCheckForErrorModel: checkForErrorModel, acceptableStatusCodes: acceptableStatusCodes) + } +} + +public extension NetworkPublisher { + + /// Attaches a subscriber with closure-based behavior. + /// + /// This method creates the subscriber and immediately requests an unlimited number of values, prior to returning the subscriber. + /// - parameter receiveComplete: The closure to execute on completion. + /// - parameter receiveValue: The closure to execute on receipt of a value. + /// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. + func completion(_ block: @escaping (Result) -> Void) -> NetworkCancellable { + NetworkPublishers.Completion(upstream: self, block: block) + } +} + +public extension NetworkPublisher where Self.Failure == Never { + + /// Assigns each element from a Publisher to a property on an object. + /// + /// - Parameters: + /// - keyPath: The key path of the property to assign. + /// - object: The object on which to assign the value. + /// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. + func assign(to keyPath: ReferenceWritableKeyPath, on root: Root) -> NetworkCancellable { + NetworkPublishers.Assign(upstream: self, to: keyPath, on: root) + } +} diff --git a/Sources/NetworkKit/Protocols/Publisher/Publisher+Queue.swift b/Sources/NetworkKit/Protocols/Publisher/Publisher+Queue.swift new file mode 100644 index 0000000..dfa1af9 --- /dev/null +++ b/Sources/NetworkKit/Protocols/Publisher/Publisher+Queue.swift @@ -0,0 +1,31 @@ +// +// Publisher+Queue.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension NetworkPublisher { + + func addToQueue(isSuspended: Bool = false, _ block: @escaping () -> Void) { + let op = BlockOperation(block: block) + result.operation = op + queue.addOperation(op) + queue(isSuspended: isSuspended) + } + + func addToQueue(isSuspended: Bool = false, _ op: Operation) { + result.operation = op + queue.addOperation(op) + queue(isSuspended: isSuspended) + } + + func queue(isSuspended: Bool) { + if queue.operationQueue.isSuspended != isSuspended { + queue.operationQueue.isSuspended = isSuspended + } + } +} diff --git a/Sources/NetworkKit/Request/Network Request.swift b/Sources/NetworkKit/Request/Network Request.swift new file mode 100644 index 0000000..230b8f5 --- /dev/null +++ b/Sources/NetworkKit/Request/Network Request.swift @@ -0,0 +1,129 @@ +// +// NetworkRequest.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public final class NetworkRequest { + private(set) var request: URLRequest? + + let apiName: String + + init(to endPoint: ConnectionRepresentable) { + request = CreateRequest(with: endPoint, query: nil)?.request + apiName = endPoint.name ?? "nil" + } + + public func urlQuery(_ query: URLQuery) -> NetworkRequest { + guard let url = request?.url?.absoluteURL, + var components = URLComponents(string: url.absoluteString) else { + return self + } + + var items = components.queryItems ?? [] + items.addURLQuery(query: query) + components.queryItems = items + + self.request?.url = components.url + + #if DEBUG + DebugPrint.print( + """ + Request Dynamic URLQuery: + \(items.toDictionary.prettyPrint) + + --------------------------------------------- + """ + ) + #endif + return self + } + + public func requestBody(_ body: T, strategy: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys) -> Self { + var data: Data? = nil + + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = strategy + encoder.outputFormatting = .prettyPrinted + + do { + let bodyData = try encoder.encode(body) + data = bodyData + + #if DEBUG + DebugPrint.print( + """ + Request Body: + \(String(data: bodyData, encoding: .utf8) ?? "nil") + + --------------------------------------------- + """ + ) + #endif + } catch { + #if DEBUG + DebugPrint.print( + """ + Request Body: nil + Error Encoding: - + \(error) + + --------------------------------------------- + """ + ) + #endif + + data = nil + } + + request?.httpBody = data + + var headers = request?.allHTTPHeaderFields ?? [:] + headers = headers.merging(HTTPBodyEncodingType.json.headers) { (_, new) in new } + request?.allHTTPHeaderFields = headers + + return self + } + + public func requestBody(_ body: [String: Any], encoding: HTTPBodyEncodingType) -> Self { + #if DEBUG + DebugPrint.print(""" + Request Body: + \(body.prettyPrint) + + --------------------------------------------- + """) + #endif + + request?.httpBody = encoding.encode(body: body) + + var headers = request?.allHTTPHeaderFields ?? [:] + headers = headers.merging(encoding.headers) { (_, new) in new } + request?.allHTTPHeaderFields = headers + + return self + } + + public func requestBody(_ body: Data?, httpHeaders: HTTPHeaderParameters) -> Self { + request?.httpBody = body + + var headers = request?.allHTTPHeaderFields ?? [:] + headers = headers.merging(httpHeaders) { (_, new) in new } + request?.allHTTPHeaderFields = headers + + return self + } + + public func httpHeaders(_ httpHeaders: HTTPHeaderParameters) -> Self { + var headers = request?.allHTTPHeaderFields ?? [:] + headers = headers.merging(httpHeaders) { (_, new) in new } + request?.allHTTPHeaderFields = headers + + return self + } +} + diff --git a/Sources/NetworkKit/Resources/HTTP Body Encoding.swift b/Sources/NetworkKit/Resources/HTTP Body Encoding.swift new file mode 100644 index 0000000..9bc30c6 --- /dev/null +++ b/Sources/NetworkKit/Resources/HTTP Body Encoding.swift @@ -0,0 +1,164 @@ +// +// HTTP Body Encoding.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public enum HTTPBodyEncodingType { + case formURLEncoded + case json + + var headers: HTTPHeaderParameters { + switch self { + case .formURLEncoded: + return ["Content-Type": "application/x-www-form-urlencoded"] + case .json: + return ["Content-Type": "application/json"] + } + } + + func encode(body: [String: Any]) -> Data? { + switch self { + case .formURLEncoded: + if let bodyData = query(body).data(using: .utf8, allowLossyConversion: false) { + + #if DEBUG + print(""" + Request Body: + \(String(data: bodyData, encoding: .utf8) ?? "nil") + --------------------------------------------- + + """) + #endif + + return bodyData + + } else { + #if DEBUG + print(""" + Request Body: nil + Error Encoding: - Unknown + --------------------------------------------- + + """) + #endif + + return nil + } + + + case .json: + do { + let bodyData = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted) + + #if DEBUG + print(""" + Request Body: + \(String(data: bodyData, encoding: .utf8) ?? "nil") + --------------------------------------------- + + """) + #endif + + return bodyData + + } catch { + #if DEBUG + print(""" + Request Body: nil + Error Encoding: - + \(error) + --------------------------------------------- + + """) + #endif + + return nil + } + } + } +} + +private extension HTTPBodyEncodingType { + func query(_ parameters: [String: Any]) -> String { + var components: [(String, String)] = [] + + for key in parameters.keys.sorted(by: <) { + let value = parameters[key]! + components += queryComponents(fromKey: key, value: value) + } + return components.map { "\($0)=\($1)" }.joined(separator: "&") + } + + func queryComponents(fromKey key: String, + value: Any, arrayEncoding: ArrayEncoding = .brackets, + boolEncoding: BoolEncoding = .numeric) -> [(String, String)] { + + var components: [(String, String)] = [] + + if let dictionary = value as? [String: Any] { + for (nestedKey, value) in dictionary { + components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value) + } + } else if let array = value as? [Any] { + for value in array { + components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value) + } + } else if let value = value as? NSNumber { + if value.boolValue { + components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue)))) + } else { + components.append((escape(key), escape("\(value)"))) + } + } else if let bool = value as? Bool { + components.append((escape(key), escape(boolEncoding.encode(value: bool)))) + } else { + components.append((escape(key), escape("\(value)"))) + } + + return components + } + + func escape(_ string: String) -> String { + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 + let subDelimitersToEncode = "!$&'()*+,;=" + + var allowedCharacterSet = CharacterSet.urlQueryAllowed + allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") + + return string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? "" + } +} + + +enum BoolEncoding { + case numeric + case literal + + func encode(value: Bool) -> String { + switch self { + case .numeric: + return value ? "1" : "0" + case .literal: + return value ? "true" : "false" + } + } +} + +enum ArrayEncoding { + case brackets + case noBrackets + + func encode(key: String) -> String { + switch self { + case .brackets: + return "\(key)[]" + case .noBrackets: + return key + } + } +} diff --git a/Sources/NetworkKit/Resources/HTTP Method.swift b/Sources/NetworkKit/Resources/HTTP Method.swift new file mode 100644 index 0000000..c169139 --- /dev/null +++ b/Sources/NetworkKit/Resources/HTTP Method.swift @@ -0,0 +1,21 @@ +// +// HTTP Method.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public enum HTTPMethod: String { + case connect = "CONNECT" + case delete = "DELETE" + case get = "GET" + case head = "HEAD" + case options = "OPTIONS" + case post = "POST" + case put = "PUT" + case patch = "PATCH" + case trace = "TRACE" +} diff --git a/Sources/NetworkKit/Resources/Scheme.swift b/Sources/NetworkKit/Resources/Scheme.swift new file mode 100644 index 0000000..1bed048 --- /dev/null +++ b/Sources/NetworkKit/Resources/Scheme.swift @@ -0,0 +1,14 @@ +// +// Scheme.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public enum Scheme: String { + case http + case https +} diff --git a/Sources/NetworkKit/Server/API Representable.swift b/Sources/NetworkKit/Server/API Representable.swift new file mode 100644 index 0000000..24d09b0 --- /dev/null +++ b/Sources/NetworkKit/Server/API Representable.swift @@ -0,0 +1,19 @@ +// +// API Representable.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +//MARK:- API Type +public protocol APIRepresentable { + + /// Sub URL for API Type. It may include server environment for the api, it can be `current` environment. + var subUrl: String { get } + + /// EndPoint for API Type. + var endPoint: String { get } +} diff --git a/Sources/NetworkKit/Server/Environment.swift b/Sources/NetworkKit/Server/Environment.swift new file mode 100644 index 0000000..79d8d7a --- /dev/null +++ b/Sources/NetworkKit/Server/Environment.swift @@ -0,0 +1,31 @@ +// +// Environment.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +/// Server Environment. +public struct Environment: Hashable, Equatable { + + public typealias RawValue = String + + public var value: String + + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + } + + public init(value: RawValue) { + self.value = value + } + + static var current: Environment = .none + + public static var none = Environment(value: "") + public static var staging = Environment(value: "staging") + public static var dev = Environment(value: "dev") +} diff --git a/Sources/NetworkKit/Server/Host Representable.swift b/Sources/NetworkKit/Server/Host Representable.swift new file mode 100644 index 0000000..64c6043 --- /dev/null +++ b/Sources/NetworkKit/Server/Host Representable.swift @@ -0,0 +1,24 @@ +// +// Host Representable.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +/// Host URLs for the app. +public protocol HostRepresentable { + + var host: String { get } + + /// Default Headers attached to every request with this host. + var defaultHeaders: HTTPHeaderParameters { get } + + /// Default URL Query for particular host. + var defaultUrlQuery: URLQuery? { get } + + /// Default API Type for particular host. + var defaultAPIVersion: APIRepresentable? { get } +} diff --git a/Tests/NetworkKitTests/NetworkKitTests.swift b/Tests/NetworkKitTests/NetworkKitTests.swift index 2b91138..5ac50b6 100644 --- a/Tests/NetworkKitTests/NetworkKitTests.swift +++ b/Tests/NetworkKitTests/NetworkKitTests.swift @@ -1,15 +1,140 @@ import XCTest +import Combine @testable import NetworkKit +enum APIVersion: String, APIRepresentable { + case v1 = "5da1e9ae76c28f0014bbe25f" + + var subUrl: String { + return "\(rawValue)" + } + + var endPoint: String { + switch self { + case .v1: return "" + } + } +} + +enum Host: String, HostRepresentable { + + case server = "mockapi.io" + + var host: String { rawValue } + + var defaultHeaders: HTTPHeaderParameters { [:] } + + var defaultAPIVersion: APIRepresentable? { APIVersion.v1 } + + var defaultUrlQuery: URLQuery? { nil } +} + +extension Environment { + static let production = Environment(value: "") +} + +enum MockPoint: ConnectionRepresentable { + + case allUsers + + var path: String { "/users" } + + var name: String? { "Users" } + + var method: HTTPMethod { .get } + + var httpHeaders: HTTPHeaderParameters { [:] } + + var host: HostRepresentable { Host.server } + + var defaultQuery: URLQuery? { nil } + +} + +enum MockPointError: ConnectionRepresentable { + + case allUsers + + var path: String { "/users1" } + + var name: String? { "Users" } + + var method: HTTPMethod { .get } + + var httpHeaders: HTTPHeaderParameters { [:] } +// + var host: HostRepresentable { Host.server } + + var defaultQuery: URLQuery? { nil } + +} + +struct User: Codable, Equatable { + let id, createdAt, name: String? + let avatar: String? +} + +typealias Users = [User] + final class NetworkKitTests: XCTestCase { + + var users: Users = [] { + willSet { +// print("Setting new Value\n\n\n\(newValue)") +// expecatation.fulfill() + } + } + + var cancellable: NetworkCancellable? + + @available(OSX 10.15, *) + func testURLSession() { + _ = URLSession.shared.dataTaskPublisher(for: URL(string: "")!) + } + func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(NetworkKit().text, "Hello, World!") + let expecatation = XCTestExpectation() + + cancellable = NetworkKit { + NetworkRequest(to: MockPointError.allUsers) + } + .validate() + .tryCatch { (error) in + NetworkKit { + NetworkRequest(to: MockPoint.allUsers) + } + } + .map(\.data) + .decode(type: Users.self, decoder: JSONDecoder()) + .completion { (_) in + expecatation.fulfill() + } + + + + +// .completion { (result) in +// switch result { +// case .success: +// XCTAssert(true) +// +// case .failure: +// XCTAssert(false) +// +// } +// expecatation.fulfill() +// } + + wait(for: [expecatation], timeout: 60) } - + + func testPerformanceExample() { + measure { + testExample() + } + } + static var allTests = [ - ("testExample", testExample), + ("testExample", testExample, "testPerformanceExample", testPerformanceExample), ] } From 7dd1ddb12d952bbdb6b1bad2b1a9d1d813a02783 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Wed, 20 Nov 2019 18:59:47 +0530 Subject: [PATCH 02/25] Renamed APIVersion -> APIType Fix MapKeyPath and MapKeyPath2 Fix Network Configuration - `timeoutIntervalForRequest`, ` timeoutIntervalForResource` value and `waitsForConnectivity` value Updated Tests --- .../Connection/Connection Representable.swift | 6 +- .../Create Request/Create Request.swift | 4 +- .../Kit/Publishers/Map/Map Keypath 2.swift | 1 + .../Kit/Publishers/Map/Map Keypath.swift | 1 + .../Configuration/Network Configuration.swift | 6 +- Sources/NetworkKit/Server/Environment.swift | 6 +- .../Server/Host Representable.swift | 2 +- Tests/NetworkKitTests/NetworkKitTests.swift | 88 +++++++++++-------- 8 files changed, 65 insertions(+), 49 deletions(-) diff --git a/Sources/NetworkKit/Connection/Connection Representable.swift b/Sources/NetworkKit/Connection/Connection Representable.swift index 99f7b63..3dbd70e 100644 --- a/Sources/NetworkKit/Connection/Connection Representable.swift +++ b/Sources/NetworkKit/Connection/Connection Representable.swift @@ -10,17 +10,17 @@ import Foundation public protocol ConnectionRepresentable { var path: String { get } - var name: String? { get } + var name: String? { get } // for console logging purposes only var method: HTTPMethod { get } var httpHeaders: HTTPHeaderParameters { get } var scheme: Scheme { get } var host: HostRepresentable { get } var defaultQuery: URLQuery? { get } - var apiVersion: APIRepresentable? { get } + var apiType: APIRepresentable? { get } } public extension ConnectionRepresentable { var scheme: Scheme { .https } - var apiVersion: APIRepresentable? { host.defaultAPIVersion } + var apiType: APIRepresentable? { host.defaultAPIType } } diff --git a/Sources/NetworkKit/Create Request/Create Request.swift b/Sources/NetworkKit/Create Request/Create Request.swift index fc1c5c9..a0ddb98 100644 --- a/Sources/NetworkKit/Create Request/Create Request.swift +++ b/Sources/NetworkKit/Create Request/Create Request.swift @@ -19,8 +19,8 @@ final class CreateRequest { var components = URLComponents() components.scheme = connection.scheme.rawValue - let subURL = connection.apiVersion?.subUrl ?? "" - let endPoint = connection.apiVersion?.endPoint ?? "" + let subURL = connection.apiType?.subUrl ?? "" + let endPoint = connection.apiType?.endPoint ?? "" components.host = (subURL.isEmpty ? subURL : subURL + ".") + connection.host.host components.path = endPoint + connection.path diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift index 2de5fe7..1422491 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift @@ -38,6 +38,7 @@ extension NetworkPublishers { self.keyPath1 = keyPath1 result = .init() + perform() } private func perform() { diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift index 1b7b6d2..8caac43 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift @@ -31,6 +31,7 @@ public extension NetworkPublishers { self.upstream = upstream self.keyPath = keyPath result = .init() + perform() } private func perform() { diff --git a/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift b/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift index e07ff23..542794a 100644 --- a/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift +++ b/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift @@ -40,11 +40,11 @@ open class NetworkConfiguration { configuration.requestCachePolicy = .useProtocolCachePolicy if #available(iOS 11.0, watchOS 4.0, tvOS 11.0, macOS 10.13, *) { - configuration.waitsForConnectivity = false + configuration.waitsForConnectivity = true } configuration.networkServiceType = .responsiveData - configuration.timeoutIntervalForRequest = TimeInterval(integerLiteral: 0) - configuration.timeoutIntervalForResource = TimeInterval(integerLiteral: 0) + configuration.timeoutIntervalForRequest = TimeInterval(integerLiteral: 20) + configuration.timeoutIntervalForResource = TimeInterval(integerLiteral: 20) return configuration }() diff --git a/Sources/NetworkKit/Server/Environment.swift b/Sources/NetworkKit/Server/Environment.swift index 79d8d7a..de87b36 100644 --- a/Sources/NetworkKit/Server/Environment.swift +++ b/Sources/NetworkKit/Server/Environment.swift @@ -25,7 +25,7 @@ public struct Environment: Hashable, Equatable { static var current: Environment = .none - public static var none = Environment(value: "") - public static var staging = Environment(value: "staging") - public static var dev = Environment(value: "dev") + public static let none = Environment(value: "") + public static let staging = Environment(value: "staging") + public static let dev = Environment(value: "dev") } diff --git a/Sources/NetworkKit/Server/Host Representable.swift b/Sources/NetworkKit/Server/Host Representable.swift index 64c6043..4a82c33 100644 --- a/Sources/NetworkKit/Server/Host Representable.swift +++ b/Sources/NetworkKit/Server/Host Representable.swift @@ -20,5 +20,5 @@ public protocol HostRepresentable { var defaultUrlQuery: URLQuery? { get } /// Default API Type for particular host. - var defaultAPIVersion: APIRepresentable? { get } + var defaultAPIType: APIRepresentable? { get } } diff --git a/Tests/NetworkKitTests/NetworkKitTests.swift b/Tests/NetworkKitTests/NetworkKitTests.swift index 5ac50b6..db25c57 100644 --- a/Tests/NetworkKitTests/NetworkKitTests.swift +++ b/Tests/NetworkKitTests/NetworkKitTests.swift @@ -2,11 +2,11 @@ import XCTest import Combine @testable import NetworkKit -enum APIVersion: String, APIRepresentable { +enum APIType: String, APIRepresentable { case v1 = "5da1e9ae76c28f0014bbe25f" var subUrl: String { - return "\(rawValue)" + rawValue } var endPoint: String { @@ -24,7 +24,7 @@ enum Host: String, HostRepresentable { var defaultHeaders: HTTPHeaderParameters { [:] } - var defaultAPIVersion: APIRepresentable? { APIVersion.v1 } + var defaultAPIType: APIRepresentable? { APIType.v1 } var defaultUrlQuery: URLQuery? { nil } } @@ -78,52 +78,66 @@ typealias Users = [User] final class NetworkKitTests: XCTestCase { + +// var cancel: AnyCancellable? +// +// @available(OSX 10.15, *) +// func testURLSession() { +// cancel = URLSession.shared.dataTaskPublisher(for: URL(string: "")!) +// .catch { (error) -> URLSession.DataTaskPublisher in +// print(error == URLError.network) +// return URLSession.shared.dataTaskPublisher(for: URL(string: "11")!) +// } +// .map(\.data) +// .decode(type: Users.self, decoder: JSONDecoder()) +// .sink(receiveCompletion: { (error) in +// +// }, receiveValue: { (users) in +// print(users) +// }) +// } +// +// var cancellable: NetworkCancellable? +// +// func testExampleOld() { +// URLSession.shared.dataTask(with: URL(string: "high quality")!) { (data, response, error) in +// if let error = error { +// URLSession.shared.dataTask(with: URL(string: "low quality")!) { (data, response, error) in +// if let error = error { +// // fail +// } else if let data = data { +// // completion(UIImage(data: data) +// } +// } +// +// } else if let data = data { +// // completion(UIImage(data: data) +// } +// } +// .resume() +// } + + var users: Users = [] { willSet { -// print("Setting new Value\n\n\n\(newValue)") -// expecatation.fulfill() + print("Setting value: \(newValue)") + expecatation.fulfill() } } - var cancellable: NetworkCancellable? - - @available(OSX 10.15, *) - func testURLSession() { - _ = URLSession.shared.dataTaskPublisher(for: URL(string: "")!) - } + var cancellable: NetworkCancellable! + + let expecatation = XCTestExpectation() func testExample() { - let expecatation = XCTestExpectation() cancellable = NetworkKit { - NetworkRequest(to: MockPointError.allUsers) - } - .validate() - .tryCatch { (error) in - NetworkKit { - NetworkRequest(to: MockPoint.allUsers) - } + NetworkRequest(to: MockPoint.allUsers) } .map(\.data) .decode(type: Users.self, decoder: JSONDecoder()) - .completion { (_) in - expecatation.fulfill() - } - - - - -// .completion { (result) in -// switch result { -// case .success: -// XCTAssert(true) -// -// case .failure: -// XCTAssert(false) -// -// } -// expecatation.fulfill() -// } + .replaceError(with: [User(id: "12", createdAt: "Today", name: "Guest User", avatar: nil)]) + .assign(to: \.users, on: self) wait(for: [expecatation], timeout: 60) } From 0142861fea9512ea1671f7c10abc12c1288354b0 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Wed, 20 Nov 2019 22:59:45 +0530 Subject: [PATCH 03/25] Updated Validation Publisher with dynamic Error Model Renamed code -> errorCode in NetworkError --- Sources/NetworkKit/Errors/NKError.swift | 22 +++++++------------ .../Extensions/Never+Extension.swift | 2 +- .../Kit/Publishers/Debounce/Debounce.swift | 8 +++---- .../Networking/Session Manager.swift | 4 ++-- .../Operations/Debouce Operation.swift | 8 +++---- .../Publisher/Publisher+Methods.swift | 20 +++++++---------- 6 files changed, 27 insertions(+), 37 deletions(-) diff --git a/Sources/NetworkKit/Errors/NKError.swift b/Sources/NetworkKit/Errors/NKError.swift index 31b8d34..ceb8de9 100644 --- a/Sources/NetworkKit/Errors/NKError.swift +++ b/Sources/NetworkKit/Errors/NKError.swift @@ -9,24 +9,24 @@ import Foundation public protocol NetworkError: LocalizedError { - var code: Int { get } + var errorCode: Int { get } } public struct NKError: NetworkError { public let localizedDescription: String public let errorDescription: String? - public let code: Int + public let errorCode: Int init(_ httpError: HTTPStatusCode) { - code = httpError.rawValue + errorCode = httpError.rawValue localizedDescription = httpError.localizedDescription errorDescription = localizedDescription } public init(_ error: NSError) { localizedDescription = error.localizedDescription - code = error.code + errorCode = error.code if error.domain == NSCocoaErrorDomain { errorDescription = error.userInfo[NSDebugDescriptionErrorKey] as? String @@ -41,16 +41,10 @@ public struct NKError: NetworkError { } } - init(_ businessError: BusinessError) { - code = businessError.errorCode - localizedDescription = businessError.localizedDescription - errorDescription = localizedDescription - } - - init(_ nkError: NKError) { - code = nkError.code - localizedDescription = nkError.localizedDescription - errorDescription = localizedDescription + init(_ error: NetworkError) { + errorCode = error.errorCode + localizedDescription = error.localizedDescription + errorDescription = error.errorDescription } static func validationCancelled(for url: URL) -> NKError { diff --git a/Sources/NetworkKit/Extensions/Never+Extension.swift b/Sources/NetworkKit/Extensions/Never+Extension.swift index 3018558..0d801b8 100644 --- a/Sources/NetworkKit/Extensions/Never+Extension.swift +++ b/Sources/NetworkKit/Extensions/Never+Extension.swift @@ -10,5 +10,5 @@ import Foundation extension Never: NetworkError { - public var code: Int { -150716 } + public var errorCode: Int { -150716 } } diff --git a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift index 570b5a0..f3eb290 100644 --- a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift +++ b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift @@ -27,16 +27,16 @@ public extension NetworkPublishers { /// The publisher that this publisher receives elements from. public let upstream: Upstream - public let time: DispatchTime + public let dueTime: DispatchTime - public init(upstream: Upstream, time: DispatchTime) { + public init(upstream: Upstream, dueTime: DispatchTime) { self.upstream = upstream - self.time = time + self.dueTime = dueTime perform() } private func perform() { - let operation = DebounceOperation(time: time) + let operation = DebounceOperation(dueTime: dueTime) queue.operations.first?.addDependency(operation) addToQueue(operation) } diff --git a/Sources/NetworkKit/Networking/Session Manager.swift b/Sources/NetworkKit/Networking/Session Manager.swift index 0cf9c6d..fbb1927 100644 --- a/Sources/NetworkKit/Networking/Session Manager.swift +++ b/Sources/NetworkKit/Networking/Session Manager.swift @@ -9,9 +9,9 @@ import Foundation // MARK: - Session Manager -final class SessionManager: NetworkConfiguration { +final public class SessionManager: NetworkConfiguration { - static let shared = SessionManager() + public static let shared = SessionManager() private init() { super.init(configuration: NetworkConfiguration.defaultConfiguration) diff --git a/Sources/NetworkKit/Operations/Debouce Operation.swift b/Sources/NetworkKit/Operations/Debouce Operation.swift index df7cb4c..f3e39f6 100644 --- a/Sources/NetworkKit/Operations/Debouce Operation.swift +++ b/Sources/NetworkKit/Operations/Debouce Operation.swift @@ -10,16 +10,16 @@ import Foundation class DebounceOperation: AsynchronousOperation { - let time: DispatchTime + let dueTime: DispatchTime - init(time: DispatchTime) { - self.time = time + init(dueTime: DispatchTime) { + self.dueTime = dueTime super.init() queuePriority = .veryHigh } override func main() { - DispatchQueue.global(qos: .utility).asyncAfter(deadline: time) { [weak self] in + DispatchQueue.global(qos: .utility).asyncAfter(deadline: dueTime) { [weak self] in self?.finish() } } diff --git a/Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift index e444cd6..82dbf01 100644 --- a/Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift @@ -56,11 +56,9 @@ public extension NetworkPublisher { /// Use this operator when you want to wait for a pause in the delivery of events from the upstream publisher. For example, call `debounce` on the publisher from a text field to only receive elements when the user pauses or stops typing. When they start typing again, the `debounce` holds event delivery until the next pause. /// - Parameters: /// - dueTime: The time the publisher should wait before publishing an element. - /// - scheduler: The scheduler on which this publisher delivers elements - /// - options: Scheduler options that customize this publisher’s delivery of elements. /// - Returns: A publisher that publishes events only after a specified time elapses. - func debounce(_ time: DispatchTimeInterval) -> NetworkPublishers.Debounce { - NetworkPublishers.Debounce(upstream: self, time: .now() + time) + func debounce(_ dueTime: DispatchTimeInterval) -> NetworkPublishers.Debounce { + NetworkPublishers.Debounce(upstream: self, dueTime: .now() + dueTime) } /// Publishes elements only after a specified time interval elapses between events. @@ -69,10 +67,8 @@ public extension NetworkPublisher { /// - Parameters: /// - dueTime: The time the publisher should wait before publishing an element. /// - scheduler: The scheduler on which this publisher delivers elements - /// - options: Scheduler options that customize this publisher’s delivery of elements. - /// - Returns: A publisher that publishes events only after a specified time elapses. - func debounce(_ time: DispatchTime) -> NetworkPublishers.Debounce { - NetworkPublishers.Debounce(upstream: self, time: time) + func debounce(_ dueTime: DispatchTime) -> NetworkPublishers.Debounce { + NetworkPublishers.Debounce(upstream: self, dueTime: dueTime) } /// Returns a publisher that publishes the value of a key path. @@ -128,10 +124,10 @@ public extension NetworkPublisher { public extension NetworkPublisher where Self.Output == NetworkKit.Output, Self.Failure == NetworkKit.Failure { - /// <#Description#> - /// - Parameter acceptableStatusCodes: <#acceptableStatusCodes description#> - /// - Parameter checkForErrorModel: <#checkForErrorModel description#> - /// - Returns: <#checkForErrorModel description#> + /// Validates Network call response with provided acceptable status codes. + /// - Parameter acceptableStatusCodes: Acceptable HTTP Status codes for the network call. Default value is `Array(200 < 300)`. + /// - Parameter checkForErrorModel: If publisher should check for custom error model to decode. + /// - Returns: A publisher that validates response from an upstream publisher. func validate(acceptableStatusCodes: [Int] = [], checkForErrorModel: Bool = true) -> NetworkPublishers.Validate { NetworkPublishers.Validate(upstream: self, shouldCheckForErrorModel: checkForErrorModel, acceptableStatusCodes: acceptableStatusCodes) } From 008c1e467369510fa4e8f9d5d15b6e7c59dcbac7 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Wed, 20 Nov 2019 23:02:54 +0530 Subject: [PATCH 04/25] fix build issue due to protocol conformance. --- Sources/NetworkKit/Errors/Business Error.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/NetworkKit/Errors/Business Error.swift b/Sources/NetworkKit/Errors/Business Error.swift index bc8cf66..5039cf3 100644 --- a/Sources/NetworkKit/Errors/Business Error.swift +++ b/Sources/NetworkKit/Errors/Business Error.swift @@ -9,7 +9,7 @@ import Foundation /// Personal / Business / Server Errors -enum BusinessError: LocalizedError { +enum BusinessError: NetworkError { case errorModel(ErrorModel, Int) From 0870868521055cf2b671415664765b17ac303cdf Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Wed, 20 Nov 2019 23:12:22 +0530 Subject: [PATCH 05/25] `current` Environment is public. --- Sources/NetworkKit/Server/Environment.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/NetworkKit/Server/Environment.swift b/Sources/NetworkKit/Server/Environment.swift index de87b36..874f54a 100644 --- a/Sources/NetworkKit/Server/Environment.swift +++ b/Sources/NetworkKit/Server/Environment.swift @@ -23,7 +23,7 @@ public struct Environment: Hashable, Equatable { self.value = value } - static var current: Environment = .none + public internal(set) static var current: Environment = .none public static let none = Environment(value: "") public static let staging = Environment(value: "staging") From 8a54cfffd5cc9acdb6dd5363fc7ab53be1c3c9e5 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 00:10:05 +0530 Subject: [PATCH 06/25] Fix Host defaultURLQuery addition to URLRequest Added check when creating Network Request --- .../Create Request/Create Request.swift | 12 +++++------- Sources/NetworkKit/Errors/Business Error.swift | 2 +- Sources/NetworkKit/Request/Network Request.swift | 16 ++++++++++++++++ .../Resources/HTTP Body Encoding.swift | 1 + 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Sources/NetworkKit/Create Request/Create Request.swift b/Sources/NetworkKit/Create Request/Create Request.swift index a0ddb98..c0df1ff 100644 --- a/Sources/NetworkKit/Create Request/Create Request.swift +++ b/Sources/NetworkKit/Create Request/Create Request.swift @@ -11,10 +11,11 @@ import Foundation public typealias URLQuery = [String: String?] public typealias HTTPHeaderParameters = [String: String] -final class CreateRequest { - private(set) var request: URLRequest +public struct CreateRequest { - init?(with connection: ConnectionRepresentable, query urlQuery: URLQuery?) { + public let request: URLRequest + + public init?(with connection: ConnectionRepresentable, query urlQuery: URLQuery?) { var components = URLComponents() components.scheme = connection.scheme.rawValue @@ -28,13 +29,10 @@ final class CreateRequest { var queryItems: [URLQueryItem] = [] queryItems.addURLQuery(query: urlQuery) queryItems.addURLQuery(query: connection.defaultQuery) + queryItems.addURLQuery(query: connection.host.defaultUrlQuery) let method = connection.method - if method == .get { - queryItems.addURLQuery(query: connection.host.defaultUrlQuery) - } - if !queryItems.isEmpty { components.queryItems = queryItems } diff --git a/Sources/NetworkKit/Errors/Business Error.swift b/Sources/NetworkKit/Errors/Business Error.swift index 5039cf3..19ffc77 100644 --- a/Sources/NetworkKit/Errors/Business Error.swift +++ b/Sources/NetworkKit/Errors/Business Error.swift @@ -1,4 +1,4 @@ - // +// // BusinessError.swift // NetworkKit // diff --git a/Sources/NetworkKit/Request/Network Request.swift b/Sources/NetworkKit/Request/Network Request.swift index 230b8f5..5e302f3 100644 --- a/Sources/NetworkKit/Request/Network Request.swift +++ b/Sources/NetworkKit/Request/Network Request.swift @@ -44,6 +44,10 @@ public final class NetworkRequest { } public func requestBody(_ body: T, strategy: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys) -> Self { + guard request != nil else { + return self + } + var data: Data? = nil let encoder = JSONEncoder() @@ -90,6 +94,10 @@ public final class NetworkRequest { } public func requestBody(_ body: [String: Any], encoding: HTTPBodyEncodingType) -> Self { + guard request != nil else { + return self + } + #if DEBUG DebugPrint.print(""" Request Body: @@ -109,6 +117,10 @@ public final class NetworkRequest { } public func requestBody(_ body: Data?, httpHeaders: HTTPHeaderParameters) -> Self { + guard request != nil else { + return self + } + request?.httpBody = body var headers = request?.allHTTPHeaderFields ?? [:] @@ -119,6 +131,10 @@ public final class NetworkRequest { } public func httpHeaders(_ httpHeaders: HTTPHeaderParameters) -> Self { + guard request != nil else { + return self + } + var headers = request?.allHTTPHeaderFields ?? [:] headers = headers.merging(httpHeaders) { (_, new) in new } request?.allHTTPHeaderFields = headers diff --git a/Sources/NetworkKit/Resources/HTTP Body Encoding.swift b/Sources/NetworkKit/Resources/HTTP Body Encoding.swift index 9bc30c6..112d154 100644 --- a/Sources/NetworkKit/Resources/HTTP Body Encoding.swift +++ b/Sources/NetworkKit/Resources/HTTP Body Encoding.swift @@ -84,6 +84,7 @@ public enum HTTPBodyEncodingType { } private extension HTTPBodyEncodingType { + func query(_ parameters: [String: Any]) -> String { var components: [(String, String)] = [] From 85ea04b712432ce9e9e86ac417c7bea29a601eb6 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 00:13:52 +0530 Subject: [PATCH 07/25] Renamed NetworkPublisher -> NKPublisher --- Sources/NetworkKit/Kit/NetworkKit.swift | 2 +- .../NetworkKit/Kit/Publishers/Assign/Assign.swift | 2 +- .../NetworkKit/Kit/Publishers/Catch/Catch.swift | 4 ++-- .../Kit/Publishers/Catch/Try Catch.swift | 4 ++-- .../Kit/Publishers/Completion/Completion.swift | 2 +- .../Kit/Publishers/Debounce/Debounce.swift | 2 +- .../NetworkKit/Kit/Publishers/Decode/Decode.swift | 2 +- .../Kit/Publishers/Map/Map Keypath 2.swift | 2 +- .../Kit/Publishers/Map/Map Keypath.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Map/Map.swift | 2 +- .../NetworkKit/Kit/Publishers/Map/Try Map.swift | 2 +- .../Publishers/Replace Error/Replace Error.swift | 2 +- .../Kit/Publishers/Validation/Validation.swift | 2 +- ...her+Methods.swift => NKPublisher+Methods.swift} | 14 +++++++------- ...blisher+Queue.swift => NKPublisher+Queue.swift} | 4 ++-- .../{Network Publisher.swift => NKPublisher.swift} | 4 ++-- 16 files changed, 26 insertions(+), 26 deletions(-) rename Sources/NetworkKit/Protocols/Publisher/{Publisher+Methods.swift => NKPublisher+Methods.swift} (93%) rename Sources/NetworkKit/Protocols/Publisher/{Publisher+Queue.swift => NKPublisher+Queue.swift} (93%) rename Sources/NetworkKit/Protocols/Publisher/{Network Publisher.swift => NKPublisher.swift} (88%) diff --git a/Sources/NetworkKit/Kit/NetworkKit.swift b/Sources/NetworkKit/Kit/NetworkKit.swift index a5b44f9..83c7686 100644 --- a/Sources/NetworkKit/Kit/NetworkKit.swift +++ b/Sources/NetworkKit/Kit/NetworkKit.swift @@ -8,7 +8,7 @@ import Foundation -public struct NetworkKit: NetworkPublisher { +public struct NetworkKit: NKPublisher { public var result: NetworkResult diff --git a/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift b/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift index a71a6da..bda3299 100644 --- a/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift +++ b/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift @@ -10,7 +10,7 @@ import Foundation extension NetworkPublishers { - struct Assign: NetworkCancellable where Upstream.Failure == Never { + struct Assign: NetworkCancellable where Upstream.Failure == Never { /// The publisher that this publisher receives elements from. public let upstream: Upstream diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift index a08ba1c..8a0af5d 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift @@ -10,7 +10,7 @@ import Foundation public extension NetworkPublishers { - struct Catch: NetworkPublisher where Upstream.Output == NewPublisher.Output { + struct Catch: NKPublisher where Upstream.Output == NewPublisher.Output { public var result: NetworkResult @@ -45,7 +45,7 @@ public extension NetworkPublishers { } -final class CatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { +final class CatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { public typealias Output = Upstream.Output diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift index 85136d3..0c578f7 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift @@ -10,7 +10,7 @@ import Foundation public extension NetworkPublishers { - struct TryCatch: NetworkPublisher where Upstream.Output == NewPublisher.Output { + struct TryCatch: NKPublisher where Upstream.Output == NewPublisher.Output { public var result: NetworkResult @@ -44,7 +44,7 @@ public extension NetworkPublishers { } } -final class TryCatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { +final class TryCatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { public typealias Output = Upstream.Output diff --git a/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift b/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift index 3496a80..9a3a2ce 100644 --- a/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift +++ b/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift @@ -10,7 +10,7 @@ import Foundation extension NetworkPublishers { - struct Completion: NetworkCancellable { + struct Completion: NetworkCancellable { /// The publisher that this publisher receives elements from. public let upstream: Upstream diff --git a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift index f3eb290..b2c1cea 100644 --- a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift +++ b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift @@ -10,7 +10,7 @@ import Foundation public extension NetworkPublishers { - struct Debounce: NetworkPublisher { + struct Debounce: NKPublisher { public var result: NetworkResult { upstream.result diff --git a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift index 88bed23..bb11739 100644 --- a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift +++ b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift @@ -10,7 +10,7 @@ import Foundation public extension NetworkPublishers { - struct Decode: NetworkPublisher where Upstream.Output == Decoder.Input { + struct Decode: NKPublisher where Upstream.Output == Decoder.Input { public var result: NetworkResult diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift index 1422491..5a47186 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift @@ -11,7 +11,7 @@ import Foundation extension NetworkPublishers { /// A publisher that publishes the values of two key paths as a tuple. - public struct MapKeyPath2: NetworkPublisher { + public struct MapKeyPath2: NKPublisher { public var result: NetworkResult<(Output0, Output1), Upstream.Failure> diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift index 8caac43..f032fca 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift @@ -11,7 +11,7 @@ import Foundation public extension NetworkPublishers { /// A publisher that publishes the value of a key path. - struct MapKeyPath: NetworkPublisher { + struct MapKeyPath: NKPublisher { public var result: NetworkResult diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift index c68f360..76e9847 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift @@ -10,7 +10,7 @@ import Foundation public extension NetworkPublishers { - struct Map: NetworkPublisher { + struct Map: NKPublisher { public var result: NetworkResult diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift index 253886c..551aba4 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift @@ -10,7 +10,7 @@ import Foundation public extension NetworkPublishers { - struct TryMap: NetworkPublisher { + struct TryMap: NKPublisher { public var result: NetworkResult diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift index 1032dbb..2a449f0 100644 --- a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift +++ b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift @@ -10,7 +10,7 @@ import Foundation public extension NetworkPublishers { - struct ReplaceError: NetworkPublisher { + struct ReplaceError: NKPublisher { public var result: NetworkResult diff --git a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift index b8d6809..d5613f9 100644 --- a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift +++ b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift @@ -10,7 +10,7 @@ import Foundation public extension NetworkPublishers { - struct Validate: NetworkPublisher where Upstream.Output == NetworkKit.Output, Upstream.Failure == NetworkKit.Failure { + struct Validate: NKPublisher where Upstream.Output == NetworkKit.Output, Upstream.Failure == NetworkKit.Failure { public var queue: NetworkQueue { upstream.queue diff --git a/Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift similarity index 93% rename from Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift rename to Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift index 82dbf01..f75fa61 100644 --- a/Sources/NetworkKit/Protocols/Publisher/Publisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift @@ -1,5 +1,5 @@ // -// Publisher+Methods.swift +// NKPublisher+Methods.swift // NetworkKit // // Created by Raghav Ahuja on 18/11/19. @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublisher { +public extension NKPublisher { /// Decodes the output from upstream using a specified `NetworkDecoder`. /// For example, use `JSONDecoder`. @@ -109,7 +109,7 @@ public extension NetworkPublisher { /// Backpressure note: This publisher passes through `request` and `cancel` to the upstream. After receiving an error, the publisher sends sends any unfulfilled demand to the new `Publisher`. /// - Parameter handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. /// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. - func `catch`(_ handler: @escaping (Self.Failure) -> P) -> NetworkPublishers.Catch where Self.Output == P.Output { + func `catch`(_ handler: @escaping (Self.Failure) -> P) -> NetworkPublishers.Catch where Self.Output == P.Output { NetworkPublishers.Catch(upstream: self, handler: handler) } @@ -117,12 +117,12 @@ public extension NetworkPublisher { /// /// - Parameter handler: A `throw`ing closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher or if an error is thrown will send the error downstream. /// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. - func tryCatch(_ handler: @escaping (Self.Failure) throws -> P) -> NetworkPublishers.TryCatch where Self.Output == P.Output { + func tryCatch(_ handler: @escaping (Self.Failure) throws -> P) -> NetworkPublishers.TryCatch where Self.Output == P.Output { NetworkPublishers.TryCatch(upstream: self, handler: handler) } } -public extension NetworkPublisher where Self.Output == NetworkKit.Output, Self.Failure == NetworkKit.Failure { +public extension NKPublisher where Self.Output == NetworkKit.Output, Self.Failure == NetworkKit.Failure { /// Validates Network call response with provided acceptable status codes. /// - Parameter acceptableStatusCodes: Acceptable HTTP Status codes for the network call. Default value is `Array(200 < 300)`. @@ -133,7 +133,7 @@ public extension NetworkPublisher where Self.Output == NetworkKit.Output, Self.F } } -public extension NetworkPublisher { +public extension NKPublisher { /// Attaches a subscriber with closure-based behavior. /// @@ -146,7 +146,7 @@ public extension NetworkPublisher { } } -public extension NetworkPublisher where Self.Failure == Never { +public extension NKPublisher where Self.Failure == Never { /// Assigns each element from a Publisher to a property on an object. /// diff --git a/Sources/NetworkKit/Protocols/Publisher/Publisher+Queue.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift similarity index 93% rename from Sources/NetworkKit/Protocols/Publisher/Publisher+Queue.swift rename to Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift index dfa1af9..5311852 100644 --- a/Sources/NetworkKit/Protocols/Publisher/Publisher+Queue.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift @@ -1,5 +1,5 @@ // -// Publisher+Queue.swift +// NKPublisher+Queue.swift // NetworkKit // // Created by Raghav Ahuja on 18/11/19. @@ -8,7 +8,7 @@ import Foundation -extension NetworkPublisher { +extension NKPublisher { func addToQueue(isSuspended: Bool = false, _ block: @escaping () -> Void) { let op = BlockOperation(block: block) diff --git a/Sources/NetworkKit/Protocols/Publisher/Network Publisher.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift similarity index 88% rename from Sources/NetworkKit/Protocols/Publisher/Network Publisher.swift rename to Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift index 64748bf..84952c5 100644 --- a/Sources/NetworkKit/Protocols/Publisher/Network Publisher.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift @@ -1,5 +1,5 @@ // -// Network Publisher.swift +// NKPublisher.swift // NetworkKit // // Created by Raghav Ahuja on 18/11/19. @@ -8,7 +8,7 @@ import Foundation -public protocol NetworkPublisher { +public protocol NKPublisher { /// The kind of values published by this publisher. associatedtype Output From 1578b24fffc5c6789b311ec8cbd4d9fc683e846e Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 00:17:24 +0530 Subject: [PATCH 08/25] Renamed NetworkPublishers -> NKPublishers --- .../Kit/Publishers/Assign/Assign.swift | 2 +- .../Kit/Publishers/Catch/Catch.swift | 2 +- .../Kit/Publishers/Catch/Try Catch.swift | 2 +- .../Publishers/Completion/Completion.swift | 2 +- .../Kit/Publishers/Debounce/Debounce.swift | 2 +- .../Kit/Publishers/Decode/Decode.swift | 2 +- .../Kit/Publishers/Map/Map Keypath 2.swift | 2 +- .../Kit/Publishers/Map/Map Keypath.swift | 2 +- .../NetworkKit/Kit/Publishers/Map/Map.swift | 2 +- .../Kit/Publishers/Map/Try Map.swift | 2 +- ...rk Publishers.swift => NKPublishers.swift} | 4 +- .../Replace Error/Replace Error.swift | 4 +- .../Publishers/Validation/Validation.swift | 4 +- .../Publisher/NKPublisher+Methods.swift | 52 +++++++++---------- 14 files changed, 42 insertions(+), 42 deletions(-) rename Sources/NetworkKit/Kit/Publishers/{Network Publishers.swift => NKPublishers.swift} (70%) diff --git a/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift b/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift index bda3299..6c9d1d2 100644 --- a/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift +++ b/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift @@ -8,7 +8,7 @@ import Foundation -extension NetworkPublishers { +extension NKPublishers { struct Assign: NetworkCancellable where Upstream.Failure == Never { diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift index 8a0af5d..4067d23 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublishers { +public extension NKPublishers { struct Catch: NKPublisher where Upstream.Output == NewPublisher.Output { diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift index 0c578f7..9ff3fdf 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublishers { +public extension NKPublishers { struct TryCatch: NKPublisher where Upstream.Output == NewPublisher.Output { diff --git a/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift b/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift index 9a3a2ce..24fb4cd 100644 --- a/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift +++ b/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift @@ -8,7 +8,7 @@ import Foundation -extension NetworkPublishers { +extension NKPublishers { struct Completion: NetworkCancellable { diff --git a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift index b2c1cea..cd1270f 100644 --- a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift +++ b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublishers { +public extension NKPublishers { struct Debounce: NKPublisher { diff --git a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift index bb11739..5813102 100644 --- a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift +++ b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublishers { +public extension NKPublishers { struct Decode: NKPublisher where Upstream.Output == Decoder.Input { diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift index 5a47186..cab1e48 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift @@ -8,7 +8,7 @@ import Foundation -extension NetworkPublishers { +extension NKPublishers { /// A publisher that publishes the values of two key paths as a tuple. public struct MapKeyPath2: NKPublisher { diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift index f032fca..37ed35f 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublishers { +public extension NKPublishers { /// A publisher that publishes the value of a key path. struct MapKeyPath: NKPublisher { diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift index 76e9847..9dcbaef 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublishers { +public extension NKPublishers { struct Map: NKPublisher { diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift index 551aba4..d4d1b8e 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublishers { +public extension NKPublishers { struct TryMap: NKPublisher { diff --git a/Sources/NetworkKit/Kit/Publishers/Network Publishers.swift b/Sources/NetworkKit/Kit/Publishers/NKPublishers.swift similarity index 70% rename from Sources/NetworkKit/Kit/Publishers/Network Publishers.swift rename to Sources/NetworkKit/Kit/Publishers/NKPublishers.swift index 1e07742..b8f1197 100644 --- a/Sources/NetworkKit/Kit/Publishers/Network Publishers.swift +++ b/Sources/NetworkKit/Kit/Publishers/NKPublishers.swift @@ -1,5 +1,5 @@ // -// Network Publishers.swift +// NKPublishers.swift // NetworkKit // // Created by Raghav Ahuja on 18/11/19. @@ -9,5 +9,5 @@ import Foundation -public enum NetworkPublishers { +public enum NKPublishers { } diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift index 2a449f0..c5e92c4 100644 --- a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift +++ b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublishers { +public extension NKPublishers { struct ReplaceError: NKPublisher { @@ -28,7 +28,7 @@ public extension NetworkPublishers { /// The publisher from which this publisher receives elements. public let upstream: Upstream - public init(upstream: Upstream, output: NetworkPublishers.ReplaceError.Output) { + public init(upstream: Upstream, output: Output) { self.upstream = upstream self.output = output result = .init(result: .success(output)) diff --git a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift index d5613f9..821cdce 100644 --- a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift +++ b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift @@ -8,7 +8,7 @@ import Foundation -public extension NetworkPublishers { +public extension NKPublishers { struct Validate: NKPublisher where Upstream.Output == NetworkKit.Output, Upstream.Failure == NetworkKit.Failure { @@ -123,7 +123,7 @@ public extension NetworkPublishers { } -private extension NetworkPublishers.Validate { +private extension NKPublishers.Validate { /// ACCEPTABLE CONTENT TYPE CHECK struct MIMEType { diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift index f75fa61..5d5ff34 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift @@ -14,23 +14,23 @@ public extension NKPublisher { /// For example, use `JSONDecoder`. /// - Parameter type: Type to decode into. /// - Parameter decoder: `NetworkDecoder` for decoding output. - func decode(type: Item.Type, decoder: Decoder) -> NetworkPublishers.Decode { - NetworkPublishers.Decode(upstream: self, decoder: decoder) + func decode(type: Item.Type, decoder: Decoder) -> NKPublishers.Decode { + NKPublishers.Decode(upstream: self, decoder: decoder) } /// Decodes the output from upstream using a specified `JSONDecoder`. /// - Parameter type: Type to decode into. /// - Parameter jsonKeyDecodingStrategy: JSON Key Decoding Strategy. - func decode(type: Item.Type, jsonKeyDecodingStrategy: JSONDecoder.KeyDecodingStrategy) -> NetworkPublishers.Decode { - NetworkPublishers.Decode(upstream: self, jsonKeyDecodingStrategy: jsonKeyDecodingStrategy) + func decode(type: Item.Type, jsonKeyDecodingStrategy: JSONDecoder.KeyDecodingStrategy) -> NKPublishers.Decode { + NKPublishers.Decode(upstream: self, jsonKeyDecodingStrategy: jsonKeyDecodingStrategy) } /// Transforms all elements from the upstream publisher with a provided closure. /// /// - Parameter transform: A closure that takes one element as its parameter and returns a new element. /// - Returns: A publisher that uses the provided closure to map elements from the upstream publisher to new elements that it then publishes. - func map(_ transform: @escaping (Output) -> T) -> NetworkPublishers.Map { - NetworkPublishers.Map(upstream: self, transform: transform) + func map(_ transform: @escaping (Output) -> T) -> NKPublishers.Map { + NKPublishers.Map(upstream: self, transform: transform) } /// Transforms all elements from the upstream publisher with a provided error-throwing closure. @@ -38,8 +38,8 @@ public extension NKPublisher { /// If the `transform` closure throws an error, the publisher fails with the thrown error. /// - Parameter transform: A closure that takes one element as its parameter and returns a new element. /// - Returns: A publisher that uses the provided closure to map elements from the upstream publisher to new elements that it then publishes. - func tryMap(_ transform: @escaping (Output) throws -> T) -> NetworkPublishers.TryMap { - NetworkPublishers.TryMap(upstream: self, transform: transform) + func tryMap(_ transform: @escaping (Output) throws -> T) -> NKPublishers.TryMap { + NKPublishers.TryMap(upstream: self, transform: transform) } /// Replaces any errors in the stream with the provided element. @@ -47,8 +47,8 @@ public extension NKPublisher { /// If the upstream publisher fails with an error, this publisher emits the provided element, then finishes normally. /// - Parameter output: An element to emit when the upstream publisher fails. /// - Returns: A publisher that replaces an error from the upstream publisher with the provided output element. - func replaceError(with output: Output) -> NetworkPublishers.ReplaceError { - NetworkPublishers.ReplaceError(upstream: self, output: output) + func replaceError(with output: Output) -> NKPublishers.ReplaceError { + NKPublishers.ReplaceError(upstream: self, output: output) } /// Publishes elements only after a specified time interval elapses between events. @@ -57,8 +57,8 @@ public extension NKPublisher { /// - Parameters: /// - dueTime: The time the publisher should wait before publishing an element. /// - Returns: A publisher that publishes events only after a specified time elapses. - func debounce(_ dueTime: DispatchTimeInterval) -> NetworkPublishers.Debounce { - NetworkPublishers.Debounce(upstream: self, dueTime: .now() + dueTime) + func debounce(_ dueTime: DispatchTimeInterval) -> NKPublishers.Debounce { + NKPublishers.Debounce(upstream: self, dueTime: .now() + dueTime) } /// Publishes elements only after a specified time interval elapses between events. @@ -67,16 +67,16 @@ public extension NKPublisher { /// - Parameters: /// - dueTime: The time the publisher should wait before publishing an element. /// - scheduler: The scheduler on which this publisher delivers elements - func debounce(_ dueTime: DispatchTime) -> NetworkPublishers.Debounce { - NetworkPublishers.Debounce(upstream: self, dueTime: dueTime) + func debounce(_ dueTime: DispatchTime) -> NKPublishers.Debounce { + NKPublishers.Debounce(upstream: self, dueTime: dueTime) } /// Returns a publisher that publishes the value of a key path. /// /// - Parameter keyPath: The key path of a property on `Output` /// - Returns: A publisher that publishes the value of the key path. - func map(_ keyPath: KeyPath) -> NetworkPublishers.MapKeyPath { - NetworkPublishers.MapKeyPath(upstream: self, keyPath: keyPath) + func map(_ keyPath: KeyPath) -> NKPublishers.MapKeyPath { + NKPublishers.MapKeyPath(upstream: self, keyPath: keyPath) } /// Returns a publisher that publishes the values of two key paths as a tuple. @@ -85,8 +85,8 @@ public extension NKPublisher { /// - keyPath0: The key path of a property on `Output` /// - keyPath1: The key path of another property on `Output` /// - Returns: A publisher that publishes the values of two key paths as a tuple. - func map(_ keyPath0: KeyPath, _ keyPath1: KeyPath) -> NetworkPublishers.MapKeyPath2 { - NetworkPublishers.MapKeyPath2(upstream: self, keyPath0: keyPath0, keyPath1: keyPath1) + func map(_ keyPath0: KeyPath, _ keyPath1: KeyPath) -> NKPublishers.MapKeyPath2 { + NKPublishers.MapKeyPath2(upstream: self, keyPath0: keyPath0, keyPath1: keyPath1) } /// Handles errors from an upstream publisher by replacing it with another publisher. @@ -109,16 +109,16 @@ public extension NKPublisher { /// Backpressure note: This publisher passes through `request` and `cancel` to the upstream. After receiving an error, the publisher sends sends any unfulfilled demand to the new `Publisher`. /// - Parameter handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. /// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. - func `catch`(_ handler: @escaping (Self.Failure) -> P) -> NetworkPublishers.Catch where Self.Output == P.Output { - NetworkPublishers.Catch(upstream: self, handler: handler) + func `catch`(_ handler: @escaping (Self.Failure) -> P) -> NKPublishers.Catch where Self.Output == P.Output { + NKPublishers.Catch(upstream: self, handler: handler) } /// Handles errors from an upstream publisher by either replacing it with another publisher or `throw`ing a new error. /// /// - Parameter handler: A `throw`ing closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher or if an error is thrown will send the error downstream. /// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. - func tryCatch(_ handler: @escaping (Self.Failure) throws -> P) -> NetworkPublishers.TryCatch where Self.Output == P.Output { - NetworkPublishers.TryCatch(upstream: self, handler: handler) + func tryCatch(_ handler: @escaping (Self.Failure) throws -> P) -> NKPublishers.TryCatch where Self.Output == P.Output { + NKPublishers.TryCatch(upstream: self, handler: handler) } } @@ -128,8 +128,8 @@ public extension NKPublisher where Self.Output == NetworkKit.Output, Self.Failur /// - Parameter acceptableStatusCodes: Acceptable HTTP Status codes for the network call. Default value is `Array(200 < 300)`. /// - Parameter checkForErrorModel: If publisher should check for custom error model to decode. /// - Returns: A publisher that validates response from an upstream publisher. - func validate(acceptableStatusCodes: [Int] = [], checkForErrorModel: Bool = true) -> NetworkPublishers.Validate { - NetworkPublishers.Validate(upstream: self, shouldCheckForErrorModel: checkForErrorModel, acceptableStatusCodes: acceptableStatusCodes) + func validate(acceptableStatusCodes: [Int] = [], checkForErrorModel: Bool = true) -> NKPublishers.Validate { + NKPublishers.Validate(upstream: self, shouldCheckForErrorModel: checkForErrorModel, acceptableStatusCodes: acceptableStatusCodes) } } @@ -142,7 +142,7 @@ public extension NKPublisher { /// - parameter receiveValue: The closure to execute on receipt of a value. /// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. func completion(_ block: @escaping (Result) -> Void) -> NetworkCancellable { - NetworkPublishers.Completion(upstream: self, block: block) + NKPublishers.Completion(upstream: self, block: block) } } @@ -155,6 +155,6 @@ public extension NKPublisher where Self.Failure == Never { /// - object: The object on which to assign the value. /// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. func assign(to keyPath: ReferenceWritableKeyPath, on root: Root) -> NetworkCancellable { - NetworkPublishers.Assign(upstream: self, to: keyPath, on: root) + NKPublishers.Assign(upstream: self, to: keyPath, on: root) } } From d7ae34b4e21798b9c7a2a099113401de4db7cdca Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 00:18:29 +0530 Subject: [PATCH 09/25] Renamed NetworkQueue -> NKQueue --- .../Network Queue.swift => NKQueue/NKQueue.swift} | 4 ++-- Sources/NetworkKit/Kit/NetworkKit.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Map/Map.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift | 2 +- .../Kit/Publishers/Replace Error/Replace Error.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift | 2 +- Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift | 2 +- 13 files changed, 14 insertions(+), 14 deletions(-) rename Sources/NetworkKit/Kit/{Network Queue/Network Queue.swift => NKQueue/NKQueue.swift} (92%) diff --git a/Sources/NetworkKit/Kit/Network Queue/Network Queue.swift b/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift similarity index 92% rename from Sources/NetworkKit/Kit/Network Queue/Network Queue.swift rename to Sources/NetworkKit/Kit/NKQueue/NKQueue.swift index 2747fa5..30bbe01 100644 --- a/Sources/NetworkKit/Kit/Network Queue/Network Queue.swift +++ b/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift @@ -1,5 +1,5 @@ // -// Network Queue.swift +// NKQueue.swift // NetworkKit // // Created by Raghav Ahuja on 18/11/19. @@ -8,7 +8,7 @@ import Foundation -public final class NetworkQueue { +public final class NKQueue { let operationQueue: OperationQueue var request: URLRequest? diff --git a/Sources/NetworkKit/Kit/NetworkKit.swift b/Sources/NetworkKit/Kit/NetworkKit.swift index 83c7686..f924503 100644 --- a/Sources/NetworkKit/Kit/NetworkKit.swift +++ b/Sources/NetworkKit/Kit/NetworkKit.swift @@ -12,7 +12,7 @@ public struct NetworkKit: NKPublisher { public var result: NetworkResult - public var queue: NetworkQueue + public var queue: NKQueue public typealias Output = (data: Data, response: HTTPURLResponse) diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift index 4067d23..b0cbf57 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift @@ -14,7 +14,7 @@ public extension NKPublishers { public var result: NetworkResult - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift index 9ff3fdf..36df33e 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift @@ -14,7 +14,7 @@ public extension NKPublishers { public var result: NetworkResult - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift index cd1270f..61fb87f 100644 --- a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift +++ b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift @@ -16,7 +16,7 @@ public extension NKPublishers { upstream.result } - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift index 5813102..d6d8c70 100644 --- a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift +++ b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift @@ -14,7 +14,7 @@ public extension NKPublishers { public var result: NetworkResult - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift index cab1e48..0a4d40f 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift @@ -15,7 +15,7 @@ extension NKPublishers { public var result: NetworkResult<(Output0, Output1), Upstream.Failure> - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift index 37ed35f..e49046f 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift @@ -15,7 +15,7 @@ public extension NKPublishers { public var result: NetworkResult - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift index 9dcbaef..d1e58a4 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift @@ -14,7 +14,7 @@ public extension NKPublishers { public var result: NetworkResult - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift index d4d1b8e..1d3a1ae 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift @@ -14,7 +14,7 @@ public extension NKPublishers { public var result: NetworkResult - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift index c5e92c4..22f85aa 100644 --- a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift +++ b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift @@ -14,7 +14,7 @@ public extension NKPublishers { public var result: NetworkResult - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift index 821cdce..7f89747 100644 --- a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift +++ b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift @@ -12,7 +12,7 @@ public extension NKPublishers { struct Validate: NKPublisher where Upstream.Output == NetworkKit.Output, Upstream.Failure == NetworkKit.Failure { - public var queue: NetworkQueue { + public var queue: NKQueue { upstream.queue } diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift index 84952c5..bb77b35 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift @@ -20,5 +20,5 @@ public protocol NKPublisher { var result: NetworkResult { get } - var queue: NetworkQueue { get } + var queue: NKQueue { get } } From ecdc99cd701f0e340a3a07a442a04a9e994e7ddf Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 00:19:21 +0530 Subject: [PATCH 10/25] Renamed NetworkResult -> NKResult --- .../Network Result.swift => NKResult/NKResult.swift} | 4 ++-- Sources/NetworkKit/Kit/NetworkKit.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift | 6 +++--- Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift | 6 +++--- Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Map/Map.swift | 2 +- Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift | 2 +- .../Kit/Publishers/Replace Error/Replace Error.swift | 2 +- .../NetworkKit/Kit/Publishers/Validation/Validation.swift | 2 +- Sources/NetworkKit/Operations/Fetch Operation.swift | 4 ++-- Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) rename Sources/NetworkKit/Kit/{Network Result/Network Result.swift => NKResult/NKResult.swift} (82%) diff --git a/Sources/NetworkKit/Kit/Network Result/Network Result.swift b/Sources/NetworkKit/Kit/NKResult/NKResult.swift similarity index 82% rename from Sources/NetworkKit/Kit/Network Result/Network Result.swift rename to Sources/NetworkKit/Kit/NKResult/NKResult.swift index f81f46c..8f4fd86 100644 --- a/Sources/NetworkKit/Kit/Network Result/Network Result.swift +++ b/Sources/NetworkKit/Kit/NKResult/NKResult.swift @@ -1,5 +1,5 @@ // -// Network Result.swift +// NKResult.swift // NetworkKit // // Created by Raghav Ahuja on 18/11/19. @@ -8,7 +8,7 @@ import Foundation -public final class NetworkResult { +public final class NKResult { var result: Result diff --git a/Sources/NetworkKit/Kit/NetworkKit.swift b/Sources/NetworkKit/Kit/NetworkKit.swift index f924503..b11a539 100644 --- a/Sources/NetworkKit/Kit/NetworkKit.swift +++ b/Sources/NetworkKit/Kit/NetworkKit.swift @@ -10,7 +10,7 @@ import Foundation public struct NetworkKit: NKPublisher { - public var result: NetworkResult + public var result: NKResult public var queue: NKQueue diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift index b0cbf57..f4c6727 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift @@ -12,7 +12,7 @@ public extension NKPublishers { struct Catch: NKPublisher where Upstream.Output == NewPublisher.Output { - public var result: NetworkResult + public var result: NKResult public var queue: NKQueue { upstream.queue @@ -54,12 +54,12 @@ final class CatchOperation: As /// The publisher that this publisher receives elements from. private let upstream: Upstream - private var result: NetworkResult + private var result: NKResult /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. private let handler: (Upstream.Failure) -> NewPublisher - init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher, result: NetworkResult) { + init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher, result: NKResult) { self.upstream = upstream self.handler = handler self.result = result diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift index 36df33e..6e67581 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift @@ -12,7 +12,7 @@ public extension NKPublishers { struct TryCatch: NKPublisher where Upstream.Output == NewPublisher.Output { - public var result: NetworkResult + public var result: NKResult public var queue: NKQueue { upstream.queue @@ -54,12 +54,12 @@ final class TryCatchOperation: /// The publisher that this publisher receives elements from. private let upstream: Upstream - private var result: NetworkResult + private var result: NKResult /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. private let handler: (Upstream.Failure) throws -> NewPublisher - public init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher, result: NetworkResult) { + public init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher, result: NKResult) { self.upstream = upstream self.handler = handler self.result = result diff --git a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift index 61fb87f..ab33b6b 100644 --- a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift +++ b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift @@ -12,7 +12,7 @@ public extension NKPublishers { struct Debounce: NKPublisher { - public var result: NetworkResult { + public var result: NKResult { upstream.result } diff --git a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift index d6d8c70..907333a 100644 --- a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift +++ b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift @@ -12,7 +12,7 @@ public extension NKPublishers { struct Decode: NKPublisher where Upstream.Output == Decoder.Input { - public var result: NetworkResult + public var result: NKResult public var queue: NKQueue { upstream.queue diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift index 0a4d40f..8cef393 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift @@ -13,7 +13,7 @@ extension NKPublishers { /// A publisher that publishes the values of two key paths as a tuple. public struct MapKeyPath2: NKPublisher { - public var result: NetworkResult<(Output0, Output1), Upstream.Failure> + public var result: NKResult<(Output0, Output1), Upstream.Failure> public var queue: NKQueue { upstream.queue diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift index e49046f..5276260 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift @@ -13,7 +13,7 @@ public extension NKPublishers { /// A publisher that publishes the value of a key path. struct MapKeyPath: NKPublisher { - public var result: NetworkResult + public var result: NKResult public var queue: NKQueue { upstream.queue diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift index d1e58a4..d1c8d30 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift @@ -12,7 +12,7 @@ public extension NKPublishers { struct Map: NKPublisher { - public var result: NetworkResult + public var result: NKResult public var queue: NKQueue { upstream.queue diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift index 1d3a1ae..e6980e3 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift @@ -12,7 +12,7 @@ public extension NKPublishers { struct TryMap: NKPublisher { - public var result: NetworkResult + public var result: NKResult public var queue: NKQueue { upstream.queue diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift index 22f85aa..65bb841 100644 --- a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift +++ b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift @@ -12,7 +12,7 @@ public extension NKPublishers { struct ReplaceError: NKPublisher { - public var result: NetworkResult + public var result: NKResult public var queue: NKQueue { upstream.queue diff --git a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift index 7f89747..b607a8d 100644 --- a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift +++ b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift @@ -16,7 +16,7 @@ public extension NKPublishers { upstream.queue } - public var result: NetworkResult + public var result: NKResult public typealias Output = NetworkKit.Output diff --git a/Sources/NetworkKit/Operations/Fetch Operation.swift b/Sources/NetworkKit/Operations/Fetch Operation.swift index 85d2c7b..68c4be6 100644 --- a/Sources/NetworkKit/Operations/Fetch Operation.swift +++ b/Sources/NetworkKit/Operations/Fetch Operation.swift @@ -10,12 +10,12 @@ import Foundation class FetchOperation: AsynchronousOperation { - private let result: NetworkResult? + private let result: NKResult? private let request: URLRequest? var task: URLSessionDataTask? - init(request: URLRequest?, result: NetworkResult?) { + init(request: URLRequest?, result: NKResult?) { self.request = request self.result = result super.init() diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift index bb77b35..82c120c 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift @@ -18,7 +18,7 @@ public protocol NKPublisher { /// Use `Never` if this `Publisher` does not publish errors. associatedtype Failure: NetworkError - var result: NetworkResult { get } + var result: NKResult { get } var queue: NKQueue { get } } From 653ea146fff3c6d7d625ad9aae2aa88fe6b21b99 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 00:23:17 +0530 Subject: [PATCH 11/25] Moved Operations to separate files --- Sources/NetworkKit/Errors/NKError.swift | 6 +- .../Kit/Publishers/Catch/Catch.swift | 51 -------------- .../Kit/Publishers/Catch/Try Catch.swift | 59 ---------------- .../Operations/Catch Operation.swift | 59 ++++++++++++++++ .../Operations/TryCatch Operation.swift | 67 +++++++++++++++++++ .../NetworkKit/Protocols/NetworkError.swift | 13 ++++ 6 files changed, 140 insertions(+), 115 deletions(-) create mode 100644 Sources/NetworkKit/Operations/Catch Operation.swift create mode 100644 Sources/NetworkKit/Operations/TryCatch Operation.swift create mode 100644 Sources/NetworkKit/Protocols/NetworkError.swift diff --git a/Sources/NetworkKit/Errors/NKError.swift b/Sources/NetworkKit/Errors/NKError.swift index ceb8de9..0bb8481 100644 --- a/Sources/NetworkKit/Errors/NKError.swift +++ b/Sources/NetworkKit/Errors/NKError.swift @@ -1,5 +1,5 @@ // -// Network Error.swift +// NKError.swift // NetworkKit // // Created by Raghav Ahuja on 15/10/19. @@ -8,10 +8,6 @@ import Foundation -public protocol NetworkError: LocalizedError { - var errorCode: Int { get } -} - public struct NKError: NetworkError { public let localizedDescription: String public let errorDescription: String? diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift index f4c6727..d395e3d 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift @@ -43,54 +43,3 @@ public extension NKPublishers { } } } - - -final class CatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { - - public typealias Output = Upstream.Output - - public typealias Failure = NewPublisher.Failure - - /// The publisher that this publisher receives elements from. - private let upstream: Upstream - - private var result: NKResult - - /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. - private let handler: (Upstream.Failure) -> NewPublisher - - init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher, result: NKResult) { - self.upstream = upstream - self.handler = handler - self.result = result - } - - override func main() { - switch upstream.result.result { - case .success(let output): - result.result = .success(output) - - case .failure(let error): - let newPublisher = handler(error) - - guard let newOperation = newPublisher.result.operation else { - return - } - - newOperation.completionBlock = { [weak self] in - switch newPublisher.result.result { - case .success(let output): - self?.result.result = .success(output) - - case .failure(let error): - self?.result.result = .failure(error) - } - - newPublisher.queue.operationQueue.cancelAllOperations() - self?.finish() - } - - newOperation.main() - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift index 6e67581..ba716d2 100644 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift +++ b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift @@ -43,62 +43,3 @@ public extension NKPublishers { } } } - -final class TryCatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { - - - public typealias Output = Upstream.Output - - public typealias Failure = NewPublisher.Failure - - /// The publisher that this publisher receives elements from. - private let upstream: Upstream - - private var result: NKResult - - /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. - private let handler: (Upstream.Failure) throws -> NewPublisher - - public init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher, result: NKResult) { - self.upstream = upstream - self.handler = handler - self.result = result - } - - override func main() { - switch upstream.result.result { - case .success(let output): - result.result = .success(output) - finish() - - case .failure(let error): - do { - let newPublisher = try handler(error) - - guard let newOperation = newPublisher.result.operation else { - result.result = .failure(NKError.unkown() as! NewPublisher.Failure) - return - } - - newOperation.completionBlock = { [weak self] in - switch newPublisher.result.result { - case .success(let output): - self?.result.result = .success(output) - - case .failure(let error): - self?.result.result = .failure(error) - } - - newPublisher.queue.operationQueue.cancelAllOperations() - self?.finish() - } - - newOperation.main() - - } catch let handlerError { - result.result = .failure(NKError(handlerError as NSError) as! NewPublisher.Failure) - finish() - } - } - } -} diff --git a/Sources/NetworkKit/Operations/Catch Operation.swift b/Sources/NetworkKit/Operations/Catch Operation.swift new file mode 100644 index 0000000..df2dea2 --- /dev/null +++ b/Sources/NetworkKit/Operations/Catch Operation.swift @@ -0,0 +1,59 @@ +// +// Catch Operation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 21/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +final class CatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { + + public typealias Output = Upstream.Output + + public typealias Failure = NewPublisher.Failure + + /// The publisher that this publisher receives elements from. + private let upstream: Upstream + + private var result: NKResult + + /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. + private let handler: (Upstream.Failure) -> NewPublisher + + init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher, result: NKResult) { + self.upstream = upstream + self.handler = handler + self.result = result + } + + override func main() { + switch upstream.result.result { + case .success(let output): + result.result = .success(output) + + case .failure(let error): + let newPublisher = handler(error) + + guard let newOperation = newPublisher.result.operation else { + return + } + + newOperation.completionBlock = { [weak self] in + switch newPublisher.result.result { + case .success(let output): + self?.result.result = .success(output) + + case .failure(let error): + self?.result.result = .failure(error) + } + + newPublisher.queue.operationQueue.cancelAllOperations() + self?.finish() + } + + newOperation.main() + } + } +} diff --git a/Sources/NetworkKit/Operations/TryCatch Operation.swift b/Sources/NetworkKit/Operations/TryCatch Operation.swift new file mode 100644 index 0000000..8e12083 --- /dev/null +++ b/Sources/NetworkKit/Operations/TryCatch Operation.swift @@ -0,0 +1,67 @@ +// +// TryCatch Operation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 21/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +final class TryCatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { + + public typealias Output = Upstream.Output + + public typealias Failure = NewPublisher.Failure + + /// The publisher that this publisher receives elements from. + private let upstream: Upstream + + private var result: NKResult + + /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. + private let handler: (Upstream.Failure) throws -> NewPublisher + + public init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher, result: NKResult) { + self.upstream = upstream + self.handler = handler + self.result = result + } + + override func main() { + switch upstream.result.result { + case .success(let output): + result.result = .success(output) + finish() + + case .failure(let error): + do { + let newPublisher = try handler(error) + + guard let newOperation = newPublisher.result.operation else { + result.result = .failure(NKError.unkown() as! NewPublisher.Failure) + return + } + + newOperation.completionBlock = { [weak self] in + switch newPublisher.result.result { + case .success(let output): + self?.result.result = .success(output) + + case .failure(let error): + self?.result.result = .failure(error) + } + + newPublisher.queue.operationQueue.cancelAllOperations() + self?.finish() + } + + newOperation.main() + + } catch let handlerError { + result.result = .failure(NKError(handlerError as NSError) as! NewPublisher.Failure) + finish() + } + } + } +} diff --git a/Sources/NetworkKit/Protocols/NetworkError.swift b/Sources/NetworkKit/Protocols/NetworkError.swift new file mode 100644 index 0000000..3a58954 --- /dev/null +++ b/Sources/NetworkKit/Protocols/NetworkError.swift @@ -0,0 +1,13 @@ +// +// Network Error.swift +// NetworkKit +// +// Created by Raghav Ahuja on 21/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public protocol NetworkError: LocalizedError { + var errorCode: Int { get } +} From c74b67d79fd53bc3e9d903a2d38466447168a800 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 00:26:26 +0530 Subject: [PATCH 12/25] Removed Combine Header --- Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift | 1 + Tests/NetworkKitTests/NetworkKitTests.swift | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift b/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift index a2df0af..79b8a8d 100644 --- a/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift +++ b/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift @@ -9,5 +9,6 @@ import Foundation extension JSONDecoder: NetworkDecoder { + public typealias Input = Data } diff --git a/Tests/NetworkKitTests/NetworkKitTests.swift b/Tests/NetworkKitTests/NetworkKitTests.swift index db25c57..e0e5fb1 100644 --- a/Tests/NetworkKitTests/NetworkKitTests.swift +++ b/Tests/NetworkKitTests/NetworkKitTests.swift @@ -1,5 +1,4 @@ import XCTest -import Combine @testable import NetworkKit enum APIType: String, APIRepresentable { @@ -62,7 +61,7 @@ enum MockPointError: ConnectionRepresentable { var method: HTTPMethod { .get } var httpHeaders: HTTPHeaderParameters { [:] } -// + var host: HostRepresentable { Host.server } var defaultQuery: URLQuery? { nil } From 28a369b8d691e1242fec4744c28f9cda3fba60cf Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 00:43:39 +0530 Subject: [PATCH 13/25] Update Platforms --- Package.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Package.swift b/Package.swift index de035f5..20f668c 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,12 @@ import PackageDescription let package = Package( name: "NetworkKit", + platforms: [ + .iOS(.v8), + .macOS(.v10_10), + .tvOS(.v10), + .watchOS(.v4) + ], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. .library( From efd9ea8007eef9959cf8f32235d9a7a652767ae6 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 22:26:14 +0530 Subject: [PATCH 14/25] Added Map Error Publisher. Changed Failure conformance in NKPublisher from NetworkError to Error. --- .../Extensions/NSError+Extension.swift | 15 ++++ .../NetworkKit/Kit/NKResult/NKResult.swift | 2 +- .../Kit/Publishers/Map/Map Error.swift | 58 +++++++++++++++ .../NetworkKit/Kit/Publishers/Map/Map.swift | 72 ------------------- Sources/NetworkKit/Models/API Analytics.swift | 1 + Sources/NetworkKit/Models/Error Model.swift | 1 + ...NetworkError.swift => Network Error.swift} | 0 .../Publisher/NKPublisher+Methods.swift | 10 +++ .../Protocols/Publisher/NKPublisher.swift | 2 +- .../Server/API Representable.swift | 0 .../{ => Protocols}/Server/Environment.swift | 4 +- .../Server/Host Representable.swift | 0 12 files changed, 88 insertions(+), 77 deletions(-) create mode 100644 Sources/NetworkKit/Extensions/NSError+Extension.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift rename Sources/NetworkKit/Protocols/{NetworkError.swift => Network Error.swift} (100%) rename Sources/NetworkKit/{ => Protocols}/Server/API Representable.swift (100%) rename Sources/NetworkKit/{ => Protocols}/Server/Environment.swift (89%) rename Sources/NetworkKit/{ => Protocols}/Server/Host Representable.swift (100%) diff --git a/Sources/NetworkKit/Extensions/NSError+Extension.swift b/Sources/NetworkKit/Extensions/NSError+Extension.swift new file mode 100644 index 0000000..ca5f701 --- /dev/null +++ b/Sources/NetworkKit/Extensions/NSError+Extension.swift @@ -0,0 +1,15 @@ +// +// NSError+Extension.swift +// NetworkKit +// +// Created by Raghav Ahuja on 21/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +extension NSError: NetworkError { + public var errorCode: Int { + code + } +} diff --git a/Sources/NetworkKit/Kit/NKResult/NKResult.swift b/Sources/NetworkKit/Kit/NKResult/NKResult.swift index 8f4fd86..253a167 100644 --- a/Sources/NetworkKit/Kit/NKResult/NKResult.swift +++ b/Sources/NetworkKit/Kit/NKResult/NKResult.swift @@ -8,7 +8,7 @@ import Foundation -public final class NKResult { +public final class NKResult { var result: Result diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift new file mode 100644 index 0000000..eba3a96 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift @@ -0,0 +1,58 @@ +// +// Map Error.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NKPublishers { + + struct MapError: NKPublisher { + + public var result: NKResult + + public var queue: NKQueue { + upstream.queue + } + + public typealias Output = Upstream.Output + + public typealias Failure = MapFailure + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + /// The closure that transforms elements from the upstream publisher. + public let transform: (Upstream.Failure) -> MapFailure + + public init(upstream: Upstream, transform: @escaping (Upstream.Failure) -> MapFailure) { + self.upstream = upstream + self.transform = transform + result = .init() + perform() + } + + private func perform() { + addToQueue { + self.doTransform() + } + } + + private func doTransform() { + let upstreamResult = upstream.result.result + + switch upstreamResult { + case .success(let output): + result.result = .success(output) + + case .failure(let error): + let newError = transform(error) + result.result = .failure(newError) + + } + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift index d1c8d30..05b3ef3 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift @@ -56,75 +56,3 @@ public extension NKPublishers { } } } - - - -//@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -//extension Publishers { -// -// /// A publisher that republishes all non-`nil` results of calling a closure with each received element. -// public struct CompactMap : Publisher where Upstream : Publisher { -// -// /// The kind of errors this publisher might publish. -// /// -// /// Use `Never` if this `Publisher` does not publish errors. -// public typealias Failure = Upstream.Failure -// -// /// The publisher from which this publisher receives elements. -// public let upstream: Upstream -// -// /// A closure that receives values from the upstream publisher and returns optional values. -// public let transform: (Upstream.Output) -> Output? -// -// public init(upstream: Upstream, transform: @escaping (Upstream.Output) -> Output?) -// -// /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)` -// /// -// /// - SeeAlso: `subscribe(_:)` -// /// - Parameters: -// /// - subscriber: The subscriber to attach to this `Publisher`. -// /// once attached it can begin to receive values. -// public func receive(subscriber: S) where Output == S.Input, S : Subscriber, Upstream.Failure == S.Failure -// } -// -// /// A publisher that republishes all non-`nil` results of calling an error-throwing closure with each received element. -// public struct TryCompactMap : Publisher where Upstream : Publisher { -// -// /// The kind of errors this publisher might publish. -// /// -// /// Use `Never` if this `Publisher` does not publish errors. -// public typealias Failure = Error -// -// /// The publisher from which this publisher receives elements. -// public let upstream: Upstream -// -// /// An error-throwing closure that receives values from the upstream publisher and returns optional values. -// /// -// /// If this closure throws an error, the publisher fails. -// public let transform: (Upstream.Output) throws -> Output? -// -// public init(upstream: Upstream, transform: @escaping (Upstream.Output) throws -> Output?) -// -// /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)` -// /// -// /// - SeeAlso: `subscribe(_:)` -// /// - Parameters: -// /// - subscriber: The subscriber to attach to this `Publisher`. -// /// once attached it can begin to receive values. -// public func receive(subscriber: S) where Output == S.Input, S : Subscriber, S.Failure == Publishers.TryCompactMap.Failure -// } -//} - - -///// Calls a closure with each received element and publishes any returned optional that has a value. -///// -///// - Parameter transform: A closure that receives a value and returns an optional value. -///// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure. -//public func compactMap(_ transform: @escaping (Self.Output) -> T?) -> Publishers.CompactMap -// -///// Calls an error-throwing closure with each received element and publishes any returned optional that has a value. -///// -///// If the closure throws an error, the publisher cancels the upstream and sends the thrown error to the downstream receiver as a `Failure`. -///// - Parameter transform: an error-throwing closure that receives a value and returns an optional value. -///// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure. -//public func tryCompactMap(_ transform: @escaping (Self.Output) throws -> T?) -> Publishers.TryCompactMap diff --git a/Sources/NetworkKit/Models/API Analytics.swift b/Sources/NetworkKit/Models/API Analytics.swift index a452636..e350326 100644 --- a/Sources/NetworkKit/Models/API Analytics.swift +++ b/Sources/NetworkKit/Models/API Analytics.swift @@ -9,6 +9,7 @@ import Foundation public struct APIAnalytics { + public let apiName: String public let urlString: String public let totalTime: TimeInterval diff --git a/Sources/NetworkKit/Models/Error Model.swift b/Sources/NetworkKit/Models/Error Model.swift index e0517c9..265ba29 100644 --- a/Sources/NetworkKit/Models/Error Model.swift +++ b/Sources/NetworkKit/Models/Error Model.swift @@ -10,6 +10,7 @@ import Foundation // MARK: - ErrorModel public struct ErrorModel: Codable { + public let businessCode: Int? public let errorCode: Int? public let message: String? diff --git a/Sources/NetworkKit/Protocols/NetworkError.swift b/Sources/NetworkKit/Protocols/Network Error.swift similarity index 100% rename from Sources/NetworkKit/Protocols/NetworkError.swift rename to Sources/NetworkKit/Protocols/Network Error.swift diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift index 5d5ff34..bd38018 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift @@ -120,6 +120,16 @@ public extension NKPublisher { func tryCatch(_ handler: @escaping (Self.Failure) throws -> P) -> NKPublishers.TryCatch where Self.Output == P.Output { NKPublishers.TryCatch(upstream: self, handler: handler) } + + /// Converts any failure from the upstream publisher into a new error. + /// + /// Until the upstream publisher finishes normally or fails with an error, the returned publisher republishes all the elements it receives. + /// + /// - Parameter transform: A closure that takes the upstream failure as a parameter and returns a new error for the publisher to terminate with. + /// - Returns: A publisher that replaces any upstream failure with a new error produced by the `transform` closure. + func mapError(_ transform: @escaping (Self.Failure) -> E) -> NKPublishers.MapError { + NKPublishers.MapError(upstream: self, transform: transform) + } } public extension NKPublisher where Self.Output == NetworkKit.Output, Self.Failure == NetworkKit.Failure { diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift index 82c120c..4c25486 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift @@ -16,7 +16,7 @@ public protocol NKPublisher { /// The kind of errors this publisher might publish. /// /// Use `Never` if this `Publisher` does not publish errors. - associatedtype Failure: NetworkError + associatedtype Failure: Error var result: NKResult { get } diff --git a/Sources/NetworkKit/Server/API Representable.swift b/Sources/NetworkKit/Protocols/Server/API Representable.swift similarity index 100% rename from Sources/NetworkKit/Server/API Representable.swift rename to Sources/NetworkKit/Protocols/Server/API Representable.swift diff --git a/Sources/NetworkKit/Server/Environment.swift b/Sources/NetworkKit/Protocols/Server/Environment.swift similarity index 89% rename from Sources/NetworkKit/Server/Environment.swift rename to Sources/NetworkKit/Protocols/Server/Environment.swift index 874f54a..62268fe 100644 --- a/Sources/NetworkKit/Server/Environment.swift +++ b/Sources/NetworkKit/Protocols/Server/Environment.swift @@ -11,15 +11,13 @@ import Foundation /// Server Environment. public struct Environment: Hashable, Equatable { - public typealias RawValue = String - public var value: String public func hash(into hasher: inout Hasher) { hasher.combine(value) } - public init(value: RawValue) { + public init(value: String) { self.value = value } diff --git a/Sources/NetworkKit/Server/Host Representable.swift b/Sources/NetworkKit/Protocols/Server/Host Representable.swift similarity index 100% rename from Sources/NetworkKit/Server/Host Representable.swift rename to Sources/NetworkKit/Protocols/Server/Host Representable.swift From e1bc65488a5ba4a1d437d5d2257964ffd9dba115 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 23:09:19 +0530 Subject: [PATCH 15/25] Fix Validate Publisher acceptable status code when sent empty --- .../NetworkKit/Kit/Publishers/Validation/Validation.swift | 5 +---- .../NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift index b607a8d..9b9ec2c 100644 --- a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift +++ b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift @@ -35,10 +35,7 @@ public extension NKPublishers { self.upstream = upstream self.shouldCheckForErrorModel = shouldCheckForErrorModel - let sessionCodes = SessionManager.shared.acceptableStatusCodes - let codes = acceptableStatusCodes.isEmpty ? sessionCodes : acceptableStatusCodes - - self.acceptableStatusCodes = codes + self.acceptableStatusCodes = acceptableStatusCodes result = .init() perform() diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift index bd38018..26de681 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift @@ -135,10 +135,10 @@ public extension NKPublisher { public extension NKPublisher where Self.Output == NetworkKit.Output, Self.Failure == NetworkKit.Failure { /// Validates Network call response with provided acceptable status codes. - /// - Parameter acceptableStatusCodes: Acceptable HTTP Status codes for the network call. Default value is `Array(200 < 300)`. + /// - Parameter acceptableStatusCodes: Acceptable HTTP Status codes for the network call. Default value is `Array(200 ..< 300)`. /// - Parameter checkForErrorModel: If publisher should check for custom error model to decode. /// - Returns: A publisher that validates response from an upstream publisher. - func validate(acceptableStatusCodes: [Int] = [], checkForErrorModel: Bool = true) -> NKPublishers.Validate { + func validate(acceptableStatusCodes: [Int] = SessionManager.shared.acceptableStatusCodes, checkForErrorModel: Bool = true) -> NKPublishers.Validate { NKPublishers.Validate(upstream: self, shouldCheckForErrorModel: checkForErrorModel, acceptableStatusCodes: acceptableStatusCodes) } } From 8b9d0713449f1c1c284a4268c3bc51eab583e829 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 23:11:49 +0530 Subject: [PATCH 16/25] Changed NetworkKit to NetworkTask for creating data task Added new initializers for Network Task Added dataTask methods to Session Manager --- Sources/NetworkKit/Kit/NKQueue/NKQueue.swift | 2 +- Sources/NetworkKit/Kit/Network Task.swift | 110 ++++++++++++++++++ Sources/NetworkKit/Kit/NetworkKit.swift | 47 -------- .../Kit/Publishers/NKPublishers.swift | 1 - .../Publishers/Validation/Validation.swift | 6 +- .../Networking/Session Manager.swift | 30 +++++ .../Operations/Fetch Operation.swift | 8 +- .../Publisher/NKPublisher+Methods.swift | 2 +- .../NetworkKit/Request/Network Request.swift | 9 +- Tests/NetworkKitTests/NetworkKitTests.swift | 3 +- 10 files changed, 155 insertions(+), 63 deletions(-) create mode 100644 Sources/NetworkKit/Kit/Network Task.swift delete mode 100644 Sources/NetworkKit/Kit/NetworkKit.swift diff --git a/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift b/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift index 30bbe01..a25c08a 100644 --- a/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift +++ b/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift @@ -21,7 +21,7 @@ public final class NKQueue { init(operationQueue: OperationQueue, request: URLRequest?, apiName: String?) { self.operationQueue = operationQueue self.request = request - self.apiName = apiName ?? "nil" + self.apiName = apiName ?? request?.url?.absoluteString ?? "nil" } func addOperation(_ op: Operation) { diff --git a/Sources/NetworkKit/Kit/Network Task.swift b/Sources/NetworkKit/Kit/Network Task.swift new file mode 100644 index 0000000..c986df5 --- /dev/null +++ b/Sources/NetworkKit/Kit/Network Task.swift @@ -0,0 +1,110 @@ +// +// NetworkTask.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public struct NetworkTask: NKPublisher { + + public var result: NKResult + + public var queue: NKQueue + + public typealias Output = (data: Data, response: HTTPURLResponse) + + public typealias Failure = NKError + + public let request: URLRequest? + + public let session: URLSession + + private let operationQueue: OperationQueue = { + let queue = OperationQueue() + queue.maxConcurrentOperationCount = 1 + queue.qualityOfService = .utility + queue.isSuspended = true + return queue + }() + + /// Creates a data task from the provided Network Request and URL session. + /// - Parameter session: The `URLSession` from `NetworkConfiguration` to create the data task. + /// - Parameter builder: The block which returns a `NetworkRequest` to create a URL session data task. + public init(session: NetworkConfiguration, _ builder: () -> NetworkRequest) { + self.init(session: session.session, builder) + } + + /// Creates a data task from the provided URL request and URL session. + /// - Parameter request: The `URLRequest` from which to create a URL session data task. + /// - Parameter session: The `URLSession` from `NetworkConfiguration` to create the data task. + /// - Parameter apiName: API Name for debug console logging. + public init(request: URLRequest, session: NetworkConfiguration, apiName: String? = nil) { + self.init(request: request, session: session.session, apiName: apiName) + } + + + /// Creates a data task from the provided URL and URL session. + /// - Parameter url: The `URL` from which to create a URL session data task. + /// - Parameter session: The `URLSession` from `NetworkConfiguration` to create the data task. + /// - Parameter apiName: API Name for debug console logging. + public init(url: URL, session: NetworkConfiguration, apiName: String? = nil) { + self.init(url: url, session: session.session, apiName: apiName) + } + + /// Creates a data task from the provided Network Request and URL session. + /// - Parameter session: The `URLSession` to create the data task. + /// - Parameter builder: The block which returns a `NetworkRequest` to create a URL session data task. + public init(session: URLSession, _ builder: () -> NetworkRequest) { + let requestBuilder = DispatchQueue.global(qos: .utility).sync { builder() } + + self.session = session + + request = requestBuilder.request + + result = .init() + queue = .init(operationQueue: operationQueue, + request: requestBuilder.request, + apiName: requestBuilder.apiName) + resume() + } + + /// Creates a data task from the provided URL request and URL session. + /// - Parameter request: The `URLRequest` from which to create a URL session data task. + /// - Parameter session: The `URLSession` to create the data task. + /// - Parameter apiName: API Name for debug console logging. + public init(request: URLRequest, session: URLSession, apiName: String? = nil) { + self.request = request + + self.session = session + + result = .init() + queue = .init(operationQueue: operationQueue, + request: request, + apiName: apiName) + resume() + } + + /// Creates a data task from the provided URL and URL session. + /// - Parameter url: The `URL` from which to create a URL session data task. + /// - Parameter session: The `URLSession` to create the data task. + /// - Parameter apiName: API Name for debug console logging. + public init(url: URL, session: URLSession, apiName: String? = nil) { + request = URLRequest(url: url) + + self.session = session + + result = .init() + queue = .init(operationQueue: operationQueue, + request: request, + apiName: apiName) + resume() + } + + private func resume() { + let fetchOperation = FetchOperation(session: session, request: request, result: result) + addToQueue(isSuspended: true, fetchOperation) + } +} diff --git a/Sources/NetworkKit/Kit/NetworkKit.swift b/Sources/NetworkKit/Kit/NetworkKit.swift deleted file mode 100644 index b11a539..0000000 --- a/Sources/NetworkKit/Kit/NetworkKit.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// NetworkKit.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public struct NetworkKit: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue - - public typealias Output = (data: Data, response: HTTPURLResponse) - - public typealias Failure = NKError - - public let request: URLRequest? - - private let operationQueue: OperationQueue = { - let queue = OperationQueue() - queue.maxConcurrentOperationCount = 1 - queue.qualityOfService = .utility - queue.isSuspended = true - return queue - }() - - public init(_ builder: () -> NetworkRequest) { - - let requestBuilder = DispatchQueue.global(qos: .utility).sync { builder() } - request = requestBuilder.request - - result = .init() - queue = .init(operationQueue: operationQueue, - request: requestBuilder.request, - apiName: requestBuilder.apiName) - resume() - } - - private func resume() { - let fetchOperation = FetchOperation(request: request, result: result) - addToQueue(isSuspended: true, fetchOperation) - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/NKPublishers.swift b/Sources/NetworkKit/Kit/Publishers/NKPublishers.swift index b8f1197..ab99513 100644 --- a/Sources/NetworkKit/Kit/Publishers/NKPublishers.swift +++ b/Sources/NetworkKit/Kit/Publishers/NKPublishers.swift @@ -6,7 +6,6 @@ // Copyright © 2019 Raghav Ahuja. All rights reserved. // - import Foundation public enum NKPublishers { diff --git a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift index 9b9ec2c..5ab0632 100644 --- a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift +++ b/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift @@ -10,7 +10,7 @@ import Foundation public extension NKPublishers { - struct Validate: NKPublisher where Upstream.Output == NetworkKit.Output, Upstream.Failure == NetworkKit.Failure { + struct Validate: NKPublisher where Upstream.Output == NetworkTask.Output, Upstream.Failure == NetworkTask.Failure { public var queue: NKQueue { upstream.queue @@ -18,9 +18,9 @@ public extension NKPublishers { public var result: NKResult - public typealias Output = NetworkKit.Output + public typealias Output = NetworkTask.Output - public typealias Failure = NetworkKit.Failure + public typealias Failure = NetworkTask.Failure /// The publisher from which this publisher receives elements. public let upstream: Upstream diff --git a/Sources/NetworkKit/Networking/Session Manager.swift b/Sources/NetworkKit/Networking/Session Manager.swift index fbb1927..8807e77 100644 --- a/Sources/NetworkKit/Networking/Session Manager.swift +++ b/Sources/NetworkKit/Networking/Session Manager.swift @@ -26,4 +26,34 @@ final public class SessionManager: NetworkConfiguration { Environment.current = .none #endif } + + /// Returns a publisher that wraps a URL session data task for a given Network request. + /// + /// The publisher publishes data when the task completes, or terminates if the task fails with an error. + /// - Parameter builder: The block which returns a `NetworkRequest` to create a URL session data task. + /// - Parameter apiName: API Name for debug console logging. + /// - Returns: A publisher that wraps a data task for the URL request. + public func dataTask(_ builder: () -> NetworkRequest) -> NetworkTask { + NetworkTask(session: session, builder) + } + + /// Returns a publisher that wraps a URL session data task for a given URL request. + /// + /// The publisher publishes data when the task completes, or terminates if the task fails with an error. + /// - Parameter request: The URL request for which to create a data task. + /// - Parameter apiName: API Name for debug console logging. + /// - Returns: A publisher that wraps a data task for the URL request. + public func dataTask(for request: URLRequest, apiName: String? = nil) -> NetworkTask { + NetworkTask(request: request, session: session, apiName: apiName) + } + + /// Returns a publisher that wraps a URL session data task for a given URL. + /// + /// The publisher publishes data when the task completes, or terminates if the task fails with an error. + /// - Parameter url: The URL for which to create a data task. + /// - Parameter apiName: API Name for debug console logging. + /// - Returns: A publisher that wraps a data task for the URL. + public func dataTask(for url: URL, apiName: String? = nil) -> NetworkTask { + NetworkTask(url: url, session: session, apiName: apiName) + } } diff --git a/Sources/NetworkKit/Operations/Fetch Operation.swift b/Sources/NetworkKit/Operations/Fetch Operation.swift index 68c4be6..5bdfdfe 100644 --- a/Sources/NetworkKit/Operations/Fetch Operation.swift +++ b/Sources/NetworkKit/Operations/Fetch Operation.swift @@ -10,12 +10,14 @@ import Foundation class FetchOperation: AsynchronousOperation { - private let result: NKResult? + private let result: NKResult? private let request: URLRequest? + private let session: URLSession var task: URLSessionDataTask? - init(request: URLRequest?, result: NKResult?) { + init(session: URLSession, request: URLRequest?, result: NKResult?) { + self.session = session self.request = request self.result = result super.init() @@ -24,8 +26,6 @@ class FetchOperation: AsynchronousOperation { } override func main() { - let session = SessionManager.shared.session - guard let request = request else { result?.result = .failure(NKError.unsupportedURL(for: nil)) finish() diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift index 26de681..4d9a9fa 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift @@ -132,7 +132,7 @@ public extension NKPublisher { } } -public extension NKPublisher where Self.Output == NetworkKit.Output, Self.Failure == NetworkKit.Failure { +public extension NKPublisher where Self.Output == NetworkTask.Output, Self.Failure == NetworkTask.Failure { /// Validates Network call response with provided acceptable status codes. /// - Parameter acceptableStatusCodes: Acceptable HTTP Status codes for the network call. Default value is `Array(200 ..< 300)`. diff --git a/Sources/NetworkKit/Request/Network Request.swift b/Sources/NetworkKit/Request/Network Request.swift index 5e302f3..50a76fe 100644 --- a/Sources/NetworkKit/Request/Network Request.swift +++ b/Sources/NetworkKit/Request/Network Request.swift @@ -9,16 +9,17 @@ import Foundation public final class NetworkRequest { - private(set) var request: URLRequest? - let apiName: String + public private(set) var request: URLRequest? - init(to endPoint: ConnectionRepresentable) { + public let apiName: String + + public init(to endPoint: ConnectionRepresentable) { request = CreateRequest(with: endPoint, query: nil)?.request apiName = endPoint.name ?? "nil" } - public func urlQuery(_ query: URLQuery) -> NetworkRequest { + public func urlQuery(_ query: URLQuery) -> Self { guard let url = request?.url?.absoluteURL, var components = URLComponents(string: url.absoluteString) else { return self diff --git a/Tests/NetworkKitTests/NetworkKitTests.swift b/Tests/NetworkKitTests/NetworkKitTests.swift index e0e5fb1..ddb60bb 100644 --- a/Tests/NetworkKitTests/NetworkKitTests.swift +++ b/Tests/NetworkKitTests/NetworkKitTests.swift @@ -129,8 +129,7 @@ final class NetworkKitTests: XCTestCase { let expecatation = XCTestExpectation() func testExample() { - - cancellable = NetworkKit { + cancellable = NetworkTask(session: SessionManager.shared) { NetworkRequest(to: MockPoint.allUsers) } .map(\.data) From d355325d7a5be4c4ecff0408e452de06c8ea1645 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 23:14:01 +0530 Subject: [PATCH 17/25] Renamed SessionManager to NKSession --- .../Networking/{Session Manager.swift => NKSession.swift} | 7 +++---- .../Protocols/Publisher/NKPublisher+Methods.swift | 2 +- Tests/NetworkKitTests/NetworkKitTests.swift | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) rename Sources/NetworkKit/Networking/{Session Manager.swift => NKSession.swift} (93%) diff --git a/Sources/NetworkKit/Networking/Session Manager.swift b/Sources/NetworkKit/Networking/NKSession.swift similarity index 93% rename from Sources/NetworkKit/Networking/Session Manager.swift rename to Sources/NetworkKit/Networking/NKSession.swift index 8807e77..0f56e99 100644 --- a/Sources/NetworkKit/Networking/Session Manager.swift +++ b/Sources/NetworkKit/Networking/NKSession.swift @@ -1,5 +1,5 @@ // -// APIManager.swift +// NKSession.swift // NetworkKit // // Created by Raghav Ahuja on 15/10/19. @@ -8,10 +8,9 @@ import Foundation -// MARK: - Session Manager -final public class SessionManager: NetworkConfiguration { +final public class NKSession: NetworkConfiguration { - public static let shared = SessionManager() + public static let shared = NKSession() private init() { super.init(configuration: NetworkConfiguration.defaultConfiguration) diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift index 4d9a9fa..caf0514 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift @@ -138,7 +138,7 @@ public extension NKPublisher where Self.Output == NetworkTask.Output, Self.Failu /// - Parameter acceptableStatusCodes: Acceptable HTTP Status codes for the network call. Default value is `Array(200 ..< 300)`. /// - Parameter checkForErrorModel: If publisher should check for custom error model to decode. /// - Returns: A publisher that validates response from an upstream publisher. - func validate(acceptableStatusCodes: [Int] = SessionManager.shared.acceptableStatusCodes, checkForErrorModel: Bool = true) -> NKPublishers.Validate { + func validate(acceptableStatusCodes: [Int] = NKSession.shared.acceptableStatusCodes, checkForErrorModel: Bool = true) -> NKPublishers.Validate { NKPublishers.Validate(upstream: self, shouldCheckForErrorModel: checkForErrorModel, acceptableStatusCodes: acceptableStatusCodes) } } diff --git a/Tests/NetworkKitTests/NetworkKitTests.swift b/Tests/NetworkKitTests/NetworkKitTests.swift index ddb60bb..ebbc914 100644 --- a/Tests/NetworkKitTests/NetworkKitTests.swift +++ b/Tests/NetworkKitTests/NetworkKitTests.swift @@ -129,7 +129,7 @@ final class NetworkKitTests: XCTestCase { let expecatation = XCTestExpectation() func testExample() { - cancellable = NetworkTask(session: SessionManager.shared) { + cancellable = NetworkTask(session: NKSession.shared) { NetworkRequest(to: MockPoint.allUsers) } .map(\.data) From a9ee86c3a82d3fe59489d7b97b99806154f4f633 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Thu, 21 Nov 2019 23:16:56 +0530 Subject: [PATCH 18/25] Renamed ImageSessionManager to NKImageSession Renamed ImageDownloadDelegate to NKImageSessionDelegate --- ...View+NSImageView+WKInterfaceImage+Extension.swift | 6 +++--- .../NKImageSession+ImageType.swift} | 6 +++--- .../NKImageSession.swift} | 10 +++++----- ...adDelegate.swift => NKImageSessionDelegate.swift} | 12 ++++++------ .../Protocols/Server/API Representable.swift | 1 - 5 files changed, 17 insertions(+), 18 deletions(-) rename Sources/NetworkKit/Networking/{Image Session Manager/ImageSession+ImageType.swift => NKImageSession/NKImageSession+ImageType.swift} (77%) rename Sources/NetworkKit/Networking/{Image Session Manager/Image Session Manager.swift => NKImageSession/NKImageSession.swift} (96%) rename Sources/NetworkKit/Protocols/{ImageDownloadDelegate.swift => NKImageSessionDelegate.swift} (93%) diff --git a/Sources/NetworkKit/Extensions/UIImageView+NSImageView+WKInterfaceImage+Extension.swift b/Sources/NetworkKit/Extensions/UIImageView+NSImageView+WKInterfaceImage+Extension.swift index 0a6af02..649683a 100644 --- a/Sources/NetworkKit/Extensions/UIImageView+NSImageView+WKInterfaceImage+Extension.swift +++ b/Sources/NetworkKit/Extensions/UIImageView+NSImageView+WKInterfaceImage+Extension.swift @@ -10,7 +10,7 @@ import WatchKit -extension WKInterfaceImage: ImageDownloadDelegate { +extension WKInterfaceImage: NKImageSessionDelegate { public var image: ImageType? { get { nil } @@ -26,7 +26,7 @@ extension WKInterfaceImage: ImageDownloadDelegate { import UIKit.UIImage -extension UIImageView: ImageDownloadDelegate { +extension UIImageView: NKImageSessionDelegate { open func prepareForReuse(_ placeholder: ImageType? = nil) { image = placeholder @@ -37,7 +37,7 @@ extension UIImageView: ImageDownloadDelegate { import AppKit.NSImage -extension NSImageView: ImageDownloadDelegate { +extension NSImageView: NKImageSessionDelegate { open func prepareForReuse(_ placeholder: ImageType? = nil) { image = placeholder diff --git a/Sources/NetworkKit/Networking/Image Session Manager/ImageSession+ImageType.swift b/Sources/NetworkKit/Networking/NKImageSession/NKImageSession+ImageType.swift similarity index 77% rename from Sources/NetworkKit/Networking/Image Session Manager/ImageSession+ImageType.swift rename to Sources/NetworkKit/Networking/NKImageSession/NKImageSession+ImageType.swift index ba15a47..4ce2d89 100644 --- a/Sources/NetworkKit/Networking/Image Session Manager/ImageSession+ImageType.swift +++ b/Sources/NetworkKit/Networking/NKImageSession/NKImageSession+ImageType.swift @@ -9,21 +9,21 @@ #if canImport(AppKit) import AppKit.NSImage -public extension ImageSessionManager { +public extension NKImageSession { typealias ImageType = NSImage } #elseif canImport(WatchKit) import UIKit.UIImage -public extension ImageSessionManager { +public extension NKImageSession { typealias ImageType = UIImage } #elseif canImport(UIKit) import UIKit.UIImage -public extension ImageSessionManager { +public extension NKImageSession { typealias ImageType = UIImage } diff --git a/Sources/NetworkKit/Networking/Image Session Manager/Image Session Manager.swift b/Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift similarity index 96% rename from Sources/NetworkKit/Networking/Image Session Manager/Image Session Manager.swift rename to Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift index 8ca441b..c6e9125 100644 --- a/Sources/NetworkKit/Networking/Image Session Manager/Image Session Manager.swift +++ b/Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift @@ -1,5 +1,5 @@ // -// ImageDownloader.swift +// NKImageSession.swift // NetworkKit // // Created by Raghav Ahuja on 15/10/19. @@ -8,10 +8,10 @@ import Foundation -public final class ImageSessionManager: NetworkConfiguration { +public final class NKImageSession: NetworkConfiguration { private typealias ImageValidationResult = (response: HTTPURLResponse, data: Data, image: ImageType) - public static let shared = ImageSessionManager() + public static let shared = NKImageSession() public init(useCache: Bool = true, cacheDiskPath: String? = "cachedImages") { let requestCachePolicy: NSURLRequest.CachePolicy = useCache ? .returnCacheDataElseLoad : .useProtocolCachePolicy @@ -20,7 +20,7 @@ public final class ImageSessionManager: NetworkConfiguration { } } -public extension ImageSessionManager { +public extension NKImageSession { /// Creates a task that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion. /// - Parameter url: URL from where image has to be fetched. @@ -98,7 +98,7 @@ public extension ImageSessionManager { } } -private extension ImageSessionManager { +private extension NKImageSession { /// Validates URL Request's HTTP Response. /// - Parameter response: HTTP URL Response for the provided request. diff --git a/Sources/NetworkKit/Protocols/ImageDownloadDelegate.swift b/Sources/NetworkKit/Protocols/NKImageSessionDelegate.swift similarity index 93% rename from Sources/NetworkKit/Protocols/ImageDownloadDelegate.swift rename to Sources/NetworkKit/Protocols/NKImageSessionDelegate.swift index e5009ef..684ef86 100644 --- a/Sources/NetworkKit/Protocols/ImageDownloadDelegate.swift +++ b/Sources/NetworkKit/Protocols/NKImageSessionDelegate.swift @@ -1,5 +1,5 @@ // -// ImageDownloadDelegate.swift +// NKImageSessionDelegate.swift // NetworkKit // // Created by Raghav Ahuja on 15/10/19. @@ -7,9 +7,9 @@ // import Foundation -public protocol ImageDownloadDelegate: class { +public protocol NKImageSessionDelegate: class { - typealias ImageType = ImageSessionManager.ImageType + typealias ImageType = NKImageSession.ImageType var image: ImageType? { get set } @@ -22,7 +22,7 @@ public protocol ImageDownloadDelegate: class { func prepareForReuse(_ placeholder: ImageType?) } -public extension ImageDownloadDelegate { +public extension NKImageSessionDelegate { /// Fetches Image from provided URL String and sets it on this UIImageView. @@ -50,7 +50,7 @@ public extension ImageDownloadDelegate { } -private extension ImageDownloadDelegate { +private extension NKImageSessionDelegate { @inline(__always) func _fetch(fromUrlString urlString: String?, @@ -94,7 +94,7 @@ private extension ImageDownloadDelegate { image = placeholder } - return ImageSessionManager.shared.fetch(from: url) { [weak self] (result) in + return NKImageSession.shared.fetch(from: url) { [weak self] (result) in switch result { case .success(let newImage): if flag { diff --git a/Sources/NetworkKit/Protocols/Server/API Representable.swift b/Sources/NetworkKit/Protocols/Server/API Representable.swift index 24d09b0..e8ebf40 100644 --- a/Sources/NetworkKit/Protocols/Server/API Representable.swift +++ b/Sources/NetworkKit/Protocols/Server/API Representable.swift @@ -8,7 +8,6 @@ import Foundation -//MARK:- API Type public protocol APIRepresentable { /// Sub URL for API Type. It may include server environment for the api, it can be `current` environment. From 2f325e812ecbfe09300ae356144f55964381ad08 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Sat, 23 Nov 2019 22:28:42 +0530 Subject: [PATCH 19/25] Update documentation Added BaseBlockOperation Replaced NKError with NSError Fix cancel error Fix mapError crash due to force type cast --- .../Connection/Connection Representable.swift | 34 +++++ .../NetworkKit/Errors/Business Error.swift | 2 +- Sources/NetworkKit/Errors/HTTP Error.swift | 4 + Sources/NetworkKit/Errors/NKError.swift | 125 ------------------ .../Extensions/NSError+Extension.swift | 89 ++++++++++++- .../Extensions/Never+Extension.swift | 14 -- Sources/NetworkKit/Kit/NKQueue/NKQueue.swift | 8 +- .../NetworkKit/Kit/NKResult/NKResult.swift | 6 +- Sources/NetworkKit/Kit/Network Task.swift | 2 +- .../Kit/Publishers/Assign/Assign.swift | 16 ++- .../Publishers/Completion/Completion.swift | 4 +- .../Kit/Publishers/Debounce/Debounce.swift | 6 +- .../Kit/Publishers/Decode/Decode.swift | 11 +- .../Kit/Publishers/Map/Map Error.swift | 4 +- .../Kit/Publishers/Map/Map Keypath 2.swift | 3 + .../Kit/Publishers/Map/Map Keypath.swift | 4 +- .../NetworkKit/Kit/Publishers/Map/Map.swift | 2 + .../Kit/Publishers/Map/Try Map.swift | 9 +- .../Replace Error/Replace Error.swift | 3 + .../Validate.swift} | 10 +- .../Configuration/Network Configuration.swift | 12 +- .../NKImageSession/NKImageSession.swift | 20 +-- .../Operations/Base Block Operation.swift | 28 ++++ .../Operations/Catch Operation.swift | 16 ++- .../Operations/Debouce Operation.swift | 14 +- .../Operations/Fetch Operation.swift | 14 +- .../Operations/TryCatch Operation.swift | 24 +++- .../Protocols/Cancellable/Cancellable.swift | 1 + .../NetworkKit/Protocols/Network Error.swift | 13 -- .../Publisher/NKPublisher+Queue.swift | 2 +- .../Protocols/Server/API Representable.swift | 29 +++- .../Protocols/Server/Environment.swift | 22 ++- .../Protocols/Server/Host Representable.swift | 10 +- 33 files changed, 331 insertions(+), 230 deletions(-) delete mode 100644 Sources/NetworkKit/Errors/NKError.swift delete mode 100644 Sources/NetworkKit/Extensions/Never+Extension.swift rename Sources/NetworkKit/Kit/Publishers/{Validation/Validation.swift => Validate/Validate.swift} (95%) create mode 100644 Sources/NetworkKit/Operations/Base Block Operation.swift delete mode 100644 Sources/NetworkKit/Protocols/Network Error.swift diff --git a/Sources/NetworkKit/Connection/Connection Representable.swift b/Sources/NetworkKit/Connection/Connection Representable.swift index 3dbd70e..35f09ea 100644 --- a/Sources/NetworkKit/Connection/Connection Representable.swift +++ b/Sources/NetworkKit/Connection/Connection Representable.swift @@ -8,18 +8,52 @@ import Foundation +/** + A type that represents as a connection or an endpoint. + + ``` + let url = "https://api.example.com/users/all" + // `/users/all` is a connection. + ``` + */ public protocol ConnectionRepresentable { + + /** + The path subcomponent. It is the connection endpoint for the url. + + ``` + let url = "https://api.example.com/users/all" + // `/users/all` is the path for this connection + ``` + + Setting this property assumes the subcomponent or component string is not percent encoded and will add percent encoding (if the component allows percent encoding). + */ var path: String { get } + + /// Connection name if any. Use for console logging. Defaults to connection url if provided `nil`. var name: String? { get } // for console logging purposes only + + /// HTTP Method for the connection request. var method: HTTPMethod { get } + + /// Default Headers attached to connection request. Example: ["User-Agent": "iOS_13_0"] var httpHeaders: HTTPHeaderParameters { get } + + /// The scheme subcomponent of the URL. Default value is `.https` var scheme: Scheme { get } + + /// Host for the connection. var host: HostRepresentable { get } + + /// Default URL Query for connection. Example: ["client": "ios"] var defaultQuery: URLQuery? { get } + + /// API Type for connection. Default value is `host.defaultAPIType`. var apiType: APIRepresentable? { get } } public extension ConnectionRepresentable { + var scheme: Scheme { .https } var apiType: APIRepresentable? { host.defaultAPIType } diff --git a/Sources/NetworkKit/Errors/Business Error.swift b/Sources/NetworkKit/Errors/Business Error.swift index 19ffc77..fd6f8d9 100644 --- a/Sources/NetworkKit/Errors/Business Error.swift +++ b/Sources/NetworkKit/Errors/Business Error.swift @@ -9,7 +9,7 @@ import Foundation /// Personal / Business / Server Errors -enum BusinessError: NetworkError { +enum BusinessError: LocalizedError { case errorModel(ErrorModel, Int) diff --git a/Sources/NetworkKit/Errors/HTTP Error.swift b/Sources/NetworkKit/Errors/HTTP Error.swift index ec751b9..f5f8c85 100644 --- a/Sources/NetworkKit/Errors/HTTP Error.swift +++ b/Sources/NetworkKit/Errors/HTTP Error.swift @@ -100,6 +100,10 @@ enum HTTPStatusCode: Int, LocalizedError { var localizedDescription: String { return HTTPURLResponse.localizedString(forStatusCode: rawValue) } + + var errorDescription: String? { + return HTTPURLResponse.localizedString(forStatusCode: rawValue) + } } extension HTTPStatusCode { diff --git a/Sources/NetworkKit/Errors/NKError.swift b/Sources/NetworkKit/Errors/NKError.swift deleted file mode 100644 index 0bb8481..0000000 --- a/Sources/NetworkKit/Errors/NKError.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// NKError.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public struct NKError: NetworkError { - public let localizedDescription: String - public let errorDescription: String? - - public let errorCode: Int - - init(_ httpError: HTTPStatusCode) { - errorCode = httpError.rawValue - localizedDescription = httpError.localizedDescription - errorDescription = localizedDescription - } - - public init(_ error: NSError) { - localizedDescription = error.localizedDescription - errorCode = error.code - - if error.domain == NSCocoaErrorDomain { - errorDescription = error.userInfo[NSDebugDescriptionErrorKey] as? String - - } else if error.domain == NSURLErrorDomain { - let failingURL = error.userInfo[NSURLErrorFailingURLStringErrorKey] as? String - let description = "Failing URL: \(failingURL ?? "nil"). \(localizedDescription)" - errorDescription = description - - } else { - errorDescription = localizedDescription - } - } - - init(_ error: NetworkError) { - errorCode = error.errorCode - localizedDescription = error.localizedDescription - errorDescription = error.errorDescription - } - - static func validationCancelled(for url: URL) -> NKError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: [ - NSURLErrorFailingURLErrorKey: url, - NSLocalizedDescriptionKey: "Response validation cancelled." - ]) - - return NKError(error) - } - - static func badServerResponse(for url: URL) -> NKError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorBadServerResponse, userInfo: [ - NSURLErrorFailingURLErrorKey: url, - NSLocalizedDescriptionKey: "Bad server response for request : \(url.absoluteString)" - ]) - - return NKError(error) - } - - static func resourceUnavailable(for url: URL) -> NKError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorResourceUnavailable, userInfo: [ - NSURLErrorFailingURLErrorKey: url, - NSLocalizedDescriptionKey: "A requested resource couldn’t be retrieved from url: \(url.absoluteString)." - ]) - - return NKError(error) - } - - static func unsupportedURL(for url: URL?) -> NKError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnsupportedURL, userInfo: [ - NSURLErrorFailingURLErrorKey: url ?? "nill", - NSLocalizedDescriptionKey: "A requested resource couldn’t be retrieved from url: \(url?.absoluteString ?? "nil")." - ]) - - return NKError(error) - } - - static func zeroByteResource(for url: URL) -> NKError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorZeroByteResource, userInfo: [ - NSURLErrorFailingURLErrorKey: url, - NSLocalizedDescriptionKey: "A server reported that a URL has a non-zero content length, but terminated the network connection gracefully without sending any data." - ]) - - return NKError(error) - } - - static func cannotDecodeContentData(for url: URL) -> NKError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotDecodeContentData, userInfo: [ - NSURLErrorFailingURLErrorKey: url, - NSLocalizedDescriptionKey: "Content data received during a connection request had an unknown content encoding." - ]) - - return NKError(error) - } - - static func cannotDecodeRawData(for url: URL) -> NKError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotDecodeRawData, userInfo: [ - NSURLErrorFailingURLErrorKey: url, - NSLocalizedDescriptionKey: "Content data received during a connection request had an unknown content encoding." - ]) - - return NKError(error) - } - - static func notStarted(for url: URL?) -> NKError { - let error = NSError(domain: NSURLErrorDomain, code: NSUserCancelledError, userInfo: [ - NSURLErrorFailingURLErrorKey: url ?? "nil", - NSLocalizedDescriptionKey: "An asynchronous load has been canceled or not started." - ]) - - return NKError(error) - } - - static func unkown() -> NKError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: [ - NSLocalizedDescriptionKey: "An Unknown Occurred." - ]) - - return NKError(error) - } -} diff --git a/Sources/NetworkKit/Extensions/NSError+Extension.swift b/Sources/NetworkKit/Extensions/NSError+Extension.swift index ca5f701..b970565 100644 --- a/Sources/NetworkKit/Extensions/NSError+Extension.swift +++ b/Sources/NetworkKit/Extensions/NSError+Extension.swift @@ -8,8 +8,91 @@ import Foundation -extension NSError: NetworkError { - public var errorCode: Int { - code +extension NSError { + + static func cancelled(for url: URL?) -> NSError { + var userInfo: [String: Any] = [NSLocalizedDescriptionKey: "User cancelled the task for url: \(url?.absoluteString ?? "nil")."] + if let url = url { + userInfo[NSURLErrorFailingURLErrorKey] = url + } + + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: userInfo) + + return error + } + + static func badServerResponse(for url: URL) -> NSError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorBadServerResponse, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "Bad server response for request : \(url.absoluteString)" + ]) + + return error + } + + static func resourceUnavailable(for url: URL) -> NSError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorResourceUnavailable, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "A requested resource couldn’t be retrieved from url: \(url.absoluteString)." + ]) + + return error + } + + static func unsupportedURL(for url: URL?) -> NSError { + var userInfo: [String: Any] = [NSLocalizedDescriptionKey: "A requested resource couldn’t be retrieved from url: \(url?.absoluteString ?? "nil")."] + if let url = url { + userInfo[NSURLErrorFailingURLErrorKey] = url + } + + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnsupportedURL, userInfo: userInfo) + + return error + } + + static func zeroByteResource(for url: URL) -> NSError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorZeroByteResource, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "A server reported that a URL has a non-zero content length, but terminated the network connection gracefully without sending any data." + ]) + + return error + } + + static func cannotDecodeContentData(for url: URL) -> NSError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotDecodeContentData, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "Content data received during a connection request had an unknown content encoding." + ]) + + return error + } + + static func cannotDecodeRawData(for url: URL) -> NSError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotDecodeRawData, userInfo: [ + NSURLErrorFailingURLErrorKey: url, + NSLocalizedDescriptionKey: "Content data received during a connection request had an unknown content encoding." + ]) + + return error + } + + static func notStarted(for url: URL?) -> NSError { + var userInfo: [String: Any] = [NSLocalizedDescriptionKey: "An asynchronous load has been canceled or not started."] + if let url = url { + userInfo[NSURLErrorFailingURLErrorKey] = url + } + + let error = NSError(domain: NSURLErrorDomain, code: NSUserCancelledError, userInfo: userInfo) + + return error + } + + static func unkown() -> NSError { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: [ + NSLocalizedDescriptionKey: "An Unknown Occurred." + ]) + + return error } } diff --git a/Sources/NetworkKit/Extensions/Never+Extension.swift b/Sources/NetworkKit/Extensions/Never+Extension.swift deleted file mode 100644 index 0d801b8..0000000 --- a/Sources/NetworkKit/Extensions/Never+Extension.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Never+Extension.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -extension Never: NetworkError { - - public var errorCode: Int { -150716 } -} diff --git a/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift b/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift index a25c08a..9970e0d 100644 --- a/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift +++ b/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift @@ -10,9 +10,13 @@ import Foundation public final class NKQueue { + /// A queue that regulates the execution of operations. + /// It is a serial queue, that has `quality of service` of `utility`. let operationQueue: OperationQueue - var request: URLRequest? - var apiName: String + + let request: URLRequest? + + let apiName: String var operations: [Operation] { operationQueue.operations diff --git a/Sources/NetworkKit/Kit/NKResult/NKResult.swift b/Sources/NetworkKit/Kit/NKResult/NKResult.swift index 253a167..b78952c 100644 --- a/Sources/NetworkKit/Kit/NKResult/NKResult.swift +++ b/Sources/NetworkKit/Kit/NKResult/NKResult.swift @@ -10,16 +10,16 @@ import Foundation public final class NKResult { - var result: Result + var result: Result? var operation: Operation? - init(result: Result) { + init(result: Result?) { self.result = result } init() { - let error = NKError.notStarted(for: nil) + let error = NSError.notStarted(for: nil) result = .failure(error as! Failure) } } diff --git a/Sources/NetworkKit/Kit/Network Task.swift b/Sources/NetworkKit/Kit/Network Task.swift index c986df5..34314e9 100644 --- a/Sources/NetworkKit/Kit/Network Task.swift +++ b/Sources/NetworkKit/Kit/Network Task.swift @@ -16,7 +16,7 @@ public struct NetworkTask: NKPublisher { public typealias Output = (data: Data, response: HTTPURLResponse) - public typealias Failure = NKError + public typealias Failure = NSError public let request: URLRequest? diff --git a/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift b/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift index 6c9d1d2..8aa441c 100644 --- a/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift +++ b/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift @@ -15,26 +15,30 @@ extension NKPublishers { /// The publisher that this publisher receives elements from. public let upstream: Upstream - public let root: Root + /// The object on which to assign the value. + public let object: Root + /// The key path of the property to assign. public let keyPath: ReferenceWritableKeyPath - public init(upstream: Upstream, to keyPath: ReferenceWritableKeyPath, on root: Root) { + public init(upstream: Upstream, to keyPath: ReferenceWritableKeyPath, on object: Root) { self.upstream = upstream - self.root = root + self.object = object self.keyPath = keyPath assign() } private func assign() { addToQueue { - let value = try! self.upstream.result.result.get() - self.root[keyPath: self.keyPath] = value + guard let value = try? self.upstream.result.result?.get() else { + return + } + self.object[keyPath: self.keyPath] = value } } private func addToQueue(_ block: @escaping () -> Void) { - let op = BlockOperation(block: block) + let op = BaseBlockOperation(request: upstream.queue.request, result: upstream.result, block: block) upstream.queue.addOperation(op) } diff --git a/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift b/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift index 24fb4cd..389e39d 100644 --- a/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift +++ b/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift @@ -26,12 +26,12 @@ extension NKPublishers { private func completion() { addToQueue { - self.block(self.upstream.result.result) + self.block(self.upstream.result.result!) } } private func addToQueue(_ block: @escaping () -> Void) { - let op = BlockOperation(block: block) + let op = BaseBlockOperation(request: upstream.queue.request, result: upstream.result, block: block) upstream.queue.addOperation(op) } diff --git a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift index ab33b6b..5573b05 100644 --- a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift +++ b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift @@ -36,13 +36,9 @@ public extension NKPublishers { } private func perform() { - let operation = DebounceOperation(dueTime: dueTime) + let operation = DebounceOperation(result: result, url: queue.request?.url, dueTime: dueTime) queue.operations.first?.addDependency(operation) addToQueue(operation) } - - public func cancel() { - upstream.queue.operationQueue.cancelAllOperations() - } } } diff --git a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift index 907333a..d7020f1 100644 --- a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift +++ b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift @@ -20,7 +20,7 @@ public extension NKPublishers { public typealias Output = Item - public typealias Failure = NKError + public typealias Failure = NSError /// The publisher that this publisher receives elements from. public let upstream: Upstream @@ -62,13 +62,14 @@ public extension NKPublishers { self.result.result = .success(output) } catch { - let nkError = NKError(error as NSError) - result.result = .failure(nkError) + result.result = .failure(error as NSError) } case .failure(let error): - let nkError = NKError(error as NSError) - result.result = .failure(nkError) + result.result = .failure(error as NSError) + + case .none: + result.result = .none } } } diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift index eba3a96..d266aac 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift @@ -31,7 +31,7 @@ public extension NKPublishers { public init(upstream: Upstream, transform: @escaping (Upstream.Failure) -> MapFailure) { self.upstream = upstream self.transform = transform - result = .init() + result = .init(result: .none) perform() } @@ -52,6 +52,8 @@ public extension NKPublishers { let newError = transform(error) result.result = .failure(newError) + case .none: + result.result = .none } } } diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift index 8cef393..f05e589 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift @@ -57,6 +57,9 @@ extension NKPublishers { case .failure(let error): result.result = .failure(error) + + case .none: + result.result = .none } } } diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift index 5276260..decd805 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift @@ -47,8 +47,10 @@ public extension NKPublishers { case .failure(let error): result.result = .failure(error) + + case .none: + result.result = .none } } } - } diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift index 05b3ef3..e6ab7d9 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift @@ -52,6 +52,8 @@ public extension NKPublishers { case .failure(let error): result.result = .failure(error) + case .none: + result.result = .none } } } diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift index e6980e3..a929cae 100644 --- a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift +++ b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift @@ -51,13 +51,16 @@ public extension NKPublishers { let newOutput = try transform(output) result.result = .success(newOutput) - } catch let tranformError { - let error = tranformError as NSError - result.result = .failure(NKError(error) as! Upstream.Failure) + } catch { + let error = error as NSError + result.result = .failure(error as! Failure) } case .failure(let error): result.result = .failure(error) + + case .none: + result.result = .none } } } diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift index 65bb841..73620c2 100644 --- a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift +++ b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift @@ -50,6 +50,9 @@ public extension NKPublishers { case .failure: result.result = .success(output) + + case .none: + result.result = .none } } } diff --git a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift b/Sources/NetworkKit/Kit/Publishers/Validate/Validate.swift similarity index 95% rename from Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift rename to Sources/NetworkKit/Kit/Publishers/Validate/Validate.swift index 5ab0632..44598a5 100644 --- a/Sources/NetworkKit/Kit/Publishers/Validation/Validation.swift +++ b/Sources/NetworkKit/Kit/Publishers/Validate/Validate.swift @@ -1,5 +1,5 @@ // -// Validation.swift +// Validate.swift // NetworkKit // // Created by Raghav Ahuja on 18/11/19. @@ -53,7 +53,7 @@ public extension NKPublishers { return } - guard let (data, response) = try? upstream.result.result.get() else { + guard let (data, response) = try? upstream.result.result?.get() else { result.result = upstream.result.result return } @@ -102,14 +102,14 @@ public extension NKPublishers { let model = try? JSONDecoder().decode(ErrorModel.self, from: data) if let errorModel = model { let error = BusinessError.errorModel(errorModel, response.statusCode) - result.result = .failure(NKError(error)) + result.result = .failure(error as NSError) return } // else throw http or url error - if let code = HTTPStatusCode(rawValue: response.statusCode) { - result.result = .failure(.init(code)) + if let httpError = HTTPStatusCode(rawValue: response.statusCode) { + result.result = .failure(httpError as NSError) } else { result.result = .failure(.badServerResponse(for: url)) } diff --git a/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift b/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift index 542794a..90648f2 100644 --- a/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift +++ b/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift @@ -8,7 +8,7 @@ import Foundation -// MARK: - NETWORKING +// MARK: - NETWORK CONFIGURATION /// A Class that coordinates a group of related network data transfer tasks. open class NetworkConfiguration { @@ -151,16 +151,14 @@ extension NetworkConfiguration { // MARK: - SERVER ENVIRONMENT public extension NetworkConfiguration { - /// Changes Server Environment. - /// - Parameter newEnvironment: New Environment type to be set. - @discardableResult - static func changeEnvironment(_ newEnvironment: Environment) -> Bool { + /// Updates the current environment. + /// - Parameter newEnvironment: New Server Environment to be set. + static func updateEnvironment(_ newEnvironment: Environment) { Environment.current = newEnvironment UserDefaults.standard.set(newEnvironment.value, forKey: "api_environment") - return true } - /// Returns Current Server Environment set. + /// Returns the current environment. static var currentEnvironment: Environment? { return Environment.current } diff --git a/Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift b/Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift index c6e9125..5d88903 100644 --- a/Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift +++ b/Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift @@ -9,6 +9,7 @@ import Foundation public final class NKImageSession: NetworkConfiguration { + private typealias ImageValidationResult = (response: HTTPURLResponse, data: Data, image: ImageType) public static let shared = NKImageSession() @@ -26,9 +27,9 @@ public extension NKImageSession { /// - Parameter url: URL from where image has to be fetched. /// - Parameter useCache: Flag which allows Response and Data to be cached. /// - Parameter completion: The completion handler to call when the load request is complete. This handler is executed on the main queue. This completion handler takes the Result as parameter. On Success, it returns the image. On Failure, returns URLError. - /// - Returns: **URLSessionTask** for further operations. + /// - Returns: **URLSessionDataTask** for further operations. @discardableResult - func fetch(from url: URL, cacheImage useCache: Bool = true, completion: @escaping (Result) -> ()) -> URLSessionDataTask { + func fetch(from url: URL, cacheImage useCache: Bool = true, completion: @escaping (Result) -> ()) -> URLSessionDataTask { let requestCachePolicy: NSURLRequest.CachePolicy = useCache ? .returnCacheDataElseLoad : .reloadIgnoringLocalCacheData let request = URLRequest(url: url, cachePolicy: requestCachePolicy, timeoutInterval: session.configuration.timeoutIntervalForRequest) @@ -37,7 +38,7 @@ public extension NKImageSession { guard let `self` = self else { let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil) - completion(.failure(.init(error))) + completion(.failure(error)) return } @@ -58,7 +59,7 @@ public extension NKImageSession { #endif DispatchQueue.main.async { - completion(.failure(.init(error))) + completion(.failure(error)) } return } @@ -104,7 +105,7 @@ private extension NKImageSession { /// - Parameter response: HTTP URL Response for the provided request. /// - Parameter data: Response Data containing Image Data sent by server. /// - Returns: Result Containing Image on success or URL Error if validation fails. - private func validateImageResponse(url: URL, response: URLResponse?, data: Data?) -> Result { + private func validateImageResponse(url: URL, response: URLResponse?, data: Data?) -> Result { guard let httpURLResponse = response as? HTTPURLResponse, acceptableStatusCodes.contains(httpURLResponse.statusCode), let mimeType = httpURLResponse.mimeType, mimeType.hasPrefix("image") else { @@ -115,17 +116,10 @@ private extension NKImageSession { return .failure(.zeroByteResource(for: url)) } - guard let image = getImage(from: &data) else { + guard let image = ImageType.initialize(using: &data) else { return .failure(.cannotDecodeRawData(for: url)) } return .success((httpURLResponse, data, image)) } - - /// Initializes and returns the image object with the specified data and scale factor. - /// - Parameter data: The data object containing the image data. - /// - Returns: An initialized UIImage object, or nil if the method could not initialize the image from the specified data. - private func getImage(from data: inout Data) -> ImageType? { - return ImageType.initialize(using: &data) - } } diff --git a/Sources/NetworkKit/Operations/Base Block Operation.swift b/Sources/NetworkKit/Operations/Base Block Operation.swift new file mode 100644 index 0000000..f86ce7b --- /dev/null +++ b/Sources/NetworkKit/Operations/Base Block Operation.swift @@ -0,0 +1,28 @@ +// +// Base Block Operation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 23/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +class BaseBlockOperation: BlockOperation { + + var result: NKResult + var request: URLRequest? + + init(request: URLRequest?, result: NKResult, block: @escaping () -> Void) { + self.request = request + self.result = result + super.init() + addExecutionBlock(block) + } + + override func cancel() { + let error = NSError.cancelled(for: request?.url) + result.result = .failure(error as! Failure) + super.cancel() + } +} diff --git a/Sources/NetworkKit/Operations/Catch Operation.swift b/Sources/NetworkKit/Operations/Catch Operation.swift index df2dea2..7bb64ba 100644 --- a/Sources/NetworkKit/Operations/Catch Operation.swift +++ b/Sources/NetworkKit/Operations/Catch Operation.swift @@ -22,6 +22,8 @@ final class CatchOperation: As /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. private let handler: (Upstream.Failure) -> NewPublisher + private var newOperation: Operation? + init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher, result: NKResult) { self.upstream = upstream self.handler = handler @@ -40,8 +42,10 @@ final class CatchOperation: As return } + self.newOperation = newOperation + newOperation.completionBlock = { [weak self] in - switch newPublisher.result.result { + switch newPublisher.result.result! { case .success(let output): self?.result.result = .success(output) @@ -54,6 +58,16 @@ final class CatchOperation: As } newOperation.main() + + case .none: + result.result = .none } } + + override func cancel() { + newOperation?.cancel() + let error = NSError.cancelled(for: upstream.queue.request?.url) + result.result = .failure(error as! Failure) + super.cancel() + } } diff --git a/Sources/NetworkKit/Operations/Debouce Operation.swift b/Sources/NetworkKit/Operations/Debouce Operation.swift index f3e39f6..494299f 100644 --- a/Sources/NetworkKit/Operations/Debouce Operation.swift +++ b/Sources/NetworkKit/Operations/Debouce Operation.swift @@ -8,11 +8,15 @@ import Foundation -class DebounceOperation: AsynchronousOperation { +final class DebounceOperation: AsynchronousOperation { let dueTime: DispatchTime + let result: NKResult + let url: URL? - init(dueTime: DispatchTime) { + init(result: NKResult, url: URL?, dueTime: DispatchTime) { + self.result = result + self.url = url self.dueTime = dueTime super.init() queuePriority = .veryHigh @@ -23,4 +27,10 @@ class DebounceOperation: AsynchronousOperation { self?.finish() } } + + override func cancel() { + let error = NSError.cancelled(for: url) + result.result = .failure(error as! Failure) + super.cancel() + } } diff --git a/Sources/NetworkKit/Operations/Fetch Operation.swift b/Sources/NetworkKit/Operations/Fetch Operation.swift index 5bdfdfe..a2de4ac 100644 --- a/Sources/NetworkKit/Operations/Fetch Operation.swift +++ b/Sources/NetworkKit/Operations/Fetch Operation.swift @@ -8,10 +8,12 @@ import Foundation -class FetchOperation: AsynchronousOperation { +final class FetchOperation: AsynchronousOperation { private let result: NKResult? + private let request: URLRequest? + private let session: URLSession var task: URLSessionDataTask? @@ -27,14 +29,14 @@ class FetchOperation: AsynchronousOperation { override func main() { guard let request = request else { - result?.result = .failure(NKError.unsupportedURL(for: nil)) + result?.result = .failure(NSError.unsupportedURL(for: nil) as NSError) finish() return } task = session.dataTask(with: request) { [weak self] (data, response, error) in if let error = error as NSError? { - self?.result?.result = .failure(NKError(error)) + self?.result?.result = .failure(error) } else if let response = response as? HTTPURLResponse, let data = data { self?.result?.result = .success((data, response)) } @@ -44,4 +46,10 @@ class FetchOperation: AsynchronousOperation { task?.resume() } + + override func cancel() { + result?.result = .failure(NSError.cancelled(for: request?.url) as NSError) + task?.cancel() + super.cancel() + } } diff --git a/Sources/NetworkKit/Operations/TryCatch Operation.swift b/Sources/NetworkKit/Operations/TryCatch Operation.swift index 8e12083..aa7a8fa 100644 --- a/Sources/NetworkKit/Operations/TryCatch Operation.swift +++ b/Sources/NetworkKit/Operations/TryCatch Operation.swift @@ -28,6 +28,8 @@ final class TryCatchOperation: self.result = result } + private var newOperation: Operation? + override func main() { switch upstream.result.result { case .success(let output): @@ -39,12 +41,15 @@ final class TryCatchOperation: let newPublisher = try handler(error) guard let newOperation = newPublisher.result.operation else { - result.result = .failure(NKError.unkown() as! NewPublisher.Failure) + let failError = NSError.unkown() + result.result = .failure(failError as! Failure) return } + self.newOperation = newOperation + newOperation.completionBlock = { [weak self] in - switch newPublisher.result.result { + switch newPublisher.result.result! { case .success(let output): self?.result.result = .success(output) @@ -58,10 +63,21 @@ final class TryCatchOperation: newOperation.main() - } catch let handlerError { - result.result = .failure(NKError(handlerError as NSError) as! NewPublisher.Failure) + } catch { + let error = error as NSError + result.result = .failure(error as! Failure) finish() } + + case .none: + result.result = .none } } + + override func cancel() { + newOperation?.cancel() + let error = NSError.cancelled(for: upstream.queue.request?.url) + result.result = .failure(error as! Failure) + super.cancel() + } } diff --git a/Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift b/Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift index b46b9ce..da790e4 100644 --- a/Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift +++ b/Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift @@ -9,5 +9,6 @@ import Foundation public protocol NetworkCancellable { + func cancel() } diff --git a/Sources/NetworkKit/Protocols/Network Error.swift b/Sources/NetworkKit/Protocols/Network Error.swift deleted file mode 100644 index 3a58954..0000000 --- a/Sources/NetworkKit/Protocols/Network Error.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Network Error.swift -// NetworkKit -// -// Created by Raghav Ahuja on 21/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public protocol NetworkError: LocalizedError { - var errorCode: Int { get } -} diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift index 5311852..2efcbf3 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift @@ -11,7 +11,7 @@ import Foundation extension NKPublisher { func addToQueue(isSuspended: Bool = false, _ block: @escaping () -> Void) { - let op = BlockOperation(block: block) + let op = BaseBlockOperation(request: queue.request, result: result, block: block) result.operation = op queue.addOperation(op) queue(isSuspended: isSuspended) diff --git a/Sources/NetworkKit/Protocols/Server/API Representable.swift b/Sources/NetworkKit/Protocols/Server/API Representable.swift index e8ebf40..3d0a7ae 100644 --- a/Sources/NetworkKit/Protocols/Server/API Representable.swift +++ b/Sources/NetworkKit/Protocols/Server/API Representable.swift @@ -8,11 +8,36 @@ import Foundation +/** + A type that represents server api. It can also be used for managing server environment in URL. + + ``` + let url = "https://api-staging.example.com/v1/users/all" + // `api` is a Server API. + // `staging` is Server Environment. + ``` + */ public protocol APIRepresentable { - /// Sub URL for API Type. It may include server environment for the api, it can be `current` environment. + /** + Sub URL for API. + + It may include server environment for the api. + Use **Environment.current** to maintain environment. + ``` + let url = "https://api-staging.example.com/users/all" + // `api-staging` is sub url. + ``` + */ var subUrl: String { get } - /// EndPoint for API Type. + /** + EndPoint for API. + + ``` + let url = "https://api-staging.example.com/v1/users/all" + // `/v1` is api endpoint. + ``` + */ var endPoint: String { get } } diff --git a/Sources/NetworkKit/Protocols/Server/Environment.swift b/Sources/NetworkKit/Protocols/Server/Environment.swift index 62268fe..016dcb5 100644 --- a/Sources/NetworkKit/Protocols/Server/Environment.swift +++ b/Sources/NetworkKit/Protocols/Server/Environment.swift @@ -8,14 +8,24 @@ import Foundation -/// Server Environment. +/** + Server Environment. + + ``` + let url = "https://api-staging.example.com/v1/users/all" + // `staging` is Server Environment. + ``` + + It has a `current` property for maintaining the server environment. + + To update the `current` environment, use `NetworkConfiguration.updateEnvironment(:_)`. + + In `DEBUG` mode, it persists the `current` value in `UserDefaults`. + */ public struct Environment: Hashable, Equatable { - public var value: String - - public func hash(into hasher: inout Hasher) { - hasher.combine(value) - } + /// String value of the environment + public let value: String public init(value: String) { self.value = value diff --git a/Sources/NetworkKit/Protocols/Server/Host Representable.swift b/Sources/NetworkKit/Protocols/Server/Host Representable.swift index 4a82c33..8c50c9a 100644 --- a/Sources/NetworkKit/Protocols/Server/Host Representable.swift +++ b/Sources/NetworkKit/Protocols/Server/Host Representable.swift @@ -8,7 +8,15 @@ import Foundation -/// Host URLs for the app. +/** +A type that represents a URL host. + +``` +let url = "https://api.example.com/users/all" +// `example.com` is a host. +``` + +*/ public protocol HostRepresentable { var host: String { get } From ae5b9d518cf63a24593487999aa378abcde26570 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Tue, 26 Nov 2019 00:04:30 +0530 Subject: [PATCH 20/25] Replaced Assign and Completion Publishers with Operation subclasses Added Replace Error Operation Added NKAnyCancellable Renamed NetworkCancellable -> xv --- .../NKAnyCancellable/NKAnyCancellable.swift | 33 +++++++++++++ Sources/NetworkKit/Kit/NKQueue/NKQueue.swift | 15 +++++- .../Kit/Publishers/Assign/Assign.swift | 49 ------------------- .../Publishers/Completion/Completion.swift | 42 ---------------- .../Replace Error/Replace Error.swift | 20 +------- .../Operations/Assign Operation.swift | 36 ++++++++++++++ .../Operations/Catch Operation.swift | 6 +-- .../Operations/Debouce Operation.swift | 8 +-- .../Operations/Replace Error Operation.swift | 46 +++++++++++++++++ .../Operations/TryCatch Operation.swift | 12 ++--- .../NKCancellable.swift} | 5 +- .../Publisher/NKPublisher+Methods.swift | 27 ++++++---- .../Publisher/NKPublisher+Queue.swift | 4 +- Tests/NetworkKitTests/NetworkKitTests.swift | 4 +- 14 files changed, 171 insertions(+), 136 deletions(-) create mode 100644 Sources/NetworkKit/Kit/NKAnyCancellable/NKAnyCancellable.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift create mode 100644 Sources/NetworkKit/Operations/Assign Operation.swift create mode 100644 Sources/NetworkKit/Operations/Replace Error Operation.swift rename Sources/NetworkKit/Protocols/{Cancellable/Cancellable.swift => NKCancellable/NKCancellable.swift} (66%) diff --git a/Sources/NetworkKit/Kit/NKAnyCancellable/NKAnyCancellable.swift b/Sources/NetworkKit/Kit/NKAnyCancellable/NKAnyCancellable.swift new file mode 100644 index 0000000..7984b62 --- /dev/null +++ b/Sources/NetworkKit/Kit/NKAnyCancellable/NKAnyCancellable.swift @@ -0,0 +1,33 @@ +// +// NKAnyCancellable.swift +// NetworkKit +// +// Created by Raghav Ahuja on 25/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public class NKAnyCancellable: NKCancellable { + + let block: () -> Void + + /// Initializes the cancellable object with the given cancel-time closure. + /// + /// - Parameter cancel: A closure that the `cancel()` method executes. + public init(cancel: @escaping () -> Void) { + block = cancel + } + + public init(_ canceller: C) { + block = canceller.cancel + } + + deinit { + cancel() + } + + public func cancel() { + block() + } +} diff --git a/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift b/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift index 9970e0d..297203a 100644 --- a/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift +++ b/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift @@ -12,7 +12,7 @@ public final class NKQueue { /// A queue that regulates the execution of operations. /// It is a serial queue, that has `quality of service` of `utility`. - let operationQueue: OperationQueue + private let operationQueue: OperationQueue let request: URLRequest? @@ -22,12 +22,21 @@ public final class NKQueue { operationQueue.operations } + var isSuspended: Bool { + get { operationQueue.isSuspended } + set { operationQueue.isSuspended = newValue } + } + init(operationQueue: OperationQueue, request: URLRequest?, apiName: String?) { self.operationQueue = operationQueue self.request = request self.apiName = apiName ?? request?.url?.absoluteString ?? "nil" } + deinit { + operationQueue.cancelAllOperations() + } + func addOperation(_ op: Operation) { operationQueue.addOperation(op) } @@ -35,4 +44,8 @@ public final class NKQueue { func addOperation(_ block: @escaping () -> Void) { operationQueue.addOperation(block) } + + func cancelAllOperations() { + operationQueue.cancelAllOperations() + } } diff --git a/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift b/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift deleted file mode 100644 index 8aa441c..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Assign/Assign.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Assign.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -extension NKPublishers { - - struct Assign: NetworkCancellable where Upstream.Failure == Never { - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - /// The object on which to assign the value. - public let object: Root - - /// The key path of the property to assign. - public let keyPath: ReferenceWritableKeyPath - - public init(upstream: Upstream, to keyPath: ReferenceWritableKeyPath, on object: Root) { - self.upstream = upstream - self.object = object - self.keyPath = keyPath - assign() - } - - private func assign() { - addToQueue { - guard let value = try? self.upstream.result.result?.get() else { - return - } - self.object[keyPath: self.keyPath] = value - } - } - - private func addToQueue(_ block: @escaping () -> Void) { - let op = BaseBlockOperation(request: upstream.queue.request, result: upstream.result, block: block) - upstream.queue.addOperation(op) - } - - public func cancel() { - upstream.queue.operationQueue.cancelAllOperations() - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift b/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift deleted file mode 100644 index 389e39d..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Completion/Completion.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// Completion.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -extension NKPublishers { - - struct Completion: NetworkCancellable { - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - /// A closure that returns the result of the upstream publisher. - public let block: (Result) -> Void - - public init(upstream: Upstream, block: @escaping (Result) -> Void) { - self.upstream = upstream - self.block = block - completion() - } - - private func completion() { - addToQueue { - self.block(self.upstream.result.result!) - } - } - - private func addToQueue(_ block: @escaping () -> Void) { - let op = BaseBlockOperation(request: upstream.queue.request, result: upstream.result, block: block) - upstream.queue.addOperation(op) - } - - public func cancel() { - upstream.queue.operationQueue.cancelAllOperations() - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift index 73620c2..a3fb3ab 100644 --- a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift +++ b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift @@ -36,24 +36,8 @@ public extension NKPublishers { } private func perform() { - addToQueue { - self.doReplace() - } - } - - private func doReplace() { - let upstreamResult = upstream.result.result - - switch upstreamResult { - case .success(let output): - result.result = .success(output) - - case .failure: - result.result = .success(output) - - case .none: - result.result = .none - } + let operation = ReplaceErrorOperation(upstream: upstream, output: output, result: result) + addToQueue(operation) } } } diff --git a/Sources/NetworkKit/Operations/Assign Operation.swift b/Sources/NetworkKit/Operations/Assign Operation.swift new file mode 100644 index 0000000..2860dbc --- /dev/null +++ b/Sources/NetworkKit/Operations/Assign Operation.swift @@ -0,0 +1,36 @@ +// +// Assign Operation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 25/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +final class AssignOperation: Operation where Upstream.Failure == Never { + + let upstrem: Upstream + let keypath: ReferenceWritableKeyPath + let object: Root + + var updatedValue = false + + init(upstrem: Upstream, keypath: ReferenceWritableKeyPath, object: Root) { + self.upstrem = upstrem + self.keypath = keypath + self.object = object + } + + override func main() { + object[keyPath: keypath] = try! upstrem.result.result!.get() + updatedValue = true + } + + override func cancel() { + super.cancel() + if !updatedValue { + main() + } + } +} diff --git a/Sources/NetworkKit/Operations/Catch Operation.swift b/Sources/NetworkKit/Operations/Catch Operation.swift index 7bb64ba..912681e 100644 --- a/Sources/NetworkKit/Operations/Catch Operation.swift +++ b/Sources/NetworkKit/Operations/Catch Operation.swift @@ -10,9 +10,9 @@ import Foundation final class CatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { - public typealias Output = Upstream.Output + typealias Output = Upstream.Output - public typealias Failure = NewPublisher.Failure + typealias Failure = NewPublisher.Failure /// The publisher that this publisher receives elements from. private let upstream: Upstream @@ -53,7 +53,7 @@ final class CatchOperation: As self?.result.result = .failure(error) } - newPublisher.queue.operationQueue.cancelAllOperations() + newPublisher.queue.cancelAllOperations() self?.finish() } diff --git a/Sources/NetworkKit/Operations/Debouce Operation.swift b/Sources/NetworkKit/Operations/Debouce Operation.swift index 494299f..0728dc6 100644 --- a/Sources/NetworkKit/Operations/Debouce Operation.swift +++ b/Sources/NetworkKit/Operations/Debouce Operation.swift @@ -10,9 +10,11 @@ import Foundation final class DebounceOperation: AsynchronousOperation { - let dueTime: DispatchTime - let result: NKResult - let url: URL? + private let dueTime: DispatchTime + + private let result: NKResult + + private let url: URL? init(result: NKResult, url: URL?, dueTime: DispatchTime) { self.result = result diff --git a/Sources/NetworkKit/Operations/Replace Error Operation.swift b/Sources/NetworkKit/Operations/Replace Error Operation.swift new file mode 100644 index 0000000..c0f7a75 --- /dev/null +++ b/Sources/NetworkKit/Operations/Replace Error Operation.swift @@ -0,0 +1,46 @@ +// +// Replace Error Operation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 25/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +final class ReplaceErrorOperation: Operation { + + typealias Output = Upstream.Output + + private let upstream: Upstream + + private let output: Output + + private let result: NKResult + + init(upstream: Upstream, output: Output, result: NKResult) { + self.upstream = upstream + self.output = output + self.result = result + } + + override func main() { + let upstreamResult = upstream.result.result + + switch upstreamResult { + case .success(let output): + result.result = .success(output) + + case .failure: + result.result = .success(output) + + case .none: + result.result = .none + } + } + + override func cancel() { + result.result = .success(output) + super.cancel() + } +} diff --git a/Sources/NetworkKit/Operations/TryCatch Operation.swift b/Sources/NetworkKit/Operations/TryCatch Operation.swift index aa7a8fa..f764fe5 100644 --- a/Sources/NetworkKit/Operations/TryCatch Operation.swift +++ b/Sources/NetworkKit/Operations/TryCatch Operation.swift @@ -10,26 +10,26 @@ import Foundation final class TryCatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { - public typealias Output = Upstream.Output + typealias Output = Upstream.Output - public typealias Failure = NewPublisher.Failure + typealias Failure = NewPublisher.Failure /// The publisher that this publisher receives elements from. private let upstream: Upstream private var result: NKResult + + private var newOperation: Operation? /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. private let handler: (Upstream.Failure) throws -> NewPublisher - public init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher, result: NKResult) { + init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher, result: NKResult) { self.upstream = upstream self.handler = handler self.result = result } - private var newOperation: Operation? - override func main() { switch upstream.result.result { case .success(let output): @@ -57,7 +57,7 @@ final class TryCatchOperation: self?.result.result = .failure(error) } - newPublisher.queue.operationQueue.cancelAllOperations() + newPublisher.queue.cancelAllOperations() self?.finish() } diff --git a/Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift b/Sources/NetworkKit/Protocols/NKCancellable/NKCancellable.swift similarity index 66% rename from Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift rename to Sources/NetworkKit/Protocols/NKCancellable/NKCancellable.swift index da790e4..f7eb82f 100644 --- a/Sources/NetworkKit/Protocols/Cancellable/Cancellable.swift +++ b/Sources/NetworkKit/Protocols/NKCancellable/NKCancellable.swift @@ -1,5 +1,5 @@ // -// Cancellable.swift +// NKCancellable.swift // NetworkKit // // Created by Raghav Ahuja on 18/11/19. @@ -8,7 +8,8 @@ import Foundation -public protocol NetworkCancellable { +public protocol NKCancellable { + /// Cancel the activity. func cancel() } diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift index caf0514..a6578a4 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift @@ -20,8 +20,8 @@ public extension NKPublisher { /// Decodes the output from upstream using a specified `JSONDecoder`. /// - Parameter type: Type to decode into. - /// - Parameter jsonKeyDecodingStrategy: JSON Key Decoding Strategy. - func decode(type: Item.Type, jsonKeyDecodingStrategy: JSONDecoder.KeyDecodingStrategy) -> NKPublishers.Decode { + /// - Parameter jsonKeyDecodingStrategy: JSON Key Decoding Strategy. Default value is `.useDefaultKeys`. + func decode(type: Item.Type, jsonKeyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> NKPublishers.Decode { NKPublishers.Decode(upstream: self, jsonKeyDecodingStrategy: jsonKeyDecodingStrategy) } @@ -66,7 +66,6 @@ public extension NKPublisher { /// Use this operator when you want to wait for a pause in the delivery of events from the upstream publisher. For example, call `debounce` on the publisher from a text field to only receive elements when the user pauses or stops typing. When they start typing again, the `debounce` holds event delivery until the next pause. /// - Parameters: /// - dueTime: The time the publisher should wait before publishing an element. - /// - scheduler: The scheduler on which this publisher delivers elements func debounce(_ dueTime: DispatchTime) -> NKPublishers.Debounce { NKPublishers.Debounce(upstream: self, dueTime: dueTime) } @@ -148,11 +147,16 @@ public extension NKPublisher { /// Attaches a subscriber with closure-based behavior. /// /// This method creates the subscriber and immediately requests an unlimited number of values, prior to returning the subscriber. - /// - parameter receiveComplete: The closure to execute on completion. - /// - parameter receiveValue: The closure to execute on receipt of a value. + /// - parameter block: The closure to execute on completion. /// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. - func completion(_ block: @escaping (Result) -> Void) -> NetworkCancellable { - NKPublishers.Completion(upstream: self, block: block) + func completion(_ block: @escaping (Result) -> Void) -> NKAnyCancellable { + queue.addOperation { + block(self.result.result!) + } + + return NKAnyCancellable { + self.queue.cancelAllOperations() + } } } @@ -164,7 +168,12 @@ public extension NKPublisher where Self.Failure == Never { /// - keyPath: The key path of the property to assign. /// - object: The object on which to assign the value. /// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. - func assign(to keyPath: ReferenceWritableKeyPath, on root: Root) -> NetworkCancellable { - NKPublishers.Assign(upstream: self, to: keyPath, on: root) + func assign(to keyPath: ReferenceWritableKeyPath, on object: Root) -> NKAnyCancellable { + let operation = AssignOperation(upstrem: self, keypath: keyPath, object: object) + queue.addOperation(operation) + + return NKAnyCancellable { + self.queue.cancelAllOperations() + } } } diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift index 2efcbf3..f8ea9f1 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift @@ -24,8 +24,8 @@ extension NKPublisher { } func queue(isSuspended: Bool) { - if queue.operationQueue.isSuspended != isSuspended { - queue.operationQueue.isSuspended = isSuspended + if queue.isSuspended != isSuspended { + queue.isSuspended = isSuspended } } } diff --git a/Tests/NetworkKitTests/NetworkKitTests.swift b/Tests/NetworkKitTests/NetworkKitTests.swift index ebbc914..b9f5e2f 100644 --- a/Tests/NetworkKitTests/NetworkKitTests.swift +++ b/Tests/NetworkKitTests/NetworkKitTests.swift @@ -124,7 +124,7 @@ final class NetworkKitTests: XCTestCase { } } - var cancellable: NetworkCancellable! + var cancellable: NKAnyCancellable? let expecatation = XCTestExpectation() @@ -137,6 +137,8 @@ final class NetworkKitTests: XCTestCase { .replaceError(with: [User(id: "12", createdAt: "Today", name: "Guest User", avatar: nil)]) .assign(to: \.users, on: self) + cancellable?.cancel() + wait(for: [expecatation], timeout: 60) } From ea90ba4d572e7dd93a3ec418d942d496ff2f34a1 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Tue, 26 Nov 2019 00:36:51 +0530 Subject: [PATCH 21/25] Minor Updates --- .../NetworkKit/Kit/Publishers/Map/{ => Error}/Map Error.swift | 0 .../Kit/Publishers/Map/{ => Keypath}/Map Keypath 2.swift | 0 .../Kit/Publishers/Map/{ => Keypath}/Map Keypath.swift | 0 Sources/NetworkKit/Operations/Fetch Operation.swift | 4 ++-- 4 files changed, 2 insertions(+), 2 deletions(-) rename Sources/NetworkKit/Kit/Publishers/Map/{ => Error}/Map Error.swift (100%) rename Sources/NetworkKit/Kit/Publishers/Map/{ => Keypath}/Map Keypath 2.swift (100%) rename Sources/NetworkKit/Kit/Publishers/Map/{ => Keypath}/Map Keypath.swift (100%) diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift b/Sources/NetworkKit/Kit/Publishers/Map/Error/Map Error.swift similarity index 100% rename from Sources/NetworkKit/Kit/Publishers/Map/Map Error.swift rename to Sources/NetworkKit/Kit/Publishers/Map/Error/Map Error.swift diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift b/Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath 2.swift similarity index 100% rename from Sources/NetworkKit/Kit/Publishers/Map/Map Keypath 2.swift rename to Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath 2.swift diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift b/Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath.swift similarity index 100% rename from Sources/NetworkKit/Kit/Publishers/Map/Map Keypath.swift rename to Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath.swift diff --git a/Sources/NetworkKit/Operations/Fetch Operation.swift b/Sources/NetworkKit/Operations/Fetch Operation.swift index a2de4ac..7fcd4f1 100644 --- a/Sources/NetworkKit/Operations/Fetch Operation.swift +++ b/Sources/NetworkKit/Operations/Fetch Operation.swift @@ -29,7 +29,7 @@ final class FetchOperation: AsynchronousOperation { override func main() { guard let request = request else { - result?.result = .failure(NSError.unsupportedURL(for: nil) as NSError) + result?.result = .failure(NSError.unsupportedURL(for: nil)) finish() return } @@ -48,7 +48,7 @@ final class FetchOperation: AsynchronousOperation { } override func cancel() { - result?.result = .failure(NSError.cancelled(for: request?.url) as NSError) + result?.result = .failure(NSError.cancelled(for: request?.url)) task?.cancel() super.cancel() } From 16651f75034d8ee05e6dcafd1fddb49879ef8ae2 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Tue, 26 Nov 2019 00:54:57 +0530 Subject: [PATCH 22/25] Added cancel check in Asynchronous Operation --- Sources/NetworkKit/Operations/Catch Operation.swift | 8 ++++++++ Sources/NetworkKit/Operations/Debouce Operation.swift | 4 ++++ Sources/NetworkKit/Operations/Fetch Operation.swift | 4 ++++ Sources/NetworkKit/Operations/TryCatch Operation.swift | 8 ++++++++ 4 files changed, 24 insertions(+) diff --git a/Sources/NetworkKit/Operations/Catch Operation.swift b/Sources/NetworkKit/Operations/Catch Operation.swift index 912681e..33422ef 100644 --- a/Sources/NetworkKit/Operations/Catch Operation.swift +++ b/Sources/NetworkKit/Operations/Catch Operation.swift @@ -31,6 +31,10 @@ final class CatchOperation: As } override func main() { + guard !isCancelled else { + return + } + switch upstream.result.result { case .success(let output): result.result = .success(output) @@ -45,6 +49,10 @@ final class CatchOperation: As self.newOperation = newOperation newOperation.completionBlock = { [weak self] in + guard !(self?.isCancelled ?? true) else { + return + } + switch newPublisher.result.result! { case .success(let output): self?.result.result = .success(output) diff --git a/Sources/NetworkKit/Operations/Debouce Operation.swift b/Sources/NetworkKit/Operations/Debouce Operation.swift index 0728dc6..f0fb097 100644 --- a/Sources/NetworkKit/Operations/Debouce Operation.swift +++ b/Sources/NetworkKit/Operations/Debouce Operation.swift @@ -25,6 +25,10 @@ final class DebounceOperation: AsynchronousOperation { } override func main() { + guard !isCancelled else { + return + } + DispatchQueue.global(qos: .utility).asyncAfter(deadline: dueTime) { [weak self] in self?.finish() } diff --git a/Sources/NetworkKit/Operations/Fetch Operation.swift b/Sources/NetworkKit/Operations/Fetch Operation.swift index 7fcd4f1..02bd341 100644 --- a/Sources/NetworkKit/Operations/Fetch Operation.swift +++ b/Sources/NetworkKit/Operations/Fetch Operation.swift @@ -28,6 +28,10 @@ final class FetchOperation: AsynchronousOperation { } override func main() { + guard !isCancelled else { + return + } + guard let request = request else { result?.result = .failure(NSError.unsupportedURL(for: nil)) finish() diff --git a/Sources/NetworkKit/Operations/TryCatch Operation.swift b/Sources/NetworkKit/Operations/TryCatch Operation.swift index f764fe5..40fc45c 100644 --- a/Sources/NetworkKit/Operations/TryCatch Operation.swift +++ b/Sources/NetworkKit/Operations/TryCatch Operation.swift @@ -31,6 +31,10 @@ final class TryCatchOperation: } override func main() { + guard !isCancelled else { + return + } + switch upstream.result.result { case .success(let output): result.result = .success(output) @@ -49,6 +53,10 @@ final class TryCatchOperation: self.newOperation = newOperation newOperation.completionBlock = { [weak self] in + guard !(self?.isCancelled ?? true) else { + return + } + switch newPublisher.result.result! { case .success(let output): self?.result.result = .success(output) From ce72a26b4fa71648a3f2dac728501307208f1d03 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Tue, 26 Nov 2019 22:58:42 +0530 Subject: [PATCH 23/25] Added Compact Map and Try Compact Map Publishers --- .../Publishers/Compact Map/Compact Map.swift | 64 +++++++++++++++++ .../Compact Map/Try Compact Map.swift | 71 +++++++++++++++++++ .../Operations/Assign Operation.swift | 6 +- .../Publisher/NKPublisher+Methods.swift | 23 +++++- 4 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 Sources/NetworkKit/Kit/Publishers/Compact Map/Compact Map.swift create mode 100644 Sources/NetworkKit/Kit/Publishers/Compact Map/Try Compact Map.swift diff --git a/Sources/NetworkKit/Kit/Publishers/Compact Map/Compact Map.swift b/Sources/NetworkKit/Kit/Publishers/Compact Map/Compact Map.swift new file mode 100644 index 0000000..408b02f --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Compact Map/Compact Map.swift @@ -0,0 +1,64 @@ +// +// Compact Map.swift +// NetworkKit +// +// Created by Raghav Ahuja on 26/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NKPublishers { + + /// A publisher that republishes all non-`nil` results of calling a closure with each received element. + struct CompactMap: NKPublisher { + + public var result: NKResult + + public var queue: NKQueue { + upstream.queue + } + + public typealias Output = MapOutput + + public typealias Failure = Upstream.Failure + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + /// The closure that transforms elements from the upstream publisher. + public let transform: (Upstream.Output) -> Output? + + public init(upstream: Upstream, transform: @escaping (Upstream.Output) -> Output?) { + self.upstream = upstream + self.transform = transform + result = .init() + perform() + } + + private func perform() { + addToQueue { + self.doTransform() + } + } + + private func doTransform() { + let upstreamResult = upstream.result.result + + switch upstreamResult { + case .success(let output): + guard let newOutput = transform(output) else { + result.result = .none + return + } + result.result = .success(newOutput) + + case .failure(let error): + result.result = .failure(error) + + case .none: + result.result = .none + } + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Compact Map/Try Compact Map.swift b/Sources/NetworkKit/Kit/Publishers/Compact Map/Try Compact Map.swift new file mode 100644 index 0000000..ec490f2 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Compact Map/Try Compact Map.swift @@ -0,0 +1,71 @@ +// +// Compact Map.swift +// NetworkKit +// +// Created by Raghav Ahuja on 26/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NKPublishers { + + /// A publisher that republishes all non-`nil` results of calling an error-throwing closure with each received element. + struct TryCompactMap: NKPublisher { + + public var result: NKResult + + public var queue: NKQueue { + upstream.queue + } + + public typealias Output = MapOutput + + public typealias Failure = Upstream.Failure + + /// The publisher that this publisher receives elements from. + public let upstream: Upstream + + /// The closure that transforms elements from the upstream publisher. + public let transform: (Upstream.Output) throws -> Output? + + public init(upstream: Upstream, transform: @escaping (Upstream.Output) throws -> Output?) { + self.upstream = upstream + self.transform = transform + result = .init() + perform() + } + + private func perform() { + addToQueue { + self.doTransform() + } + } + + private func doTransform() { + let upstreamResult = upstream.result.result + + switch upstreamResult { + case .success(let output): + do { + guard let newOutput = try transform(output) else { + result.result = .none + return + } + + result.result = .success(newOutput) + + } catch { + let error = error as NSError + result.result = .failure(error as! Failure) + } + + case .failure(let error): + result.result = .failure(error) + + case .none: + result.result = .none + } + } + } +} diff --git a/Sources/NetworkKit/Operations/Assign Operation.swift b/Sources/NetworkKit/Operations/Assign Operation.swift index 2860dbc..5bf5096 100644 --- a/Sources/NetworkKit/Operations/Assign Operation.swift +++ b/Sources/NetworkKit/Operations/Assign Operation.swift @@ -23,7 +23,11 @@ final class AssignOperation: Operation where Upstre } override func main() { - object[keyPath: keypath] = try! upstrem.result.result!.get() + guard let output = try? upstrem.result.result?.get() else { + updatedValue = true + return + } + object[keyPath: keypath] = output updatedValue = true } diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift index a6578a4..0d4fc7b 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift @@ -129,6 +129,23 @@ public extension NKPublisher { func mapError(_ transform: @escaping (Self.Failure) -> E) -> NKPublishers.MapError { NKPublishers.MapError(upstream: self, transform: transform) } + + /// Calls a closure with each received element and publishes any returned optional that has a value. + /// + /// - Parameter transform: A closure that receives a value and returns an optional value. + /// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure. + func compactMap(_ transform: @escaping (Output) -> T?) -> NKPublishers.CompactMap { + NKPublishers.CompactMap(upstream: self, transform: transform) + } + + /// Calls an error-throwing closure with each received element and publishes any returned optional that has a value. + /// + /// If the closure throws an error, the publisher cancels the upstream and sends the thrown error to the downstream receiver as a `Failure`. + /// - Parameter transform: an error-throwing closure that receives a value and returns an optional value. + /// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure. + func tryCompactMap(_ transform: @escaping (Self.Output) throws -> T?) -> NKPublishers.TryCompactMap { + NKPublishers.TryCompactMap(upstream: self, transform: transform) + } } public extension NKPublisher where Self.Output == NetworkTask.Output, Self.Failure == NetworkTask.Failure { @@ -151,7 +168,11 @@ public extension NKPublisher { /// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. func completion(_ block: @escaping (Result) -> Void) -> NKAnyCancellable { queue.addOperation { - block(self.result.result!) + guard let output = self.result.result else { + return + } + + block(output) } return NKAnyCancellable { From 23a8d21c98d827912f1b62709c196c1c03529fd7 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Tue, 26 Nov 2019 23:18:42 +0530 Subject: [PATCH 24/25] Added Replace Empty Publisher and Replace Nil Publisher Method --- .../Replace Empty/Replace Empty.swift | 44 +++++++++++++++++++ .../Replace Error/Replace Error.swift | 1 + .../Operations/Replace Empty Operation.swift | 43 ++++++++++++++++++ .../Publisher/NKPublisher+Methods.swift | 17 +++++++ 4 files changed, 105 insertions(+) create mode 100644 Sources/NetworkKit/Kit/Publishers/Replace Empty/Replace Empty.swift create mode 100644 Sources/NetworkKit/Operations/Replace Empty Operation.swift diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Empty/Replace Empty.swift b/Sources/NetworkKit/Kit/Publishers/Replace Empty/Replace Empty.swift new file mode 100644 index 0000000..d948035 --- /dev/null +++ b/Sources/NetworkKit/Kit/Publishers/Replace Empty/Replace Empty.swift @@ -0,0 +1,44 @@ +// +// Replace Empty.swift +// NetworkKit +// +// Created by Raghav Ahuja on 26/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public extension NKPublishers { + + /// A publisher that replaces an empty stream with a provided element. + struct ReplaceEmpty: NKPublisher { + + public var result: NKResult + + public var queue: NKQueue { + upstream.queue + } + + public typealias Output = Upstream.Output + + public typealias Failure = Never + + /// The element with which to replace errors from the upstream publisher. + public let output: Upstream.Output + + /// The publisher from which this publisher receives elements. + public let upstream: Upstream + + public init(upstream: Upstream, output: Output) { + self.upstream = upstream + self.output = output + result = .init(result: .success(output)) + perform() + } + + private func perform() { + let operation = ReplaceEmptyOperation(upstream: upstream, output: output, result: result) + addToQueue(operation) + } + } +} diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift index a3fb3ab..ff1cd13 100644 --- a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift +++ b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift @@ -10,6 +10,7 @@ import Foundation public extension NKPublishers { + /// A publisher that replaces any errors in the stream with a provided element. struct ReplaceError: NKPublisher { public var result: NKResult diff --git a/Sources/NetworkKit/Operations/Replace Empty Operation.swift b/Sources/NetworkKit/Operations/Replace Empty Operation.swift new file mode 100644 index 0000000..f2711b5 --- /dev/null +++ b/Sources/NetworkKit/Operations/Replace Empty Operation.swift @@ -0,0 +1,43 @@ +// +// Replace Empty Operation.swift +// NetworkKit +// +// Created by Raghav Ahuja on 26/11/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +final class ReplaceEmptyOperation: Operation { + + typealias Output = Upstream.Output + + private let upstream: Upstream + + private let output: Output + + private let result: NKResult + + init(upstream: Upstream, output: Output, result: NKResult) { + self.upstream = upstream + self.output = output + self.result = result + } + + override func main() { + let upstreamResult = upstream.result.result + + switch upstreamResult { + case .success(let output): + result.result = .success(output) + + case .failure, .none: + result.result = .success(output) + } + } + + override func cancel() { + result.result = .success(output) + super.cancel() + } +} diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift index 0d4fc7b..0d11bd0 100644 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift +++ b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift @@ -146,6 +146,23 @@ public extension NKPublisher { func tryCompactMap(_ transform: @escaping (Self.Output) throws -> T?) -> NKPublishers.TryCompactMap { NKPublishers.TryCompactMap(upstream: self, transform: transform) } + + /// Replaces nil elements in the stream with the proviced element. + /// + /// - Parameter output: The element to use when replacing `nil`. + /// - Returns: A publisher that replaces `nil` elements from the upstream publisher with the provided element. + func replaceNil(with output: T) -> NKPublishers.Map where Output == T? { + NKPublishers.Map(upstream: self) { _ in output } + } + + /// Replaces an empty stream with the provided element. + /// + /// If the upstream publisher finishes without producing any elements, this publisher emits the provided element, then finishes normally. + /// - Parameter output: An element to emit when the upstream publisher finishes without emitting any elements. + /// - Returns: A publisher that replaces an empty stream with the provided output element. + func replaceEmpty(with output: Output) -> NKPublishers.ReplaceEmpty { + NKPublishers.ReplaceEmpty(upstream: self, output: output) + } } public extension NKPublisher where Self.Output == NetworkTask.Output, Self.Failure == NetworkTask.Failure { From d61d3325e3f06024b49d7dc007cb21b4e4f67926 Mon Sep 17 00:00:00 2001 From: Raghav Ahuja Date: Mon, 30 Dec 2019 00:21:45 +0530 Subject: [PATCH 25/25] Updated With Publisher Kit Support --- .../contents.xcworkspacedata | 2 +- Package.resolved | 16 ++ Package.swift | 3 +- .../Create Request/Create Request.swift | 16 +- .../Debug Loging/Debug Logging.swift | 42 ---- .../NetworkKit/Debug Loging/NKLogger.swift | 226 ++++++++++++++++++ .../NetworkKit/Errors/Business Error.swift | 29 --- Sources/NetworkKit/Errors/HTTP Error.swift | 135 ----------- .../NSError.swift} | 33 ++- .../Extensions/Dictionary+Extension.swift | 48 ---- .../Extensions/JSONDecoder+Extension.swift | 14 -- .../Extensions/Optional+Extension.swift | 23 -- ...ay+Extension.swift => Set+Extension.swift} | 6 +- .../NetworkKit/Extensions/URL+Extension.swift | 19 -- .../Extensions/URLRequest+Extension.swift | 27 --- .../NKAnyCancellable/NKAnyCancellable.swift | 33 --- Sources/NetworkKit/Kit/NKQueue/NKQueue.swift | 51 ---- .../NetworkKit/Kit/NKResult/NKResult.swift | 25 -- Sources/NetworkKit/Kit/Network Task.swift | 110 --------- .../Kit/Publishers/Catch/Catch.swift | 45 ---- .../Kit/Publishers/Catch/Try Catch.swift | 45 ---- .../Publishers/Compact Map/Compact Map.swift | 64 ----- .../Compact Map/Try Compact Map.swift | 71 ------ .../Kit/Publishers/Debounce/Debounce.swift | 44 ---- .../Kit/Publishers/Decode/Decode.swift | 76 ------ .../Kit/Publishers/Map/Error/Map Error.swift | 60 ----- .../Map/Keypath/Map Keypath 2.swift | 67 ------ .../Publishers/Map/Keypath/Map Keypath.swift | 56 ----- .../NetworkKit/Kit/Publishers/Map/Map.swift | 60 ----- .../Kit/Publishers/Map/Try Map.swift | 67 ------ .../Kit/Publishers/NKPublishers.swift | 12 - .../Replace Empty/Replace Empty.swift | 44 ---- .../Replace Error/Replace Error.swift | 44 ---- .../Kit/Publishers/Validate/Validate.swift | 156 ------------ Sources/NetworkKit/Models/API Analytics.swift | 18 -- Sources/NetworkKit/Models/Error Model.swift | 29 --- ...swift => Configuration+Notification.swift} | 6 +- ...onfiguration.swift => Configuration.swift} | 72 +++--- .../ImageSession+ImageType.swift} | 0 .../ImageSession.swift} | 42 +--- .../{NKSession.swift => Session.swift} | 40 +++- .../Operations/Assign Operation.swift | 40 ---- .../Operations/Asynchronous Operation.swift | 54 ----- .../Operations/Base Block Operation.swift | 28 --- .../Operations/Catch Operation.swift | 81 ------- .../Operations/Debouce Operation.swift | 42 ---- .../Operations/Fetch Operation.swift | 59 ----- .../Operations/Replace Empty Operation.swift | 43 ---- .../Operations/Replace Error Operation.swift | 46 ---- .../Operations/TryCatch Operation.swift | 91 ------- ...egate.swift => ImageSessionDelegate.swift} | 10 +- .../NKCancellable/NKCancellable.swift | 15 -- .../Protocols/Network Decoder.swift | 16 -- .../Publisher/NKPublisher+Methods.swift | 217 ----------------- .../Publisher/NKPublisher+Queue.swift | 31 --- .../Protocols/Publisher/NKPublisher.swift | 24 -- .../Protocols/Server/Environment.swift | 2 +- .../NetworkKit/Request/Network Request.swift | 146 ----------- Sources/NetworkKit/Request/Request.swift | 82 +++++++ Tests/NetworkKitTests/NetworkKitTests.swift | 2 +- 60 files changed, 439 insertions(+), 2566 deletions(-) create mode 100644 Package.resolved delete mode 100644 Sources/NetworkKit/Debug Loging/Debug Logging.swift create mode 100644 Sources/NetworkKit/Debug Loging/NKLogger.swift delete mode 100644 Sources/NetworkKit/Errors/Business Error.swift delete mode 100644 Sources/NetworkKit/Errors/HTTP Error.swift rename Sources/NetworkKit/{Extensions/NSError+Extension.swift => Errors/NSError.swift} (65%) delete mode 100644 Sources/NetworkKit/Extensions/Dictionary+Extension.swift delete mode 100644 Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift delete mode 100644 Sources/NetworkKit/Extensions/Optional+Extension.swift rename Sources/NetworkKit/Extensions/{Array+Extension.swift => Set+Extension.swift} (83%) delete mode 100644 Sources/NetworkKit/Extensions/URL+Extension.swift delete mode 100644 Sources/NetworkKit/Extensions/URLRequest+Extension.swift delete mode 100644 Sources/NetworkKit/Kit/NKAnyCancellable/NKAnyCancellable.swift delete mode 100644 Sources/NetworkKit/Kit/NKQueue/NKQueue.swift delete mode 100644 Sources/NetworkKit/Kit/NKResult/NKResult.swift delete mode 100644 Sources/NetworkKit/Kit/Network Task.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Compact Map/Compact Map.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Compact Map/Try Compact Map.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Error/Map Error.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath 2.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Map.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/NKPublishers.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Replace Empty/Replace Empty.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift delete mode 100644 Sources/NetworkKit/Kit/Publishers/Validate/Validate.swift delete mode 100644 Sources/NetworkKit/Models/API Analytics.swift delete mode 100644 Sources/NetworkKit/Models/Error Model.swift rename Sources/NetworkKit/Networking/Configuration/{Network Configuration+Notification.swift => Configuration+Notification.swift} (82%) rename Sources/NetworkKit/Networking/Configuration/{Network Configuration.swift => Configuration.swift} (74%) rename Sources/NetworkKit/Networking/{NKImageSession/NKImageSession+ImageType.swift => ImageSession/ImageSession+ImageType.swift} (100%) rename Sources/NetworkKit/Networking/{NKImageSession/NKImageSession.swift => ImageSession/ImageSession.swift} (73%) rename Sources/NetworkKit/Networking/{NKSession.swift => Session.swift} (57%) delete mode 100644 Sources/NetworkKit/Operations/Assign Operation.swift delete mode 100644 Sources/NetworkKit/Operations/Asynchronous Operation.swift delete mode 100644 Sources/NetworkKit/Operations/Base Block Operation.swift delete mode 100644 Sources/NetworkKit/Operations/Catch Operation.swift delete mode 100644 Sources/NetworkKit/Operations/Debouce Operation.swift delete mode 100644 Sources/NetworkKit/Operations/Fetch Operation.swift delete mode 100644 Sources/NetworkKit/Operations/Replace Empty Operation.swift delete mode 100644 Sources/NetworkKit/Operations/Replace Error Operation.swift delete mode 100644 Sources/NetworkKit/Operations/TryCatch Operation.swift rename Sources/NetworkKit/Protocols/{NKImageSessionDelegate.swift => ImageSessionDelegate.swift} (91%) delete mode 100644 Sources/NetworkKit/Protocols/NKCancellable/NKCancellable.swift delete mode 100644 Sources/NetworkKit/Protocols/Network Decoder.swift delete mode 100644 Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift delete mode 100644 Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift delete mode 100644 Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift delete mode 100644 Sources/NetworkKit/Request/Network Request.swift create mode 100644 Sources/NetworkKit/Request/Request.swift diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata index 706eede..919434a 100644 --- a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..3cadc13 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "PublisherKit", + "repositoryURL": "https://github.com/ragzy15/PublisherKit", + "state": { + "branch": null, + "revision": "ad211780f0f532a473e3d5d1cef2a2644024d7c4", + "version": "1.0.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index 20f668c..68f8439 100644 --- a/Package.swift +++ b/Package.swift @@ -18,6 +18,7 @@ let package = Package( targets: ["NetworkKit"]), ], dependencies: [ + .package(url: "https://github.com/ragzy15/PublisherKit", from: .init(1, 0, 0)) // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), ], @@ -26,7 +27,7 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( name: "NetworkKit", - dependencies: []), + dependencies: ["PublisherKit"]), .testTarget( name: "NetworkKitTests", dependencies: ["NetworkKit"]), diff --git a/Sources/NetworkKit/Create Request/Create Request.swift b/Sources/NetworkKit/Create Request/Create Request.swift index c0df1ff..4a9841e 100644 --- a/Sources/NetworkKit/Create Request/Create Request.swift +++ b/Sources/NetworkKit/Create Request/Create Request.swift @@ -15,7 +15,7 @@ public struct CreateRequest { public let request: URLRequest - public init?(with connection: ConnectionRepresentable, query urlQuery: URLQuery?) { + public init?(with connection: ConnectionRepresentable, query urlQuery: Set, body: Data?, headers: HTTPHeaderParameters) { var components = URLComponents() components.scheme = connection.scheme.rawValue @@ -26,15 +26,15 @@ public struct CreateRequest { components.host = (subURL.isEmpty ? subURL : subURL + ".") + connection.host.host components.path = endPoint + connection.path - var queryItems: [URLQueryItem] = [] - queryItems.addURLQuery(query: urlQuery) + var queryItems = Set() queryItems.addURLQuery(query: connection.defaultQuery) queryItems.addURLQuery(query: connection.host.defaultUrlQuery) + queryItems = queryItems.union(urlQuery) let method = connection.method if !queryItems.isEmpty { - components.queryItems = queryItems + components.queryItems = Array(queryItems) } guard let url = components.url else { @@ -47,15 +47,15 @@ public struct CreateRequest { let defaultHeaderFields = connection.host.defaultHeaders let connectionHeaderFields = connection.httpHeaders - let headerFields = defaultHeaderFields.merging(connectionHeaderFields) { (_, new) in new } + var headerFields = defaultHeaderFields.merging(connectionHeaderFields) { (_, new) in new } + headerFields.merge(headers) { (_, new) in new } if !headerFields.isEmpty { urlRequest.allHTTPHeaderFields = headerFields } + + urlRequest.httpBody = body request = urlRequest - #if DEBUG - DebugPrint.logAPIRequest(request: request, apiName: connection.name) - #endif } } diff --git a/Sources/NetworkKit/Debug Loging/Debug Logging.swift b/Sources/NetworkKit/Debug Loging/Debug Logging.swift deleted file mode 100644 index dc32772..0000000 --- a/Sources/NetworkKit/Debug Loging/Debug Logging.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// DebugLogging.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -typealias DebugPrint = PilotDebugLogs - -/// Allows Logs to be Printed in Debug Console. -public struct PilotDebugLogs { - - /// Enabled Network debug logs to be printed in debug console. - /// Default value is `true` - public static var isEnabled = true - - // MARK: - DEBUG LOGS HANDLER - - /// Writes the textual representations of the given items into the standard output. - /// - Parameter value: String to be printed on console. - /// - Parameter flag: If the value should be printed on console - static func print(_ value: String, shouldPrint flag: Bool = true) { - if DebugPrint.isEnabled, flag { - Swift.print(value) - } - } - - static func logAPIRequest(request: URLRequest, apiName: String?) { - print( - """ - ------------------------------------------------------------ - API Call Request for: - Name: \(apiName ?? "nil") - \(request.debugDescription) - - """ - ) - } -} diff --git a/Sources/NetworkKit/Debug Loging/NKLogger.swift b/Sources/NetworkKit/Debug Loging/NKLogger.swift new file mode 100644 index 0000000..9bcb0e5 --- /dev/null +++ b/Sources/NetworkKit/Debug Loging/NKLogger.swift @@ -0,0 +1,226 @@ +// +// NKLogger.swift +// NetworkKit +// +// Created by Raghav Ahuja on 18/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +final public class NKLogger { + + /// Allows Logs to be Printed in Debug Console. + /// Default value is `true` + public var isLoggingEnabled: Bool = true + + public static let `default` = NKLogger() + + /** + Creates a `NKLogger`. + */ + public init() { } + + /** + Writes the textual representations of the given items into the standard output. + + - parameter items: Zero or more items to print.. + - parameter separator: A string to print between each item. The default is a single space (" "). + - parameter terminator: The string to print after all items have been printed. The default is a newline ("\n"). + + */ + func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { + #if DEBUG + guard NKConfiguration.allowLoggingOnAllSessions, isLoggingEnabled else { return } + Swift.print(items, separator: separator, terminator: terminator) + #endif + } + + /** + Writes the textual representations of the given items most suitable for debugging into the standard output. + + - parameter items: Zero or more items to print. + - parameter separator: A string to print between each item. The default is a single space (" "). + - parameter terminator: The string to print after all items have been printed. The default is a newline ("\n"). + + */ + func debugPrint(_ items: Any..., separator: String = " ", terminator: String = "\n") { + #if DEBUG + guard NKConfiguration.allowLoggingOnAllSessions, isLoggingEnabled else { return } + Swift.debugPrint(items, separator: separator, terminator: terminator) + #endif + } + + /** + Handles APIRequest logging sent by the `NetworkKit`. + + - parameter request: URLRequest + - parameter apiName: API name. + */ + func logAPIRequest(request: URLRequest?, apiName: String?) { + #if DEBUG + guard NKConfiguration.allowLoggingOnAllSessions, isLoggingEnabled else { return } + + Swift.print( + """ + ------------------------------------------------------------ + API Call Request for: + Name: \(apiName ?? "nil") + \(request?.debugDescription ?? "") + + """ + ) + #endif + } + + /** + Print JSON sent by the `NetworkKit`. + + - parameter data: Input Type to be printed + - parameter apiName: API name. + */ + func printJSON(data: Input, apiName: String) { + #if DEBUG + guard NKConfiguration.allowLoggingOnAllSessions, isLoggingEnabled else { return } + guard let data = data as? Data else { + return + } + + do { + let object = try JSONSerialization.jsonObject(with: data, options: []) + let newData = try JSONSerialization.data(withJSONObject: object, options: .prettyPrinted) + +// Swift.print( +// """ +// ------------------------------------------------------------ +// Printing JSON for: +// API Name: \(apiName) +// JSON: +// + // """) + Swift.print(""" + ------------------------------------------------------------ + JSON: + + """) + Swift.print(String(data: newData, encoding: .utf8) ?? "nil") + Swift.print("------------------------------------------------------------") + + } catch { + + } + #endif + } + + /** + Handles errors sent by the `NetworkKit`. + + - parameter error: Error occurred. + - parameter file: Source file name. + - parameter line: Source line number. + - parameter function: Source function name. + */ + @inline(__always) + func log(error: Error, file: StaticString = #file, line: UInt = #line, function: StaticString = #function) { + #if DEBUG + guard NKConfiguration.allowLoggingOnAllSessions, isLoggingEnabled else { return } + + Swift.print("⚠️ [NetworkKit: Error] \((String(describing: file) as NSString).lastPathComponent):\(line) \(function)\n ↪︎ \(error as NSError)\n") + #endif + } + + /** + Handles assertions made throughout the `NetworkKit`. + + - parameter condition: Assertion condition. + - parameter message: Assertion failure message. + - parameter file: Source file name. + - parameter line: Source line number. + - parameter function: Source function name. + */ + @inline(__always) + func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line, function: StaticString = #function) { + + #if DEBUG + let condition = condition() + + if condition { return } + + let message = message() + + Swift.print("❗ [NetworkKit: Assertion Failure] \((String(describing: file) as NSString).lastPathComponent):\(line) \(function)\n ↪︎ \(message)\n") + Swift.assert(condition, message, file: file, line: line) + #endif + } + + /** + Handles assertion failures made throughout the `NetworkKit`. + + - parameter message: Assertion failure message. + - parameter file: Source file name. + - parameter line: Source line number. + - parameter function: Source function name. + */ + @inlinable public func assertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line, function: StaticString = #function) { + let message = message() + Swift.print("❗ [NetworkKit: Assertion Failure] \((String(describing: file) as NSString).lastPathComponent):\(line) \(function)\n ↪︎ \(message)\n") + Swift.assertionFailure(message, file: file, line: line) + } + + /** + Handles precondition failures made throughout the `NetworkKit`. + + - parameter message: Assertion failure message. + - parameter file: Source file name. + - parameter line: Source line number. + - parameter function: Source function name. + */ + @inlinable public func preconditionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line, function: StaticString = #function) -> Never { + let message = message() + Swift.print("❗ [NetworkKit: Assertion Failure] \((String(describing: file) as NSString).lastPathComponent):\(line) \(function)\n ↪︎ \(message)\n") + Swift.preconditionFailure(message, file: file, line: line) + } + + /** + Handles preconditions made throughout the `NetworkKit`. + + - parameter condition: Precondition to be satisfied. + - parameter message: Precondition failure message. + - parameter file: Source file name. + - parameter line: Source line number. + - parameter function: Source function name. + */ + @inline(__always) + func precondition(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line, function: StaticString = #function) { + + #if DEBUG + let condition = condition() + + if condition { return } + + let message = message() + + Swift.print("❗ [NetworkKit: Precondition Failure] \((String(describing: file) as NSString).lastPathComponent):\(line) \(function)\n ↪︎ \(message)\n") + Swift.preconditionFailure(message, file: file, line: line) + #endif + } + + /** + Handles fatal errors made throughout the `NetworkKit`. + - Important: Implementers should guarantee that this function doesn't return, either by calling another `Never` function such as `fatalError()` or `abort()`, or by raising an exception. + + - parameter message: Fatal error message. + - parameter file: Source file name. + - parameter line: Source line number. + - parameter function: Source function name. + */ + @inline(__always) + func fatalError(_ message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line, function: StaticString = #function) -> Never { + + #if DEBUG + let message = message() + Swift.print("❗ [NetworkKit: Fatal Error] \((String(describing: file) as NSString).lastPathComponent):\(line) \(function)\n ↪︎ \(message)\n") + Swift.fatalError(message, file: file, line: line) + #endif + } +} diff --git a/Sources/NetworkKit/Errors/Business Error.swift b/Sources/NetworkKit/Errors/Business Error.swift deleted file mode 100644 index fd6f8d9..0000000 --- a/Sources/NetworkKit/Errors/Business Error.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// BusinessError.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -/// Personal / Business / Server Errors -enum BusinessError: LocalizedError { - - case errorModel(ErrorModel, Int) - - var localizedDescription: String { - switch self { - case .errorModel(let model, _): - return model.message ?? "" - } - } - - var errorCode: Int { - switch self { - case .errorModel(let model, let httpStatusCode): - return model.code ?? httpStatusCode - } - } -} diff --git a/Sources/NetworkKit/Errors/HTTP Error.swift b/Sources/NetworkKit/Errors/HTTP Error.swift deleted file mode 100644 index f5f8c85..0000000 --- a/Sources/NetworkKit/Errors/HTTP Error.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// HTTPStatusCodes.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -/// HTTP Status Codes, obtained in `HTTPURLResponse` -enum HTTPStatusCode: Int, LocalizedError { - - case `continue` = 100 - case switchingProtocols = 101 - case processing = 102 - case earlyHints = 103 - - - case ok = 200 - case created = 201 - case accepted = 202 - case nonAuthoritativeInformation = 203 - case noContent = 204 - case resetContent = 205 - case partialContent = 206 - case multiStatus = 207 - case alreadyReported = 208 - case imUsed = 226 - - - case multipleChoices = 300 - case movedPermanently = 301 - case found = 302 - case seeOther = 303 - case notModified = 304 - case useProxy = 305 - case temporaryRedirect = 307 - case permanentRedirect = 308 - - - case badRequest = 400 - case unauthorized = 401 - case paymentRequired = 402 - case forbidden = 403 - case notFound = 404 - case methodNotAllowed = 405 - case notAcceptable = 406 - case proxyAuthenticationRequired = 407 - case requestTimeout = 408 - case conflict = 409 - case gone = 410 - case lengthRequired = 411 - case preconditionFailed = 412 - case payloadTooLarge = 413 - case uriTooLong = 414 - case unsupportedMediaType = 415 - case rangeNotSatisfiable = 416 - case expectationFailed = 417 - - case imATeapot = 418 - case misdirectedRequest = 421 - case unprocessableEntity = 422 - case locked = 423 - case failedDependency = 424 - case tooEarly = 425 - case upgradeRequired = 426 - case preconditionRequired = 428 - case tooManyRequests = 429 - case requestHeaderFieldsTooLarge = 431 - case iisLoginTimeout = 440 - case nginxNoResponse = 444 - case iisRetryWith = 449 - case blockedByWindowsParentalControls = 450 - case unavailableForLegalReasons = 451 - case nginxSSLCertificateError = 495 - case nginxSSLCertificateRequired = 496 - - case nginxHTTPToHTTPS = 497 - case tokenExpired = 498 - case nginxClientClosedRequest = 499 - - - case internalServerError = 500 - case notImplemented = 501 - case badGateway = 502 - case serviceUnavailable = 503 - case gatewayTimeout = 504 - case httpVersionNotSupported = 505 - case variantAlsoNegotiates = 506 - case insufficientStorage = 507 - case loopDetected = 508 - case bandwidthLimitExceeded = 509 - case notExtended = 510 - case networkAuthenticationRequired = 511 - case siteIsFrozen = 530 - case networkConnectTimeoutError = 599 - - /// Retrieve the localized description for this error. - var localizedDescription: String { - return HTTPURLResponse.localizedString(forStatusCode: rawValue) - } - - var errorDescription: String? { - return HTTPURLResponse.localizedString(forStatusCode: rawValue) - } -} - -extension HTTPStatusCode { - /// Informational - Request received, continuing process. - var isInformational: Bool { - return isIn(range: 100...199) - } - /// Success - The action was successfully received, understood, and accepted. - var isSuccess: Bool { - return isIn(range: 200...299) - } - /// Redirection - Further action must be taken in order to complete the request. - var isRedirection: Bool { - return isIn(range: 300...399) - } - /// Client Error - The request contains bad syntax or cannot be fulfilled. - var isClientError: Bool { - return isIn(range: 400...499) - } - /// Server Error - The server failed to fulfill an apparently valid request. - var isServerError: Bool { - return isIn(range: 500...599) - } - - /// - returns: `true` if the status code is in the provided range, false otherwise. - private func isIn(range: ClosedRange) -> Bool { - return range.contains(rawValue) - } -} diff --git a/Sources/NetworkKit/Extensions/NSError+Extension.swift b/Sources/NetworkKit/Errors/NSError.swift similarity index 65% rename from Sources/NetworkKit/Extensions/NSError+Extension.swift rename to Sources/NetworkKit/Errors/NSError.swift index b970565..4708a1f 100644 --- a/Sources/NetworkKit/Extensions/NSError+Extension.swift +++ b/Sources/NetworkKit/Errors/NSError.swift @@ -1,8 +1,8 @@ // -// NSError+Extension.swift +// NSError.swift // NetworkKit // -// Created by Raghav Ahuja on 21/11/19. +// Created by Raghav Ahuja on 18/10/19. // Copyright © 2019 Raghav Ahuja. All rights reserved. // @@ -10,19 +10,21 @@ import Foundation extension NSError { + public static var networkKitErrorDomain: String { "NKErrorDomain" } + static func cancelled(for url: URL?) -> NSError { var userInfo: [String: Any] = [NSLocalizedDescriptionKey: "User cancelled the task for url: \(url?.absoluteString ?? "nil")."] if let url = url { userInfo[NSURLErrorFailingURLErrorKey] = url } - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: userInfo) + let error = NSError(domain: networkKitErrorDomain, code: NSURLErrorCancelled, userInfo: userInfo) return error } static func badServerResponse(for url: URL) -> NSError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorBadServerResponse, userInfo: [ + let error = NSError(domain: networkKitErrorDomain, code: NSURLErrorBadServerResponse, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSLocalizedDescriptionKey: "Bad server response for request : \(url.absoluteString)" ]) @@ -30,8 +32,17 @@ extension NSError { return error } + static func badURL(for urlString: String?) -> NSError { + let error = NSError(domain: networkKitErrorDomain, code: NSURLErrorBadURL, userInfo: [ + NSURLErrorFailingURLStringErrorKey: urlString ?? "nil", + NSLocalizedDescriptionKey: "Invalid URL provied." + ]) + + return error + } + static func resourceUnavailable(for url: URL) -> NSError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorResourceUnavailable, userInfo: [ + let error = NSError(domain: networkKitErrorDomain, code: NSURLErrorResourceUnavailable, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSLocalizedDescriptionKey: "A requested resource couldn’t be retrieved from url: \(url.absoluteString)." ]) @@ -45,13 +56,13 @@ extension NSError { userInfo[NSURLErrorFailingURLErrorKey] = url } - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnsupportedURL, userInfo: userInfo) + let error = NSError(domain: networkKitErrorDomain, code: NSURLErrorUnsupportedURL, userInfo: userInfo) return error } static func zeroByteResource(for url: URL) -> NSError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorZeroByteResource, userInfo: [ + let error = NSError(domain: networkKitErrorDomain, code: NSURLErrorZeroByteResource, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSLocalizedDescriptionKey: "A server reported that a URL has a non-zero content length, but terminated the network connection gracefully without sending any data." ]) @@ -60,7 +71,7 @@ extension NSError { } static func cannotDecodeContentData(for url: URL) -> NSError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotDecodeContentData, userInfo: [ + let error = NSError(domain: networkKitErrorDomain, code: NSURLErrorCannotDecodeContentData, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSLocalizedDescriptionKey: "Content data received during a connection request had an unknown content encoding." ]) @@ -69,7 +80,7 @@ extension NSError { } static func cannotDecodeRawData(for url: URL) -> NSError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotDecodeRawData, userInfo: [ + let error = NSError(domain: networkKitErrorDomain, code: NSURLErrorCannotDecodeRawData, userInfo: [ NSURLErrorFailingURLErrorKey: url, NSLocalizedDescriptionKey: "Content data received during a connection request had an unknown content encoding." ]) @@ -83,13 +94,13 @@ extension NSError { userInfo[NSURLErrorFailingURLErrorKey] = url } - let error = NSError(domain: NSURLErrorDomain, code: NSUserCancelledError, userInfo: userInfo) + let error = NSError(domain: networkKitErrorDomain, code: NSUserCancelledError, userInfo: userInfo) return error } static func unkown() -> NSError { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: [ + let error = NSError(domain: networkKitErrorDomain, code: NSURLErrorUnknown, userInfo: [ NSLocalizedDescriptionKey: "An Unknown Occurred." ]) diff --git a/Sources/NetworkKit/Extensions/Dictionary+Extension.swift b/Sources/NetworkKit/Extensions/Dictionary+Extension.swift deleted file mode 100644 index 38bfeaf..0000000 --- a/Sources/NetworkKit/Extensions/Dictionary+Extension.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Dictionary+Extension.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -extension Dictionary { - - var prettyPrint: String { - let prefix = isEmpty ? "" : "\n" - var printString = "\(prefix)[" - - for (key, value) in self { - let itemString = "\n\t\(key): \(value)," - printString.append(itemString) - } - - let postfix = isEmpty ? " " : "\n" - printString.append("\(postfix)]") - - return printString - } -} - -extension Dictionary where Value: OptionalDelegate { - - var prettyPrint: String { - let prefix = isEmpty ? "" : "\n" - var printString = "\(prefix)[" - - for (key, value) in self { - let optionalValue = value.unwrappedValue() - let value = optionalValue == nil ? "nil" : "\(optionalValue!)" - - let itemString = "\n\t\(key): \(value)," - printString.append(itemString) - } - - let postfix = isEmpty ? " " : "\n" - printString.append("\(postfix)]") - - return printString - } -} diff --git a/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift b/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift deleted file mode 100644 index 79b8a8d..0000000 --- a/Sources/NetworkKit/Extensions/JSONDecoder+Extension.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// JSONDecoder+Extension.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -extension JSONDecoder: NetworkDecoder { - - public typealias Input = Data -} diff --git a/Sources/NetworkKit/Extensions/Optional+Extension.swift b/Sources/NetworkKit/Extensions/Optional+Extension.swift deleted file mode 100644 index 07f1b81..0000000 --- a/Sources/NetworkKit/Extensions/Optional+Extension.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Optional+Extension.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -protocol OptionalDelegate { - - associatedtype Wrapped - - func unwrappedValue() -> Wrapped? -} - -extension Optional: OptionalDelegate { - - func unwrappedValue() -> Wrapped? { - return self - } -} diff --git a/Sources/NetworkKit/Extensions/Array+Extension.swift b/Sources/NetworkKit/Extensions/Set+Extension.swift similarity index 83% rename from Sources/NetworkKit/Extensions/Array+Extension.swift rename to Sources/NetworkKit/Extensions/Set+Extension.swift index a420376..63b79dc 100644 --- a/Sources/NetworkKit/Extensions/Array+Extension.swift +++ b/Sources/NetworkKit/Extensions/Set+Extension.swift @@ -1,5 +1,5 @@ // -// Array+Extension.swift +// Set+Extension.swift // NetworkKit // // Created by Raghav Ahuja on 15/10/19. @@ -8,14 +8,14 @@ import Foundation -extension Array where Element == URLQueryItem { +extension Set where Element == URLQueryItem { mutating func addURLQuery(query urlQuery: URLQuery?) { if let urlQuery = urlQuery { for query in urlQuery { let queryItem = URLQueryItem(name: query.key, value: query.value) if !self.contains(queryItem) { - self.append(queryItem) + insert(queryItem) } } } diff --git a/Sources/NetworkKit/Extensions/URL+Extension.swift b/Sources/NetworkKit/Extensions/URL+Extension.swift deleted file mode 100644 index e20dad4..0000000 --- a/Sources/NetworkKit/Extensions/URL+Extension.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// URL+Extension.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -extension URL { - - var parameters: URLQuery { - guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { - return [:] - } - return components.queryItems?.toDictionary ?? [:] - } -} diff --git a/Sources/NetworkKit/Extensions/URLRequest+Extension.swift b/Sources/NetworkKit/Extensions/URLRequest+Extension.swift deleted file mode 100644 index b75db4f..0000000 --- a/Sources/NetworkKit/Extensions/URLRequest+Extension.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// URLRequest+Extension.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -extension URLRequest { - - var debugDescription: String { - """ - ------------------------------------------------------------ - Request Method: \(httpMethod ?? "nil") - Request URL: \(url?.absoluteString ?? "nil") - - Request Parameters: \((url?.parameters ?? [:]).prettyPrint) - - Request Headers: \((allHTTPHeaderFields ?? [:]).prettyPrint) - - Request HTTPBody: \(httpBody?.debugDescription ?? "nil") - ------------------------------------------------------------ - """ - } -} diff --git a/Sources/NetworkKit/Kit/NKAnyCancellable/NKAnyCancellable.swift b/Sources/NetworkKit/Kit/NKAnyCancellable/NKAnyCancellable.swift deleted file mode 100644 index 7984b62..0000000 --- a/Sources/NetworkKit/Kit/NKAnyCancellable/NKAnyCancellable.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// NKAnyCancellable.swift -// NetworkKit -// -// Created by Raghav Ahuja on 25/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public class NKAnyCancellable: NKCancellable { - - let block: () -> Void - - /// Initializes the cancellable object with the given cancel-time closure. - /// - /// - Parameter cancel: A closure that the `cancel()` method executes. - public init(cancel: @escaping () -> Void) { - block = cancel - } - - public init(_ canceller: C) { - block = canceller.cancel - } - - deinit { - cancel() - } - - public func cancel() { - block() - } -} diff --git a/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift b/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift deleted file mode 100644 index 297203a..0000000 --- a/Sources/NetworkKit/Kit/NKQueue/NKQueue.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// NKQueue.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public final class NKQueue { - - /// A queue that regulates the execution of operations. - /// It is a serial queue, that has `quality of service` of `utility`. - private let operationQueue: OperationQueue - - let request: URLRequest? - - let apiName: String - - var operations: [Operation] { - operationQueue.operations - } - - var isSuspended: Bool { - get { operationQueue.isSuspended } - set { operationQueue.isSuspended = newValue } - } - - init(operationQueue: OperationQueue, request: URLRequest?, apiName: String?) { - self.operationQueue = operationQueue - self.request = request - self.apiName = apiName ?? request?.url?.absoluteString ?? "nil" - } - - deinit { - operationQueue.cancelAllOperations() - } - - func addOperation(_ op: Operation) { - operationQueue.addOperation(op) - } - - func addOperation(_ block: @escaping () -> Void) { - operationQueue.addOperation(block) - } - - func cancelAllOperations() { - operationQueue.cancelAllOperations() - } -} diff --git a/Sources/NetworkKit/Kit/NKResult/NKResult.swift b/Sources/NetworkKit/Kit/NKResult/NKResult.swift deleted file mode 100644 index b78952c..0000000 --- a/Sources/NetworkKit/Kit/NKResult/NKResult.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// NKResult.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public final class NKResult { - - var result: Result? - - var operation: Operation? - - init(result: Result?) { - self.result = result - } - - init() { - let error = NSError.notStarted(for: nil) - result = .failure(error as! Failure) - } -} diff --git a/Sources/NetworkKit/Kit/Network Task.swift b/Sources/NetworkKit/Kit/Network Task.swift deleted file mode 100644 index 34314e9..0000000 --- a/Sources/NetworkKit/Kit/Network Task.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// NetworkTask.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public struct NetworkTask: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue - - public typealias Output = (data: Data, response: HTTPURLResponse) - - public typealias Failure = NSError - - public let request: URLRequest? - - public let session: URLSession - - private let operationQueue: OperationQueue = { - let queue = OperationQueue() - queue.maxConcurrentOperationCount = 1 - queue.qualityOfService = .utility - queue.isSuspended = true - return queue - }() - - /// Creates a data task from the provided Network Request and URL session. - /// - Parameter session: The `URLSession` from `NetworkConfiguration` to create the data task. - /// - Parameter builder: The block which returns a `NetworkRequest` to create a URL session data task. - public init(session: NetworkConfiguration, _ builder: () -> NetworkRequest) { - self.init(session: session.session, builder) - } - - /// Creates a data task from the provided URL request and URL session. - /// - Parameter request: The `URLRequest` from which to create a URL session data task. - /// - Parameter session: The `URLSession` from `NetworkConfiguration` to create the data task. - /// - Parameter apiName: API Name for debug console logging. - public init(request: URLRequest, session: NetworkConfiguration, apiName: String? = nil) { - self.init(request: request, session: session.session, apiName: apiName) - } - - - /// Creates a data task from the provided URL and URL session. - /// - Parameter url: The `URL` from which to create a URL session data task. - /// - Parameter session: The `URLSession` from `NetworkConfiguration` to create the data task. - /// - Parameter apiName: API Name for debug console logging. - public init(url: URL, session: NetworkConfiguration, apiName: String? = nil) { - self.init(url: url, session: session.session, apiName: apiName) - } - - /// Creates a data task from the provided Network Request and URL session. - /// - Parameter session: The `URLSession` to create the data task. - /// - Parameter builder: The block which returns a `NetworkRequest` to create a URL session data task. - public init(session: URLSession, _ builder: () -> NetworkRequest) { - let requestBuilder = DispatchQueue.global(qos: .utility).sync { builder() } - - self.session = session - - request = requestBuilder.request - - result = .init() - queue = .init(operationQueue: operationQueue, - request: requestBuilder.request, - apiName: requestBuilder.apiName) - resume() - } - - /// Creates a data task from the provided URL request and URL session. - /// - Parameter request: The `URLRequest` from which to create a URL session data task. - /// - Parameter session: The `URLSession` to create the data task. - /// - Parameter apiName: API Name for debug console logging. - public init(request: URLRequest, session: URLSession, apiName: String? = nil) { - self.request = request - - self.session = session - - result = .init() - queue = .init(operationQueue: operationQueue, - request: request, - apiName: apiName) - resume() - } - - /// Creates a data task from the provided URL and URL session. - /// - Parameter url: The `URL` from which to create a URL session data task. - /// - Parameter session: The `URLSession` to create the data task. - /// - Parameter apiName: API Name for debug console logging. - public init(url: URL, session: URLSession, apiName: String? = nil) { - request = URLRequest(url: url) - - self.session = session - - result = .init() - queue = .init(operationQueue: operationQueue, - request: request, - apiName: apiName) - resume() - } - - private func resume() { - let fetchOperation = FetchOperation(session: session, request: request, result: result) - addToQueue(isSuspended: true, fetchOperation) - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift deleted file mode 100644 index d395e3d..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Catch.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Catch.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - struct Catch: NKPublisher where Upstream.Output == NewPublisher.Output { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = Upstream.Output - - public typealias Failure = NewPublisher.Failure - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. - public let handler: (Upstream.Failure) -> NewPublisher - - /// Creates a publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. - /// - /// - Parameters: - /// - upstream: The publisher that this publisher receives elements from. - /// - handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. - public init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher) { - self.upstream = upstream - self.handler = handler - result = .init() - - let operation = CatchOperation(upstream: upstream, handler: handler, result: result) - addToQueue(operation) - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift b/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift deleted file mode 100644 index ba716d2..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Catch/Try Catch.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Try Catch.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - struct TryCatch: NKPublisher where Upstream.Output == NewPublisher.Output { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = Upstream.Output - - public typealias Failure = NewPublisher.Failure - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. - public let handler: (Upstream.Failure) throws -> NewPublisher - - /// Creates a publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. - /// - /// - Parameters: - /// - upstream: The publisher that this publisher receives elements from. - /// - handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. - public init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher) { - self.upstream = upstream - self.handler = handler - result = .init() - - let operation = TryCatchOperation(upstream: upstream, handler: handler, result: result) - addToQueue(operation) - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Compact Map/Compact Map.swift b/Sources/NetworkKit/Kit/Publishers/Compact Map/Compact Map.swift deleted file mode 100644 index 408b02f..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Compact Map/Compact Map.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// Compact Map.swift -// NetworkKit -// -// Created by Raghav Ahuja on 26/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - /// A publisher that republishes all non-`nil` results of calling a closure with each received element. - struct CompactMap: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = MapOutput - - public typealias Failure = Upstream.Failure - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - /// The closure that transforms elements from the upstream publisher. - public let transform: (Upstream.Output) -> Output? - - public init(upstream: Upstream, transform: @escaping (Upstream.Output) -> Output?) { - self.upstream = upstream - self.transform = transform - result = .init() - perform() - } - - private func perform() { - addToQueue { - self.doTransform() - } - } - - private func doTransform() { - let upstreamResult = upstream.result.result - - switch upstreamResult { - case .success(let output): - guard let newOutput = transform(output) else { - result.result = .none - return - } - result.result = .success(newOutput) - - case .failure(let error): - result.result = .failure(error) - - case .none: - result.result = .none - } - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Compact Map/Try Compact Map.swift b/Sources/NetworkKit/Kit/Publishers/Compact Map/Try Compact Map.swift deleted file mode 100644 index ec490f2..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Compact Map/Try Compact Map.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// Compact Map.swift -// NetworkKit -// -// Created by Raghav Ahuja on 26/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - /// A publisher that republishes all non-`nil` results of calling an error-throwing closure with each received element. - struct TryCompactMap: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = MapOutput - - public typealias Failure = Upstream.Failure - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - /// The closure that transforms elements from the upstream publisher. - public let transform: (Upstream.Output) throws -> Output? - - public init(upstream: Upstream, transform: @escaping (Upstream.Output) throws -> Output?) { - self.upstream = upstream - self.transform = transform - result = .init() - perform() - } - - private func perform() { - addToQueue { - self.doTransform() - } - } - - private func doTransform() { - let upstreamResult = upstream.result.result - - switch upstreamResult { - case .success(let output): - do { - guard let newOutput = try transform(output) else { - result.result = .none - return - } - - result.result = .success(newOutput) - - } catch { - let error = error as NSError - result.result = .failure(error as! Failure) - } - - case .failure(let error): - result.result = .failure(error) - - case .none: - result.result = .none - } - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift b/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift deleted file mode 100644 index 5573b05..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Debounce/Debounce.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Debounce.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - struct Debounce: NKPublisher { - - public var result: NKResult { - upstream.result - } - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = Upstream.Output - - public typealias Failure = Upstream.Failure - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - public let dueTime: DispatchTime - - public init(upstream: Upstream, dueTime: DispatchTime) { - self.upstream = upstream - self.dueTime = dueTime - perform() - } - - private func perform() { - let operation = DebounceOperation(result: result, url: queue.request?.url, dueTime: dueTime) - queue.operations.first?.addDependency(operation) - addToQueue(operation) - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift b/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift deleted file mode 100644 index d7020f1..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Decode/Decode.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// Decode.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - struct Decode: NKPublisher where Upstream.Output == Decoder.Input { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = Item - - public typealias Failure = NSError - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - public let decoder: Decoder - - public init(upstream: Upstream, decoder: Decoder) { - self.upstream = upstream - self.decoder = decoder - result = .init() - perform() - } - - public init(upstream: Upstream, jsonKeyDecodingStrategy: JSONDecoder.KeyDecodingStrategy) { - self.upstream = upstream - - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = jsonKeyDecodingStrategy - self.decoder = decoder as! Decoder - - result = .init() - perform() - } - - private func perform() { - addToQueue { - self.doDecoding() - } - } - - private func doDecoding() { - let upstreamResult = upstream.result.result - - switch upstreamResult { - case .success(let data): - - do { - let output = try decoder.decode(Item.self, from: data) - self.result.result = .success(output) - - } catch { - result.result = .failure(error as NSError) - } - - case .failure(let error): - result.result = .failure(error as NSError) - - case .none: - result.result = .none - } - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Error/Map Error.swift b/Sources/NetworkKit/Kit/Publishers/Map/Error/Map Error.swift deleted file mode 100644 index d266aac..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Map/Error/Map Error.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Map Error.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - struct MapError: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = Upstream.Output - - public typealias Failure = MapFailure - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - /// The closure that transforms elements from the upstream publisher. - public let transform: (Upstream.Failure) -> MapFailure - - public init(upstream: Upstream, transform: @escaping (Upstream.Failure) -> MapFailure) { - self.upstream = upstream - self.transform = transform - result = .init(result: .none) - perform() - } - - private func perform() { - addToQueue { - self.doTransform() - } - } - - private func doTransform() { - let upstreamResult = upstream.result.result - - switch upstreamResult { - case .success(let output): - result.result = .success(output) - - case .failure(let error): - let newError = transform(error) - result.result = .failure(newError) - - case .none: - result.result = .none - } - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath 2.swift b/Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath 2.swift deleted file mode 100644 index f05e589..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath 2.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// Map KeyPath 2.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -extension NKPublishers { - - /// A publisher that publishes the values of two key paths as a tuple. - public struct MapKeyPath2: NKPublisher { - - public var result: NKResult<(Output0, Output1), Upstream.Failure> - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = (Output0, Output1) - - public typealias Failure = Upstream.Failure - - /// The publisher from which this publisher receives elements. - public let upstream: Upstream - - /// The key path of a property to publish. - public let keyPath0: KeyPath - - /// The key path of a second property to publish. - public let keyPath1: KeyPath - - public init(upstream: Upstream, keyPath0: KeyPath, keyPath1: KeyPath) { - self.upstream = upstream - self.keyPath0 = keyPath0 - self.keyPath1 = keyPath1 - - result = .init() - perform() - } - - private func perform() { - addToQueue { - self.map() - } - } - - private func map() { - switch upstream.result.result { - case .success(let output): - let output1 = output[keyPath: keyPath0] - let output2 = output[keyPath: keyPath1] - - result.result = .success((output1, output2)) - - case .failure(let error): - result.result = .failure(error) - - case .none: - result.result = .none - } - } - } - -} diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath.swift b/Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath.swift deleted file mode 100644 index decd805..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Map/Keypath/Map Keypath.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Map KeyPath.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - /// A publisher that publishes the value of a key path. - struct MapKeyPath: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Failure = Upstream.Failure - - /// The publisher from which this publisher receives elements. - public let upstream: Upstream - - /// The key path of a property to publish. - public let keyPath: KeyPath - - public init(upstream: Upstream, keyPath: KeyPath) { - self.upstream = upstream - self.keyPath = keyPath - result = .init() - perform() - } - - private func perform() { - addToQueue { - self.map() - } - } - - private func map() { - switch upstream.result.result { - case .success(let output): - result.result = .success(output[keyPath: keyPath]) - - case .failure(let error): - result.result = .failure(error) - - case .none: - result.result = .none - } - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Map.swift deleted file mode 100644 index e6ab7d9..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Map/Map.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Map.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - struct Map: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = MapOutput - - public typealias Failure = Upstream.Failure - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - /// The closure that transforms elements from the upstream publisher. - public let transform: (Upstream.Output) -> Output - - public init(upstream: Upstream, transform: @escaping (Upstream.Output) -> Output) { - self.upstream = upstream - self.transform = transform - result = .init() - perform() - } - - private func perform() { - addToQueue { - self.doTransform() - } - } - - private func doTransform() { - let upstreamResult = upstream.result.result - - switch upstreamResult { - case .success(let output): - let newOutput = transform(output) - result.result = .success(newOutput) - - case .failure(let error): - result.result = .failure(error) - - case .none: - result.result = .none - } - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift b/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift deleted file mode 100644 index a929cae..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Map/Try Map.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// Try Map.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - struct TryMap: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = MapOutput - - public typealias Failure = Upstream.Failure - - /// The publisher that this publisher receives elements from. - public let upstream: Upstream - - /// The closure that transforms elements from the upstream publisher. - public let transform: (Upstream.Output) throws -> Output - - public init(upstream: Upstream, transform: @escaping (Upstream.Output) throws -> Output) { - self.upstream = upstream - self.transform = transform - result = .init() - perform() - } - - private func perform() { - addToQueue { - self.doTransform() - } - } - - private func doTransform() { - let upstreamResult = upstream.result.result - - switch upstreamResult { - case .success(let output): - - do { - let newOutput = try transform(output) - result.result = .success(newOutput) - - } catch { - let error = error as NSError - result.result = .failure(error as! Failure) - } - - case .failure(let error): - result.result = .failure(error) - - case .none: - result.result = .none - } - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/NKPublishers.swift b/Sources/NetworkKit/Kit/Publishers/NKPublishers.swift deleted file mode 100644 index ab99513..0000000 --- a/Sources/NetworkKit/Kit/Publishers/NKPublishers.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// NKPublishers.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public enum NKPublishers { -} diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Empty/Replace Empty.swift b/Sources/NetworkKit/Kit/Publishers/Replace Empty/Replace Empty.swift deleted file mode 100644 index d948035..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Replace Empty/Replace Empty.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Replace Empty.swift -// NetworkKit -// -// Created by Raghav Ahuja on 26/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - /// A publisher that replaces an empty stream with a provided element. - struct ReplaceEmpty: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = Upstream.Output - - public typealias Failure = Never - - /// The element with which to replace errors from the upstream publisher. - public let output: Upstream.Output - - /// The publisher from which this publisher receives elements. - public let upstream: Upstream - - public init(upstream: Upstream, output: Output) { - self.upstream = upstream - self.output = output - result = .init(result: .success(output)) - perform() - } - - private func perform() { - let operation = ReplaceEmptyOperation(upstream: upstream, output: output, result: result) - addToQueue(operation) - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift b/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift deleted file mode 100644 index ff1cd13..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Replace Error/Replace Error.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Replace Error.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - /// A publisher that replaces any errors in the stream with a provided element. - struct ReplaceError: NKPublisher { - - public var result: NKResult - - public var queue: NKQueue { - upstream.queue - } - - public typealias Output = Upstream.Output - - public typealias Failure = Never - - /// The element with which to replace errors from the upstream publisher. - public let output: Upstream.Output - - /// The publisher from which this publisher receives elements. - public let upstream: Upstream - - public init(upstream: Upstream, output: Output) { - self.upstream = upstream - self.output = output - result = .init(result: .success(output)) - perform() - } - - private func perform() { - let operation = ReplaceErrorOperation(upstream: upstream, output: output, result: result) - addToQueue(operation) - } - } -} diff --git a/Sources/NetworkKit/Kit/Publishers/Validate/Validate.swift b/Sources/NetworkKit/Kit/Publishers/Validate/Validate.swift deleted file mode 100644 index 44598a5..0000000 --- a/Sources/NetworkKit/Kit/Publishers/Validate/Validate.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// Validate.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublishers { - - struct Validate: NKPublisher where Upstream.Output == NetworkTask.Output, Upstream.Failure == NetworkTask.Failure { - - public var queue: NKQueue { - upstream.queue - } - - public var result: NKResult - - public typealias Output = NetworkTask.Output - - public typealias Failure = NetworkTask.Failure - - /// The publisher from which this publisher receives elements. - public let upstream: Upstream - - /// Check for any business error model on failure. - public let shouldCheckForErrorModel: Bool - - /// Acceptable HTTP Status codes for the network call. - public let acceptableStatusCodes: [Int] - - public init(upstream: Upstream, shouldCheckForErrorModel: Bool, acceptableStatusCodes: [Int]) { - self.upstream = upstream - self.shouldCheckForErrorModel = shouldCheckForErrorModel - - self.acceptableStatusCodes = acceptableStatusCodes - - result = .init() - perform() - } - - private func perform() { - addToQueue(isSuspended: true) { - self.doValidation() - } - } - - private func doValidation() { - guard let url = queue.request?.url else { - result.result = .failure(.unsupportedURL(for: nil)) - return - } - - guard let (data, response) = try? upstream.result.result?.get() else { - result.result = upstream.result.result - return - } - - if acceptableStatusCodes.contains(response.statusCode) { - - guard !data.isEmpty else { - result.result = .failure(.zeroByteResource(for: url)) - return - } - - var acceptableContentTypes: [String] { - if let accept = queue.request?.value(forHTTPHeaderField: "Accept") { - return accept.components(separatedBy: ",") - } - - return ["*/*"] - } - - guard let responseContentType = response.mimeType, let responseMIMEType = MIMEType(responseContentType) else { - for contentType in acceptableContentTypes { - if let mimeType = MIMEType(contentType), mimeType.isWildcard { - result.result = .success((data, response)) - return - } - } - - result.result = .failure(.cannotDecodeContentData(for: url)) - return - } - - for contentType in acceptableContentTypes { - if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) { - result.result = .success((data, response)) - return - } - } - - result.result = .failure(.cannotDecodeContentData(for: url)) - - } else { - // On Failure it checks if it a business error. - - if shouldCheckForErrorModel, !data.isEmpty { - - let model = try? JSONDecoder().decode(ErrorModel.self, from: data) - if let errorModel = model { - let error = BusinessError.errorModel(errorModel, response.statusCode) - result.result = .failure(error as NSError) - - return - } - - // else throw http or url error - if let httpError = HTTPStatusCode(rawValue: response.statusCode) { - result.result = .failure(httpError as NSError) - } else { - result.result = .failure(.badServerResponse(for: url)) - } - } - } - } - } -} - - -private extension NKPublishers.Validate { - - /// ACCEPTABLE CONTENT TYPE CHECK - struct MIMEType { - let type: String - let subtype: String - - var isWildcard: Bool { return type == "*" && subtype == "*" } - - init?(_ string: String) { - let components: [String] = { - let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines) - let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)] - return split.components(separatedBy: "/") - }() - - if let type = components.first, let subtype = components.last { - self.type = type - self.subtype = subtype - } else { - return nil - } - } - - func matches(_ mime: MIMEType) -> Bool { - switch (type, subtype) { - case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"): - return true - default: - return false - } - } - } -} diff --git a/Sources/NetworkKit/Models/API Analytics.swift b/Sources/NetworkKit/Models/API Analytics.swift deleted file mode 100644 index e350326..0000000 --- a/Sources/NetworkKit/Models/API Analytics.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// APIAnalytics.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public struct APIAnalytics { - - public let apiName: String - public let urlString: String - public let totalTime: TimeInterval - public let errorCode: Int? - public let errorMessage: String? -} diff --git a/Sources/NetworkKit/Models/Error Model.swift b/Sources/NetworkKit/Models/Error Model.swift deleted file mode 100644 index 265ba29..0000000 --- a/Sources/NetworkKit/Models/Error Model.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// ErrorModel.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -// MARK: - ErrorModel -public struct ErrorModel: Codable { - - public let businessCode: Int? - public let errorCode: Int? - public let message: String? - public let status: String? - - public var code: Int? { - return businessCode ?? errorCode - } - - public enum CodingKeys: String, CodingKey { - case businessCode = "business_error_code" - case errorCode = "error_code" - case message - case status - } -} diff --git a/Sources/NetworkKit/Networking/Configuration/Network Configuration+Notification.swift b/Sources/NetworkKit/Networking/Configuration/Configuration+Notification.swift similarity index 82% rename from Sources/NetworkKit/Networking/Configuration/Network Configuration+Notification.swift rename to Sources/NetworkKit/Networking/Configuration/Configuration+Notification.swift index c3eec3c..df088ad 100644 --- a/Sources/NetworkKit/Networking/Configuration/Network Configuration+Notification.swift +++ b/Sources/NetworkKit/Networking/Configuration/Configuration+Notification.swift @@ -1,5 +1,5 @@ // -// Network Configuration+Notification.swift +// NKConfiguration+Notification.swift // NetworkKit // // Created by Raghav Ahuja on 15/10/19. @@ -10,7 +10,7 @@ import AppKit.NSApplication // MARK: - NOTIFICATION OBSERVERS -extension NetworkConfiguration { +extension NKConfiguration { var notification: Notification.Name { NSApplication.willTerminateNotification } } @@ -21,7 +21,7 @@ extension NetworkConfiguration { import UIKit.UIApplication // MARK: - NOTIFICATION OBSERVERS -extension NetworkConfiguration { +extension NKConfiguration { var notification: Notification.Name { UIApplication.willTerminateNotification } } diff --git a/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift b/Sources/NetworkKit/Networking/Configuration/Configuration.swift similarity index 74% rename from Sources/NetworkKit/Networking/Configuration/Network Configuration.swift rename to Sources/NetworkKit/Networking/Configuration/Configuration.swift index 90648f2..65db5a8 100644 --- a/Sources/NetworkKit/Networking/Configuration/Network Configuration.swift +++ b/Sources/NetworkKit/Networking/Configuration/Configuration.swift @@ -1,5 +1,5 @@ // -// Network Configuration.swift +// NKConfiguration.swift // NetworkKit // // Created by Raghav Ahuja on 15/10/19. @@ -8,20 +8,32 @@ import Foundation -// MARK: - NETWORK CONFIGURATION +// MARK: - NKCONFIGURATION /// A Class that coordinates a group of related network data transfer tasks. -open class NetworkConfiguration { +open class NKConfiguration { - /// Allows Logs to be Printed in Debug Console. - /// Default value is `true` - open var debugPrint: Bool + /// Enables Logging for this session. + /// Default value is `true`. + open var enableLogs: Bool { + get { + logger.isLoggingEnabled + } set { + logger.isLoggingEnabled = newValue + } + } + + var logger = NKLogger() + + /// Should allow logging for all sessions. + /// Default value is `false`. + public static var allowLoggingOnAllSessions: Bool = true - /// Should Empty URL Cache Before Application Terminates. + /// Should empty cache before application terminates. /// Default value is `true`. open var emptyCacheOnAppTerminate: Bool - /// Should Empty URL Cache Before Application Terminates. + /// Should empty cache before application terminates. /// Default value is `false`. public static var emptyCacheOnAppTerminateOnAllSessions: Bool = false @@ -51,7 +63,7 @@ open class NetworkConfiguration { /// Inititalises Manager with `defaultURLSessionConfiguration` Configuration. public init(useDefaultCache: Bool, requestCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, cacheDiskPath diskPath: String? = nil) { - let configuration = NetworkConfiguration.defaultConfiguration + let configuration = NKConfiguration.defaultConfiguration configuration.requestCachePolicy = requestCachePolicy if useDefaultCache { @@ -68,7 +80,7 @@ open class NetworkConfiguration { } session = URLSession(configuration: configuration) - debugPrint = true +// session.nkLogger = .init() emptyCacheOnAppTerminate = true setNotificationObservers() } @@ -76,7 +88,7 @@ open class NetworkConfiguration { public init(urlCache cache: URLCache? = nil, configuration config: URLSessionConfiguration) { urlCache = cache session = URLSession(configuration: config) - debugPrint = true +// session.nkLogger = .init() emptyCacheOnAppTerminate = true setNotificationObservers() } @@ -90,7 +102,7 @@ open class NetworkConfiguration { private func setNotificationObservers() { #if !canImport(WatchKit) NotificationCenter.default.addObserver(forName: notification, object: nil, queue: .init()) { [weak self] (_) in - if let `self` = self, self.emptyCacheOnAppTerminate || NetworkConfiguration.emptyCacheOnAppTerminateOnAllSessions { + if let `self` = self, self.emptyCacheOnAppTerminate || NKConfiguration.emptyCacheOnAppTerminateOnAllSessions { self.removeAllCachedResponses() } } @@ -100,7 +112,7 @@ open class NetworkConfiguration { // MARK: - URL CACHE MANAGER -extension NetworkConfiguration { +extension NKConfiguration { /// Remove All Cached Responses from this session. open func removeAllCachedResponses() { @@ -110,16 +122,9 @@ extension NetworkConfiguration { #if DEBUG public func printURLCacheDetails() { guard let cache = session.configuration.urlCache else { - print( - """ - - --------------------------------------------- - Cannot Print Cache Memory And Disk Capacity And Usage - Error - No URL Cache Found - --------------------------------------------- - - """ - ) + logger.print( + "Cannot Print Cache Memory And Disk Capacity And Usage", + "Error - No URL Cache Found") return } let byteToMb: Double = 1048576 @@ -130,26 +135,19 @@ extension NetworkConfiguration { let diskCapacity = Double(cache.diskCapacity) / byteToMb let diskUsage = Double(cache.currentDiskUsage) / byteToMb - print( - """ - - --------------------------------------------- - Current URL Cache Memory And Disk Capacity And Usage - Memory Capacity: \(String(format: "%.2f", memoryCapacity)) Mb - Memory Usage: \(String(format: "%.3f", memoryUsage)) Mb - - Disk Capacity: \(String(format: "%.2f", diskCapacity)) Mb - Disk Usage: \(String(format: "%.3f", diskUsage)) Mb - --------------------------------------------- - - """ + logger.print( + "Current URL Cache Memory And Disk Capacity And Usage", + "Memory Capacity: \(String(format: "%.2f", memoryCapacity)) Mb", + "Memory Usage: \(String(format: "%.3f", memoryUsage)) Mb", + "Disk Capacity: \(String(format: "%.2f", diskCapacity)) Mb", + "Disk Usage: \(String(format: "%.3f", diskUsage)) Mb" ) } #endif } // MARK: - SERVER ENVIRONMENT -public extension NetworkConfiguration { +public extension NKConfiguration { /// Updates the current environment. /// - Parameter newEnvironment: New Server Environment to be set. diff --git a/Sources/NetworkKit/Networking/NKImageSession/NKImageSession+ImageType.swift b/Sources/NetworkKit/Networking/ImageSession/ImageSession+ImageType.swift similarity index 100% rename from Sources/NetworkKit/Networking/NKImageSession/NKImageSession+ImageType.swift rename to Sources/NetworkKit/Networking/ImageSession/ImageSession+ImageType.swift diff --git a/Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift b/Sources/NetworkKit/Networking/ImageSession/ImageSession.swift similarity index 73% rename from Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift rename to Sources/NetworkKit/Networking/ImageSession/ImageSession.swift index 5d88903..f039708 100644 --- a/Sources/NetworkKit/Networking/NKImageSession/NKImageSession.swift +++ b/Sources/NetworkKit/Networking/ImageSession/ImageSession.swift @@ -8,12 +8,12 @@ import Foundation -public final class NKImageSession: NetworkConfiguration { +public final class NKImageSession: NKConfiguration { private typealias ImageValidationResult = (response: HTTPURLResponse, data: Data, image: ImageType) public static let shared = NKImageSession() - + public init(useCache: Bool = true, cacheDiskPath: String? = "cachedImages") { let requestCachePolicy: NSURLRequest.CachePolicy = useCache ? .returnCacheDataElseLoad : .useProtocolCachePolicy super.init(useDefaultCache: useCache, requestCachePolicy: requestCachePolicy, cacheDiskPath: cacheDiskPath) @@ -37,27 +37,11 @@ public extension NKImageSession { let task = session.dataTask(with: request) { [weak self] (data, response, error) in guard let `self` = self else { - let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil) - completion(.failure(error)) return } if let error = error as NSError? { - - #if DEBUG - DebugPrint.print( - """ - - --------------------------------------------- - Cannot Fetch Image From: - URL: \(url.absoluteString) - Error: \(error) - --------------------------------------------- - - """ - , shouldPrint: self.debugPrint) - #endif - + self.logger.log(error: error) DispatchQueue.main.async { completion(.failure(error)) } @@ -72,24 +56,10 @@ public extension NKImageSession { completion(.success(value.image)) } - case .failure(let networkError): - - #if DEBUG - DebugPrint.print( - """ - - --------------------------------------------- - Cannot Fetch Image From: - URL: \(url.absoluteString) - Error: \(networkError.localizedDescription) - --------------------------------------------- - - """ - , shouldPrint: self.debugPrint) - #endif - + case .failure(let error): + self.logger.log(error: error) DispatchQueue.main.async { - completion(.failure(networkError)) + completion(.failure(error)) } } } diff --git a/Sources/NetworkKit/Networking/NKSession.swift b/Sources/NetworkKit/Networking/Session.swift similarity index 57% rename from Sources/NetworkKit/Networking/NKSession.swift rename to Sources/NetworkKit/Networking/Session.swift index 0f56e99..1136c22 100644 --- a/Sources/NetworkKit/Networking/NKSession.swift +++ b/Sources/NetworkKit/Networking/Session.swift @@ -7,13 +7,27 @@ // import Foundation +import PublisherKit -final public class NKSession: NetworkConfiguration { +extension URLSession.NKDataTaskPublisher { + + public func validate() -> NKPublishers.Validate { + NKPublishers.Validate(upstream: self, shouldCheckForErrorModel: true, acceptableStatusCodes: NKSession.shared.acceptableStatusCodes) + } +} + +public typealias NKTask = URLSession.NKDataTaskPublisher +public typealias NKAnyCancellable = PublisherKit.NKAnyCancellable +public typealias NKPublishers = PublisherKit.NKPublishers + +final public class NKSession: NKConfiguration { public static let shared = NKSession() + private let queue = DispatchQueue(label: "com.networkkit.task-thread", qos: .utility, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) + private init() { - super.init(configuration: NetworkConfiguration.defaultConfiguration) + super.init(configuration: NKConfiguration.defaultConfiguration) #if DEBUG if let environmentValue = UserDefaults.standard.value(forKey: "api_environment") as? String, !environmentValue.isEmpty { @@ -32,8 +46,18 @@ final public class NKSession: NetworkConfiguration { /// - Parameter builder: The block which returns a `NetworkRequest` to create a URL session data task. /// - Parameter apiName: API Name for debug console logging. /// - Returns: A publisher that wraps a data task for the URL request. - public func dataTask(_ builder: () -> NetworkRequest) -> NetworkTask { - NetworkTask(session: session, builder) + public func dataTask(file: StaticString = #file, line: UInt = #line, function: StaticString = #function, _ builder: () -> NKRequest) -> URLSession.NKDataTaskPublisher { + let creator = builder() + let urlRequest: URLRequest? = queue.sync { + creator.create() + return creator.request + } + + guard let request = urlRequest else { + NKLogger.default.preconditionFailure("Invalid Request Created", file: file, line: line, function: function) + } + + return session.nkTaskPublisher(for: request, apiName: creator.apiName) } /// Returns a publisher that wraps a URL session data task for a given URL request. @@ -42,8 +66,8 @@ final public class NKSession: NetworkConfiguration { /// - Parameter request: The URL request for which to create a data task. /// - Parameter apiName: API Name for debug console logging. /// - Returns: A publisher that wraps a data task for the URL request. - public func dataTask(for request: URLRequest, apiName: String? = nil) -> NetworkTask { - NetworkTask(request: request, session: session, apiName: apiName) + public func dataTask(for request: URLRequest, apiName: String? = nil) -> URLSession.NKDataTaskPublisher { + session.nkTaskPublisher(for: request) } /// Returns a publisher that wraps a URL session data task for a given URL. @@ -52,7 +76,7 @@ final public class NKSession: NetworkConfiguration { /// - Parameter url: The URL for which to create a data task. /// - Parameter apiName: API Name for debug console logging. /// - Returns: A publisher that wraps a data task for the URL. - public func dataTask(for url: URL, apiName: String? = nil) -> NetworkTask { - NetworkTask(url: url, session: session, apiName: apiName) + public func dataTask(for url: URL, apiName: String? = nil) -> URLSession.NKDataTaskPublisher { + session.nkTaskPublisher(for: url) } } diff --git a/Sources/NetworkKit/Operations/Assign Operation.swift b/Sources/NetworkKit/Operations/Assign Operation.swift deleted file mode 100644 index 5bf5096..0000000 --- a/Sources/NetworkKit/Operations/Assign Operation.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Assign Operation.swift -// NetworkKit -// -// Created by Raghav Ahuja on 25/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -final class AssignOperation: Operation where Upstream.Failure == Never { - - let upstrem: Upstream - let keypath: ReferenceWritableKeyPath - let object: Root - - var updatedValue = false - - init(upstrem: Upstream, keypath: ReferenceWritableKeyPath, object: Root) { - self.upstrem = upstrem - self.keypath = keypath - self.object = object - } - - override func main() { - guard let output = try? upstrem.result.result?.get() else { - updatedValue = true - return - } - object[keyPath: keypath] = output - updatedValue = true - } - - override func cancel() { - super.cancel() - if !updatedValue { - main() - } - } -} diff --git a/Sources/NetworkKit/Operations/Asynchronous Operation.swift b/Sources/NetworkKit/Operations/Asynchronous Operation.swift deleted file mode 100644 index 908b9e8..0000000 --- a/Sources/NetworkKit/Operations/Asynchronous Operation.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Asynchronous Operation.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -class AsynchronousOperation: Operation { - override var isAsynchronous: Bool { - return true - } - - private var _isFinished: Bool = false - override var isFinished: Bool { - get { - return _isFinished - } set { - willChangeValue(forKey: "isFinished") - _isFinished = newValue - didChangeValue(forKey: "isFinished") - } - } - - private var _isExecuting: Bool = false - override var isExecuting: Bool { - get { - return _isExecuting - } set { - willChangeValue(forKey: "isExecuting") - _isExecuting = newValue - didChangeValue(forKey: "isExecuting") - } - } - - override func start() { - guard !isCancelled else { - return - } - isExecuting = true - main() - } - - override func main() { - fatalError("Implement in sublcass to perform task") - } - - func finish() { - isExecuting = false - isFinished = true - } -} diff --git a/Sources/NetworkKit/Operations/Base Block Operation.swift b/Sources/NetworkKit/Operations/Base Block Operation.swift deleted file mode 100644 index f86ce7b..0000000 --- a/Sources/NetworkKit/Operations/Base Block Operation.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// Base Block Operation.swift -// NetworkKit -// -// Created by Raghav Ahuja on 23/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -class BaseBlockOperation: BlockOperation { - - var result: NKResult - var request: URLRequest? - - init(request: URLRequest?, result: NKResult, block: @escaping () -> Void) { - self.request = request - self.result = result - super.init() - addExecutionBlock(block) - } - - override func cancel() { - let error = NSError.cancelled(for: request?.url) - result.result = .failure(error as! Failure) - super.cancel() - } -} diff --git a/Sources/NetworkKit/Operations/Catch Operation.swift b/Sources/NetworkKit/Operations/Catch Operation.swift deleted file mode 100644 index 33422ef..0000000 --- a/Sources/NetworkKit/Operations/Catch Operation.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// Catch Operation.swift -// NetworkKit -// -// Created by Raghav Ahuja on 21/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -final class CatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { - - typealias Output = Upstream.Output - - typealias Failure = NewPublisher.Failure - - /// The publisher that this publisher receives elements from. - private let upstream: Upstream - - private var result: NKResult - - /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. - private let handler: (Upstream.Failure) -> NewPublisher - - private var newOperation: Operation? - - init(upstream: Upstream, handler: @escaping (Upstream.Failure) -> NewPublisher, result: NKResult) { - self.upstream = upstream - self.handler = handler - self.result = result - } - - override func main() { - guard !isCancelled else { - return - } - - switch upstream.result.result { - case .success(let output): - result.result = .success(output) - - case .failure(let error): - let newPublisher = handler(error) - - guard let newOperation = newPublisher.result.operation else { - return - } - - self.newOperation = newOperation - - newOperation.completionBlock = { [weak self] in - guard !(self?.isCancelled ?? true) else { - return - } - - switch newPublisher.result.result! { - case .success(let output): - self?.result.result = .success(output) - - case .failure(let error): - self?.result.result = .failure(error) - } - - newPublisher.queue.cancelAllOperations() - self?.finish() - } - - newOperation.main() - - case .none: - result.result = .none - } - } - - override func cancel() { - newOperation?.cancel() - let error = NSError.cancelled(for: upstream.queue.request?.url) - result.result = .failure(error as! Failure) - super.cancel() - } -} diff --git a/Sources/NetworkKit/Operations/Debouce Operation.swift b/Sources/NetworkKit/Operations/Debouce Operation.swift deleted file mode 100644 index f0fb097..0000000 --- a/Sources/NetworkKit/Operations/Debouce Operation.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// Debounce Operation.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -final class DebounceOperation: AsynchronousOperation { - - private let dueTime: DispatchTime - - private let result: NKResult - - private let url: URL? - - init(result: NKResult, url: URL?, dueTime: DispatchTime) { - self.result = result - self.url = url - self.dueTime = dueTime - super.init() - queuePriority = .veryHigh - } - - override func main() { - guard !isCancelled else { - return - } - - DispatchQueue.global(qos: .utility).asyncAfter(deadline: dueTime) { [weak self] in - self?.finish() - } - } - - override func cancel() { - let error = NSError.cancelled(for: url) - result.result = .failure(error as! Failure) - super.cancel() - } -} diff --git a/Sources/NetworkKit/Operations/Fetch Operation.swift b/Sources/NetworkKit/Operations/Fetch Operation.swift deleted file mode 100644 index 02bd341..0000000 --- a/Sources/NetworkKit/Operations/Fetch Operation.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// Fetch Operation.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -final class FetchOperation: AsynchronousOperation { - - private let result: NKResult? - - private let request: URLRequest? - - private let session: URLSession - - var task: URLSessionDataTask? - - init(session: URLSession, request: URLRequest?, result: NKResult?) { - self.session = session - self.request = request - self.result = result - super.init() - - queuePriority = .high - } - - override func main() { - guard !isCancelled else { - return - } - - guard let request = request else { - result?.result = .failure(NSError.unsupportedURL(for: nil)) - finish() - return - } - - task = session.dataTask(with: request) { [weak self] (data, response, error) in - if let error = error as NSError? { - self?.result?.result = .failure(error) - } else if let response = response as? HTTPURLResponse, let data = data { - self?.result?.result = .success((data, response)) - } - - self?.finish() - } - - task?.resume() - } - - override func cancel() { - result?.result = .failure(NSError.cancelled(for: request?.url)) - task?.cancel() - super.cancel() - } -} diff --git a/Sources/NetworkKit/Operations/Replace Empty Operation.swift b/Sources/NetworkKit/Operations/Replace Empty Operation.swift deleted file mode 100644 index f2711b5..0000000 --- a/Sources/NetworkKit/Operations/Replace Empty Operation.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// Replace Empty Operation.swift -// NetworkKit -// -// Created by Raghav Ahuja on 26/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -final class ReplaceEmptyOperation: Operation { - - typealias Output = Upstream.Output - - private let upstream: Upstream - - private let output: Output - - private let result: NKResult - - init(upstream: Upstream, output: Output, result: NKResult) { - self.upstream = upstream - self.output = output - self.result = result - } - - override func main() { - let upstreamResult = upstream.result.result - - switch upstreamResult { - case .success(let output): - result.result = .success(output) - - case .failure, .none: - result.result = .success(output) - } - } - - override func cancel() { - result.result = .success(output) - super.cancel() - } -} diff --git a/Sources/NetworkKit/Operations/Replace Error Operation.swift b/Sources/NetworkKit/Operations/Replace Error Operation.swift deleted file mode 100644 index c0f7a75..0000000 --- a/Sources/NetworkKit/Operations/Replace Error Operation.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Replace Error Operation.swift -// NetworkKit -// -// Created by Raghav Ahuja on 25/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -final class ReplaceErrorOperation: Operation { - - typealias Output = Upstream.Output - - private let upstream: Upstream - - private let output: Output - - private let result: NKResult - - init(upstream: Upstream, output: Output, result: NKResult) { - self.upstream = upstream - self.output = output - self.result = result - } - - override func main() { - let upstreamResult = upstream.result.result - - switch upstreamResult { - case .success(let output): - result.result = .success(output) - - case .failure: - result.result = .success(output) - - case .none: - result.result = .none - } - } - - override func cancel() { - result.result = .success(output) - super.cancel() - } -} diff --git a/Sources/NetworkKit/Operations/TryCatch Operation.swift b/Sources/NetworkKit/Operations/TryCatch Operation.swift deleted file mode 100644 index 40fc45c..0000000 --- a/Sources/NetworkKit/Operations/TryCatch Operation.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// TryCatch Operation.swift -// NetworkKit -// -// Created by Raghav Ahuja on 21/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -final class TryCatchOperation: AsynchronousOperation where Upstream.Output == NewPublisher.Output { - - typealias Output = Upstream.Output - - typealias Failure = NewPublisher.Failure - - /// The publisher that this publisher receives elements from. - private let upstream: Upstream - - private var result: NKResult - - private var newOperation: Operation? - - /// A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. - private let handler: (Upstream.Failure) throws -> NewPublisher - - init(upstream: Upstream, handler: @escaping (Upstream.Failure) throws -> NewPublisher, result: NKResult) { - self.upstream = upstream - self.handler = handler - self.result = result - } - - override func main() { - guard !isCancelled else { - return - } - - switch upstream.result.result { - case .success(let output): - result.result = .success(output) - finish() - - case .failure(let error): - do { - let newPublisher = try handler(error) - - guard let newOperation = newPublisher.result.operation else { - let failError = NSError.unkown() - result.result = .failure(failError as! Failure) - return - } - - self.newOperation = newOperation - - newOperation.completionBlock = { [weak self] in - guard !(self?.isCancelled ?? true) else { - return - } - - switch newPublisher.result.result! { - case .success(let output): - self?.result.result = .success(output) - - case .failure(let error): - self?.result.result = .failure(error) - } - - newPublisher.queue.cancelAllOperations() - self?.finish() - } - - newOperation.main() - - } catch { - let error = error as NSError - result.result = .failure(error as! Failure) - finish() - } - - case .none: - result.result = .none - } - } - - override func cancel() { - newOperation?.cancel() - let error = NSError.cancelled(for: upstream.queue.request?.url) - result.result = .failure(error as! Failure) - super.cancel() - } -} diff --git a/Sources/NetworkKit/Protocols/NKImageSessionDelegate.swift b/Sources/NetworkKit/Protocols/ImageSessionDelegate.swift similarity index 91% rename from Sources/NetworkKit/Protocols/NKImageSessionDelegate.swift rename to Sources/NetworkKit/Protocols/ImageSessionDelegate.swift index 684ef86..639df1e 100644 --- a/Sources/NetworkKit/Protocols/NKImageSessionDelegate.swift +++ b/Sources/NetworkKit/Protocols/ImageSessionDelegate.swift @@ -63,15 +63,7 @@ private extension NKImageSessionDelegate { guard let urlStringValue = urlString, let url = URL(string: urlStringValue) else { #if DEBUG - print(""" - - --------------------------------------------- - Cannot Fetch Image From: - URL: \(String(describing: urlString)) - Error: \(URLError(.badURL).localizedDescription) - --------------------------------------------- - - """) + NKImageSession.shared.logger.log(error: NSError.badURL(for: urlString)) #endif if flag { diff --git a/Sources/NetworkKit/Protocols/NKCancellable/NKCancellable.swift b/Sources/NetworkKit/Protocols/NKCancellable/NKCancellable.swift deleted file mode 100644 index f7eb82f..0000000 --- a/Sources/NetworkKit/Protocols/NKCancellable/NKCancellable.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// NKCancellable.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public protocol NKCancellable { - - /// Cancel the activity. - func cancel() -} diff --git a/Sources/NetworkKit/Protocols/Network Decoder.swift b/Sources/NetworkKit/Protocols/Network Decoder.swift deleted file mode 100644 index 36d4301..0000000 --- a/Sources/NetworkKit/Protocols/Network Decoder.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Network Decoder.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public protocol NetworkDecoder { - - associatedtype Input - - func decode(_ type: T.Type, from: Self.Input) throws -> T -} diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift deleted file mode 100644 index 0d11bd0..0000000 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Methods.swift +++ /dev/null @@ -1,217 +0,0 @@ -// -// NKPublisher+Methods.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public extension NKPublisher { - - /// Decodes the output from upstream using a specified `NetworkDecoder`. - /// For example, use `JSONDecoder`. - /// - Parameter type: Type to decode into. - /// - Parameter decoder: `NetworkDecoder` for decoding output. - func decode(type: Item.Type, decoder: Decoder) -> NKPublishers.Decode { - NKPublishers.Decode(upstream: self, decoder: decoder) - } - - /// Decodes the output from upstream using a specified `JSONDecoder`. - /// - Parameter type: Type to decode into. - /// - Parameter jsonKeyDecodingStrategy: JSON Key Decoding Strategy. Default value is `.useDefaultKeys`. - func decode(type: Item.Type, jsonKeyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> NKPublishers.Decode { - NKPublishers.Decode(upstream: self, jsonKeyDecodingStrategy: jsonKeyDecodingStrategy) - } - - /// Transforms all elements from the upstream publisher with a provided closure. - /// - /// - Parameter transform: A closure that takes one element as its parameter and returns a new element. - /// - Returns: A publisher that uses the provided closure to map elements from the upstream publisher to new elements that it then publishes. - func map(_ transform: @escaping (Output) -> T) -> NKPublishers.Map { - NKPublishers.Map(upstream: self, transform: transform) - } - - /// Transforms all elements from the upstream publisher with a provided error-throwing closure. - /// - /// If the `transform` closure throws an error, the publisher fails with the thrown error. - /// - Parameter transform: A closure that takes one element as its parameter and returns a new element. - /// - Returns: A publisher that uses the provided closure to map elements from the upstream publisher to new elements that it then publishes. - func tryMap(_ transform: @escaping (Output) throws -> T) -> NKPublishers.TryMap { - NKPublishers.TryMap(upstream: self, transform: transform) - } - - /// Replaces any errors in the stream with the provided element. - /// - /// If the upstream publisher fails with an error, this publisher emits the provided element, then finishes normally. - /// - Parameter output: An element to emit when the upstream publisher fails. - /// - Returns: A publisher that replaces an error from the upstream publisher with the provided output element. - func replaceError(with output: Output) -> NKPublishers.ReplaceError { - NKPublishers.ReplaceError(upstream: self, output: output) - } - - /// Publishes elements only after a specified time interval elapses between events. - /// - /// Use this operator when you want to wait for a pause in the delivery of events from the upstream publisher. For example, call `debounce` on the publisher from a text field to only receive elements when the user pauses or stops typing. When they start typing again, the `debounce` holds event delivery until the next pause. - /// - Parameters: - /// - dueTime: The time the publisher should wait before publishing an element. - /// - Returns: A publisher that publishes events only after a specified time elapses. - func debounce(_ dueTime: DispatchTimeInterval) -> NKPublishers.Debounce { - NKPublishers.Debounce(upstream: self, dueTime: .now() + dueTime) - } - - /// Publishes elements only after a specified time interval elapses between events. - /// - /// Use this operator when you want to wait for a pause in the delivery of events from the upstream publisher. For example, call `debounce` on the publisher from a text field to only receive elements when the user pauses or stops typing. When they start typing again, the `debounce` holds event delivery until the next pause. - /// - Parameters: - /// - dueTime: The time the publisher should wait before publishing an element. - func debounce(_ dueTime: DispatchTime) -> NKPublishers.Debounce { - NKPublishers.Debounce(upstream: self, dueTime: dueTime) - } - - /// Returns a publisher that publishes the value of a key path. - /// - /// - Parameter keyPath: The key path of a property on `Output` - /// - Returns: A publisher that publishes the value of the key path. - func map(_ keyPath: KeyPath) -> NKPublishers.MapKeyPath { - NKPublishers.MapKeyPath(upstream: self, keyPath: keyPath) - } - - /// Returns a publisher that publishes the values of two key paths as a tuple. - /// - /// - Parameters: - /// - keyPath0: The key path of a property on `Output` - /// - keyPath1: The key path of another property on `Output` - /// - Returns: A publisher that publishes the values of two key paths as a tuple. - func map(_ keyPath0: KeyPath, _ keyPath1: KeyPath) -> NKPublishers.MapKeyPath2 { - NKPublishers.MapKeyPath2(upstream: self, keyPath0: keyPath0, keyPath1: keyPath1) - } - - /// Handles errors from an upstream publisher by replacing it with another publisher. - /// - /// The following example replaces any error from the upstream publisher and replaces the upstream with a `Just` publisher. This continues the stream by publishing a single value and completing normally. - /// ``` - /// enum SimpleError: Error { case error } - /// let errorPublisher = (0..<10).publisher.tryMap { v -> Int in - /// if v < 5 { - /// return v - /// } else { - /// throw SimpleError.error - /// } - /// } - /// - /// let noErrorPublisher = errorPublisher.catch { _ in - /// return Just(100) - /// } - /// ``` - /// Backpressure note: This publisher passes through `request` and `cancel` to the upstream. After receiving an error, the publisher sends sends any unfulfilled demand to the new `Publisher`. - /// - Parameter handler: A closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher. - /// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. - func `catch`(_ handler: @escaping (Self.Failure) -> P) -> NKPublishers.Catch where Self.Output == P.Output { - NKPublishers.Catch(upstream: self, handler: handler) - } - - /// Handles errors from an upstream publisher by either replacing it with another publisher or `throw`ing a new error. - /// - /// - Parameter handler: A `throw`ing closure that accepts the upstream failure as input and returns a publisher to replace the upstream publisher or if an error is thrown will send the error downstream. - /// - Returns: A publisher that handles errors from an upstream publisher by replacing the failed publisher with another publisher. - func tryCatch(_ handler: @escaping (Self.Failure) throws -> P) -> NKPublishers.TryCatch where Self.Output == P.Output { - NKPublishers.TryCatch(upstream: self, handler: handler) - } - - /// Converts any failure from the upstream publisher into a new error. - /// - /// Until the upstream publisher finishes normally or fails with an error, the returned publisher republishes all the elements it receives. - /// - /// - Parameter transform: A closure that takes the upstream failure as a parameter and returns a new error for the publisher to terminate with. - /// - Returns: A publisher that replaces any upstream failure with a new error produced by the `transform` closure. - func mapError(_ transform: @escaping (Self.Failure) -> E) -> NKPublishers.MapError { - NKPublishers.MapError(upstream: self, transform: transform) - } - - /// Calls a closure with each received element and publishes any returned optional that has a value. - /// - /// - Parameter transform: A closure that receives a value and returns an optional value. - /// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure. - func compactMap(_ transform: @escaping (Output) -> T?) -> NKPublishers.CompactMap { - NKPublishers.CompactMap(upstream: self, transform: transform) - } - - /// Calls an error-throwing closure with each received element and publishes any returned optional that has a value. - /// - /// If the closure throws an error, the publisher cancels the upstream and sends the thrown error to the downstream receiver as a `Failure`. - /// - Parameter transform: an error-throwing closure that receives a value and returns an optional value. - /// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure. - func tryCompactMap(_ transform: @escaping (Self.Output) throws -> T?) -> NKPublishers.TryCompactMap { - NKPublishers.TryCompactMap(upstream: self, transform: transform) - } - - /// Replaces nil elements in the stream with the proviced element. - /// - /// - Parameter output: The element to use when replacing `nil`. - /// - Returns: A publisher that replaces `nil` elements from the upstream publisher with the provided element. - func replaceNil(with output: T) -> NKPublishers.Map where Output == T? { - NKPublishers.Map(upstream: self) { _ in output } - } - - /// Replaces an empty stream with the provided element. - /// - /// If the upstream publisher finishes without producing any elements, this publisher emits the provided element, then finishes normally. - /// - Parameter output: An element to emit when the upstream publisher finishes without emitting any elements. - /// - Returns: A publisher that replaces an empty stream with the provided output element. - func replaceEmpty(with output: Output) -> NKPublishers.ReplaceEmpty { - NKPublishers.ReplaceEmpty(upstream: self, output: output) - } -} - -public extension NKPublisher where Self.Output == NetworkTask.Output, Self.Failure == NetworkTask.Failure { - - /// Validates Network call response with provided acceptable status codes. - /// - Parameter acceptableStatusCodes: Acceptable HTTP Status codes for the network call. Default value is `Array(200 ..< 300)`. - /// - Parameter checkForErrorModel: If publisher should check for custom error model to decode. - /// - Returns: A publisher that validates response from an upstream publisher. - func validate(acceptableStatusCodes: [Int] = NKSession.shared.acceptableStatusCodes, checkForErrorModel: Bool = true) -> NKPublishers.Validate { - NKPublishers.Validate(upstream: self, shouldCheckForErrorModel: checkForErrorModel, acceptableStatusCodes: acceptableStatusCodes) - } -} - -public extension NKPublisher { - - /// Attaches a subscriber with closure-based behavior. - /// - /// This method creates the subscriber and immediately requests an unlimited number of values, prior to returning the subscriber. - /// - parameter block: The closure to execute on completion. - /// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. - func completion(_ block: @escaping (Result) -> Void) -> NKAnyCancellable { - queue.addOperation { - guard let output = self.result.result else { - return - } - - block(output) - } - - return NKAnyCancellable { - self.queue.cancelAllOperations() - } - } -} - -public extension NKPublisher where Self.Failure == Never { - - /// Assigns each element from a Publisher to a property on an object. - /// - /// - Parameters: - /// - keyPath: The key path of the property to assign. - /// - object: The object on which to assign the value. - /// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. - func assign(to keyPath: ReferenceWritableKeyPath, on object: Root) -> NKAnyCancellable { - let operation = AssignOperation(upstrem: self, keypath: keyPath, object: object) - queue.addOperation(operation) - - return NKAnyCancellable { - self.queue.cancelAllOperations() - } - } -} diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift deleted file mode 100644 index f8ea9f1..0000000 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher+Queue.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// NKPublisher+Queue.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -extension NKPublisher { - - func addToQueue(isSuspended: Bool = false, _ block: @escaping () -> Void) { - let op = BaseBlockOperation(request: queue.request, result: result, block: block) - result.operation = op - queue.addOperation(op) - queue(isSuspended: isSuspended) - } - - func addToQueue(isSuspended: Bool = false, _ op: Operation) { - result.operation = op - queue.addOperation(op) - queue(isSuspended: isSuspended) - } - - func queue(isSuspended: Bool) { - if queue.isSuspended != isSuspended { - queue.isSuspended = isSuspended - } - } -} diff --git a/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift b/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift deleted file mode 100644 index 4c25486..0000000 --- a/Sources/NetworkKit/Protocols/Publisher/NKPublisher.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// NKPublisher.swift -// NetworkKit -// -// Created by Raghav Ahuja on 18/11/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public protocol NKPublisher { - - /// The kind of values published by this publisher. - associatedtype Output - - /// The kind of errors this publisher might publish. - /// - /// Use `Never` if this `Publisher` does not publish errors. - associatedtype Failure: Error - - var result: NKResult { get } - - var queue: NKQueue { get } -} diff --git a/Sources/NetworkKit/Protocols/Server/Environment.swift b/Sources/NetworkKit/Protocols/Server/Environment.swift index 016dcb5..4bd4f0c 100644 --- a/Sources/NetworkKit/Protocols/Server/Environment.swift +++ b/Sources/NetworkKit/Protocols/Server/Environment.swift @@ -18,7 +18,7 @@ import Foundation It has a `current` property for maintaining the server environment. - To update the `current` environment, use `NetworkConfiguration.updateEnvironment(:_)`. + To update the `current` environment, use `NKConfiguration.updateEnvironment(:_)`. In `DEBUG` mode, it persists the `current` value in `UserDefaults`. */ diff --git a/Sources/NetworkKit/Request/Network Request.swift b/Sources/NetworkKit/Request/Network Request.swift deleted file mode 100644 index 50a76fe..0000000 --- a/Sources/NetworkKit/Request/Network Request.swift +++ /dev/null @@ -1,146 +0,0 @@ -// -// NetworkRequest.swift -// NetworkKit -// -// Created by Raghav Ahuja on 15/10/19. -// Copyright © 2019 Raghav Ahuja. All rights reserved. -// - -import Foundation - -public final class NetworkRequest { - - public private(set) var request: URLRequest? - - public let apiName: String - - public init(to endPoint: ConnectionRepresentable) { - request = CreateRequest(with: endPoint, query: nil)?.request - apiName = endPoint.name ?? "nil" - } - - public func urlQuery(_ query: URLQuery) -> Self { - guard let url = request?.url?.absoluteURL, - var components = URLComponents(string: url.absoluteString) else { - return self - } - - var items = components.queryItems ?? [] - items.addURLQuery(query: query) - components.queryItems = items - - self.request?.url = components.url - - #if DEBUG - DebugPrint.print( - """ - Request Dynamic URLQuery: - \(items.toDictionary.prettyPrint) - - --------------------------------------------- - """ - ) - #endif - return self - } - - public func requestBody(_ body: T, strategy: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys) -> Self { - guard request != nil else { - return self - } - - var data: Data? = nil - - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = strategy - encoder.outputFormatting = .prettyPrinted - - do { - let bodyData = try encoder.encode(body) - data = bodyData - - #if DEBUG - DebugPrint.print( - """ - Request Body: - \(String(data: bodyData, encoding: .utf8) ?? "nil") - - --------------------------------------------- - """ - ) - #endif - } catch { - #if DEBUG - DebugPrint.print( - """ - Request Body: nil - Error Encoding: - - \(error) - - --------------------------------------------- - """ - ) - #endif - - data = nil - } - - request?.httpBody = data - - var headers = request?.allHTTPHeaderFields ?? [:] - headers = headers.merging(HTTPBodyEncodingType.json.headers) { (_, new) in new } - request?.allHTTPHeaderFields = headers - - return self - } - - public func requestBody(_ body: [String: Any], encoding: HTTPBodyEncodingType) -> Self { - guard request != nil else { - return self - } - - #if DEBUG - DebugPrint.print(""" - Request Body: - \(body.prettyPrint) - - --------------------------------------------- - """) - #endif - - request?.httpBody = encoding.encode(body: body) - - var headers = request?.allHTTPHeaderFields ?? [:] - headers = headers.merging(encoding.headers) { (_, new) in new } - request?.allHTTPHeaderFields = headers - - return self - } - - public func requestBody(_ body: Data?, httpHeaders: HTTPHeaderParameters) -> Self { - guard request != nil else { - return self - } - - request?.httpBody = body - - var headers = request?.allHTTPHeaderFields ?? [:] - headers = headers.merging(httpHeaders) { (_, new) in new } - request?.allHTTPHeaderFields = headers - - return self - } - - public func httpHeaders(_ httpHeaders: HTTPHeaderParameters) -> Self { - guard request != nil else { - return self - } - - var headers = request?.allHTTPHeaderFields ?? [:] - headers = headers.merging(httpHeaders) { (_, new) in new } - request?.allHTTPHeaderFields = headers - - return self - } -} - diff --git a/Sources/NetworkKit/Request/Request.swift b/Sources/NetworkKit/Request/Request.swift new file mode 100644 index 0000000..b22d261 --- /dev/null +++ b/Sources/NetworkKit/Request/Request.swift @@ -0,0 +1,82 @@ +// +// NetworkRequest.swift +// NetworkKit +// +// Created by Raghav Ahuja on 15/10/19. +// Copyright © 2019 Raghav Ahuja. All rights reserved. +// + +import Foundation + +public typealias NetworkRequest = NKRequest + +public class NKRequest { + + var bodyData: Data? = nil + var headers: HTTPHeaderParameters = [:] + + var queryItems = Set() + + private var _request: URLRequest? = nil + + public var request: URLRequest? { + _request + } + + public let apiName: String + + public let connection: ConnectionRepresentable + + public init(to endPoint: ConnectionRepresentable) { + connection = endPoint + apiName = endPoint.name ?? "Nil" + } + + public func create() { + let creator = CreateRequest(with: connection, query: queryItems, body: bodyData, headers: headers) + _request = creator?.request + } + + public func urlQuery(_ query: URLQuery) -> Self { + queryItems.addURLQuery(query: query) + return self + } + + public func requestBody(_ body: T, strategy: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys) -> Self { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = strategy + encoder.outputFormatting = .prettyPrinted + + do { + let bodyData = try encoder.encode(body) + self.bodyData = bodyData + } catch { + bodyData = nil + } + + headers = headers.merging(HTTPBodyEncodingType.json.headers) { (_, new) in new } + + return self + } + + public func requestBody(_ body: [String: Any], encoding: HTTPBodyEncodingType) -> Self { + bodyData = encoding.encode(body: body) + + headers = headers.merging(encoding.headers) { (_, new) in new } + + return self + } + + public func requestBody(_ body: Data?, httpHeaders: HTTPHeaderParameters) -> Self { + bodyData = body + headers = headers.merging(httpHeaders) { (_, new) in new } + + return self + } + + public func httpHeaders(_ httpHeaders: HTTPHeaderParameters) -> Self { + headers = headers.merging(httpHeaders) { (_, new) in new } + + return self + } +} diff --git a/Tests/NetworkKitTests/NetworkKitTests.swift b/Tests/NetworkKitTests/NetworkKitTests.swift index b9f5e2f..2e6af01 100644 --- a/Tests/NetworkKitTests/NetworkKitTests.swift +++ b/Tests/NetworkKitTests/NetworkKitTests.swift @@ -129,7 +129,7 @@ final class NetworkKitTests: XCTestCase { let expecatation = XCTestExpectation() func testExample() { - cancellable = NetworkTask(session: NKSession.shared) { + cancellable = NKSession.shared.dataTask { NetworkRequest(to: MockPoint.allUsers) } .map(\.data)