diff --git a/Package.swift b/Package.swift index abc9bfd..12d0956 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/README.md b/README.md index a51c166..3b3f360 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ NetworkRequester is an HTTP Combine-only networking library. - [Requirements](#requirements) - [Installation](#installation) - [Usage](#usage) -- [Conclusion](#conclusion) ## Requirements @@ -21,7 +20,10 @@ The [Swift Package Manager](https://swift.org/package-manager/) is a tool for ma NetworkRequester supports only SPM and adding it as a dependency is done just by including the URL into the `dependencies` value of your `Package.swift`. ```swift dependencies: [ - .package(url: "https://github.com/igashev/NetworkRequester.git", .upToNextMajor(from: "1.2.0")) + .package( + url: "https://github.com/igashev/NetworkRequester.git", + .upToNextMajor(from: "1.2.0") + ) ] ``` Or by using the integrated tool of Xcode. @@ -37,17 +39,13 @@ let requestBuilder = URLRequestBuilder( environment: Environment.production, endpoint: UsersEndpoint.users, httpMethod: .get, - httpHeaders: [.json, .authorization(bearerToken: "secretBearerToken")], + httpHeaders: [ + .json, + .authorization(bearerToken: "secretBearerToken") + ], httpBody: nil, queryParameters: nil ) - -// Building a URLRequest -do { - let urlRequest = try requestBuilder.build() -} catch { - // Possible errors that could be thrown here are NetworkingError.buildingURL and NetworkingError.encoding(error:) -} ``` ### Calling requests @@ -56,29 +54,52 @@ There are two options with which to make the actual network request. The first option is using plain `URLRequest`. ```swift -let url = URL(string: "https://example.get.request.com")! +struct User: Decodable { + let name: String +} + +struct BackendError: DecodableError { + let errorCode: Int + let localizedError: String +} + +let url = URL(string: "https://amazingapi.com/v1/users")! let urlRequest = URLRequest(url: url) -let caller = URLRequestCaller(decoder: JSONDecoder()) -let examplePublisher: AnyPublisher = caller.call(using: urlRequest) // Expects no response data as Void is specified as Output +let caller = AsyncCaller(decoder: JSONDecoder()) +let user: User = try await caller.call( + using: urlRequest, + errorType: BackendError.self +) ``` The second option is using `URLRequestBuilder`. ```swift +struct User: Decodable { + let name: String +} + +struct BackendError: DecodableError { + let errorCode: Int + let localizedError: String +} + let requestBuilder = URLRequestBuilder( - environment: Environment.production, - endpoint: UsersEndpoint.users, + environment: "https://amazingapi.com", + endpoint: "v1/users", httpMethod: .get, - httpHeaders: [.json, .authorization(bearerToken: "secretBearerToken")], + httpHeaders: [ + .json, + .authorization(bearerToken: "secretBearerToken") + ], httpBody: nil, queryParameters: nil ) -let caller = URLRequestCaller(decoder: JSONDecoder()) -let examplePublisher: AnyPublisher = caller.call(using: requestBuilder) // Expects response data as User is specified as Output +let caller = AsyncCaller(decoder: JSONDecoder()) +let user: User = try await caller.call( + using: requestBuilder, + errorType: BackendError.self +) ``` Take into account that when a response data is expected, a type that conforms to `Encodable` should be specified as `Output`. Otherwise `Void`. - -## Conclusion - -NetworkRequester is still very young. Improvements and new functionalities will be coming. Pull requests and suggestions are very welcomed. \ No newline at end of file diff --git a/Sources/NetworkRequester/Callers/AsyncCaller.swift b/Sources/NetworkRequester/Callers/AsyncCaller.swift index 6f9fd53..5ed77c7 100644 --- a/Sources/NetworkRequester/Callers/AsyncCaller.swift +++ b/Sources/NetworkRequester/Callers/AsyncCaller.swift @@ -1,10 +1,46 @@ import Foundation /// Use this object to make network calls and receive decoded values using the new structured concurrency (async/await). +/// +/// ``` +/// struct User: Decodable { +/// let name: String +/// } +/// +/// struct BackendError: DecodableError { +/// let errorCode: Int +/// let localizedError: String +/// } +/// +/// let requestBuilder = URLRequestBuilder( +/// environment: "https://amazingapi.com", +/// endpoint: "v1/users", +/// httpMethod: .get, +/// httpHeaders: [ +/// .json, +/// .authorization(bearerToken: "secretBearerToken") +/// ], +/// httpBody: nil, +/// queryParameters: nil +/// ) +/// +/// let caller = AsyncCaller(decoder: JSONDecoder()) +/// let user: User = try await caller.call( +/// using: requestBuilder, +/// errorType: BackendError.self +/// ) +/// ``` public struct AsyncCaller { - private let middleware: [Middleware] - private let urlSession: URLSession + public let urlSession: URLSession + public let middlewares: [Middleware] + private let utility: CallerUtility + private var middlewaresOnRequestAsyncStream: AsyncThrowingStream { + .init { continuation in + middlewares.forEach { continuation.yield($0) } + continuation.finish() + } + } /// Initialises an object which can make network calls. /// - Parameters: @@ -13,11 +49,11 @@ public struct AsyncCaller { /// - middleware: Middleware that is injected in the networking events. public init( urlSession: URLSession = .shared, - decoder: JSONDecoder, - middleware: [Middleware] = [] + middleware: [Middleware] = [], + decoder: JSONDecoder ) { self.urlSession = urlSession - self.middleware = middleware + self.middlewares = middleware self.utility = .init(decoder: decoder) } @@ -25,38 +61,70 @@ public struct AsyncCaller { using builder: URLRequestBuilder, errorType: DE.Type ) async throws -> D { - var request = try builder.build() - middleware.forEach { $0.onRequest(&request) } + try await call(using: builder.build(), errorType: errorType) + } + + public func call( + using builder: URLRequestBuilder, + errorType: DE.Type + ) async throws { + try await call(using: builder.build(), errorType: errorType) + } + + public func call( + using request: URLRequest, + errorType: DE.Type + ) async throws -> D { + var mutableRequest = request + try await runMiddlewaresOnRequest(request: &mutableRequest) do { - let (data, response) = try await urlSession.data(for: request) - let tryMap = try utility.checkResponseForErrors(data: data, urlResponse: response, errorType: errorType) + let (data, response) = try await urlSession.data(for: mutableRequest) + let tryMap = try utility.checkResponseForErrors( + data: data, + urlResponse: response, + errorType: errorType + ) let tryMap2: D = try utility.decodeIfNecessary(tryMap) - middleware.forEach { $0.onResponse(data: data, response: response) } + middlewares.forEach { $0.onResponse(data: data, response: response) } return tryMap2 } catch { let mappedError = utility.mapError(error) - middleware.forEach { $0.onError(mappedError, request: request) } + middlewares.forEach { $0.onError(mappedError, request: mutableRequest) } throw mappedError } } - public func call( - using builder: URLRequestBuilder, - errorType: E.Type + public func call( + using request: URLRequest, + errorType: DE.Type ) async throws { - var request = try builder.build() - middleware.forEach { $0.onRequest(&request) } + var mutableRequest = request + try await runMiddlewaresOnRequest(request: &mutableRequest) do { - let (data, response) = try await urlSession.data(for: request) - let tryMap = try utility.checkResponseForErrors(data: data, urlResponse: response, errorType: errorType) + let (data, response) = try await urlSession.data(for: mutableRequest) + let tryMap = try utility.checkResponseForErrors( + data: data, + urlResponse: response, + errorType: errorType + ) try utility.tryMapEmptyResponseBody(data: tryMap) - middleware.forEach { $0.onResponse(data: data, response: response) } + middlewares.forEach { $0.onResponse(data: data, response: response) } } catch { let mappedError = utility.mapError(error) - middleware.forEach { $0.onError(mappedError, request: request) } + middlewares.forEach { $0.onError(mappedError, request: mutableRequest) } throw mappedError } } + + private func runMiddlewaresOnRequest(request: inout URLRequest) async throws { + guard !middlewares.isEmpty else { + return + } + + for try await middleware in middlewaresOnRequestAsyncStream { + try await middleware.onRequest(&request) + } + } } diff --git a/Sources/NetworkRequester/Callers/CallerUtility.swift b/Sources/NetworkRequester/Callers/CallerUtility.swift index 364d667..06b756f 100644 --- a/Sources/NetworkRequester/Callers/CallerUtility.swift +++ b/Sources/NetworkRequester/Callers/CallerUtility.swift @@ -21,30 +21,37 @@ struct CallerUtility { let httpResponse = urlResponse as? HTTPURLResponse, let status = HTTPStatus(rawValue: httpResponse.statusCode) else { - throw NetworkingError.networking( - status: .internalServerError, - error: try? decoder.decode(DE.self, from: data) - ) + throw NetworkingError.unknown(underlyingError: try? decoder.decode(DE.self, from: data)) } guard status.isSuccess else { - throw NetworkingError.networking(status: status, error: try? decoder.decode(DE.self, from: data)) + throw NetworkingError.networking( + status: status, + underlyingError: try? decoder.decode(DE.self, from: data) + ) } return data } + /// Maps the response's error to the more contextual `NetworkingError` providing the underlying error as well + /// for further clarification. + /// - Parameter error: The thrown error. + /// - Returns: The more contextual `NetworkingError`. func mapError(_ error: Error) -> NetworkingError { switch error { case let decodingError as DecodingError: - return NetworkingError.decoding(error: decodingError) + return NetworkingError.decoding(underlyingError: decodingError) case let networkingError as NetworkingError: return networkingError default: - return .unknown(error) + return .unknown(underlyingError: error) } } + /// Makes sure that the response's data is empty. This is useful when empty response data is expected. + /// Throws `DecodingError.dataCorrupted`when data is not empty. + /// - Parameter data: The data to be checked for emptiness. func tryMapEmptyResponseBody(data: Data) throws { guard !data.isEmpty else { return @@ -54,13 +61,14 @@ struct CallerUtility { throw DecodingError.dataCorrupted(context) } + /// Decodes data to the provided generic parameter using a `JSONDecoder`. + /// In cases where `D` is `Data` return the raw data, instead of attempting to decode it. Otherwise - run through the decoder. + /// - Parameter data: Data to decode. + /// - Returns: Returns `Data` when the the generic is `Data` or any other `Decodable` that is run through the decoder. func decodeIfNecessary(_ data: Data) throws -> D { - // In cases where D is Data, we return the raw data instead of attempting to decode it if let data = data as? D { return data - } - // Otherwise - run through the decoder - else { + } else { return try decoder.decode(D.self, from: data) } } diff --git a/Sources/NetworkRequester/Callers/CombineCaller.swift b/Sources/NetworkRequester/Callers/CombineCaller.swift deleted file mode 100644 index d503431..0000000 --- a/Sources/NetworkRequester/Callers/CombineCaller.swift +++ /dev/null @@ -1,155 +0,0 @@ -import Combine -import Foundation - -/// Use this object to make network calls and receive decoded values wrapped into Combine's `AnyPublisher`. -public struct CombineCaller { - public typealias AnyURLSessionDataPublisher = AnyPublisher - - private let middleware: [Middleware] - private let utility: CallerUtility - - /// Gets the data task publisher. - private let getDataDataTaskPublisher: (URLRequest) -> AnyURLSessionDataPublisher - - /// Initialises an object which can make network calls. - /// - Parameters: - /// - urlSession: Session that would make the actual network call. - /// - decoder: Decoder that would decode the received data from the network call. - /// - middleware: Middleware that is injected in the networking events. - public init( - urlSession: URLSession = .shared, - decoder: JSONDecoder, - middleware: [Middleware] = [] - ) { - self.init( - decoder: decoder, - getDataPublisher: { urlSession.dataTaskPublisher(for: $0).eraseToAnyPublisher() }, - middleware: middleware - ) - } - - /// Initialises an object which can make network calls. - /// - Parameters: - /// - decoder: Decoder that would decode the received data from the network call. - /// - getDataPublisher: A closure that returns a data task publisher. - init( - decoder: JSONDecoder, - getDataPublisher: @escaping (URLRequest) -> AnyURLSessionDataPublisher, - middleware: [Middleware] = [] - ) { - self.middleware = middleware - self.getDataDataTaskPublisher = getDataPublisher - self.utility = .init(decoder: decoder) - } - - /// Method which calls the network request. - /// - Parameters: - /// - request: The `URLRequest` that should be called. - /// - errorType: The error type to be decoded from the body in non-success cases. - /// - Returns: The result from the network call wrapped into `AnyPublisher`. - public func call( - using request: URLRequest, - errorType: DE.Type - ) -> AnyPublisher { - getDataDataTaskPublisher(request) - .attachMiddleware(middleware, for: request) - .tryMap { try utility.checkResponseForErrors(data: $0.data, urlResponse: $0.response, errorType: errorType) } - .tryMap(utility.decodeIfNecessary) - .mapError(utility.mapError) - .attachCompletionMiddleware(middleware, request: request) - .eraseToAnyPublisher() - } - - /// Method which calls the network request without expecting a response body. - /// - Parameters: - /// - request: The `URLRequest` that should be called. - /// - errorType: The error type to be decoded from the body in non-success cases. - /// - Returns: The result from the network call wrapped into `AnyPublisher`. - public func call( - using request: URLRequest, - errorType: DE.Type - ) -> AnyPublisher { - getDataDataTaskPublisher(request) - .attachMiddleware(middleware, for: request) - .tryMap { try utility.checkResponseForErrors(data: $0.data, urlResponse: $0.response, errorType: errorType) } - .tryMap(utility.tryMapEmptyResponseBody(data:)) - .mapError(utility.mapError) - .attachCompletionMiddleware(middleware, request: request) - .eraseToAnyPublisher() - } -} - -public extension CombineCaller { - /// Convenient method which calls the builded network request using the `URLRequestBuilder` object. - /// The building and the error handling of the `URLRequest` are handled here. - /// - Parameters: - /// - builder: The builder from which the `URLRequest` will be constructed and called. - /// - errorType: The error type to be decoded from the body in non-success cases. - /// - Returns: The result from the network call wrapped into `AnyPublisher`. - func call( - using builder: URLRequestBuilder, - errorType: E.Type - ) -> AnyPublisher { - do { - let urlRequest = try builder.build() - return call(using: urlRequest, errorType: errorType) - } catch { - return Fail(error: utility.mapError(error)) - .attachCompletionMiddleware(middleware, request: nil) - .eraseToAnyPublisher() - } - } - - /// Convenient method which calls the builded network request using the `URLRequestBuilder` object without expecting a response body. - /// The building and the error handling of the `URLRequest` are handled here. - /// - Parameters: - /// - builder: The builder from which the `URLRequest` will be constructed and called. - /// - errorType: The error type to be decoded from the body in non-success cases. - /// - Returns: The result from the network call wrapped into `AnyPublisher`. - func call( - using builder: URLRequestBuilder, - errorType: E.Type - ) -> AnyPublisher { - do { - let urlRequest = try builder.build() - return call(using: urlRequest, errorType: errorType) - } catch { - return Fail(error: utility.mapError(error)) - .attachCompletionMiddleware(middleware, request: nil) - .eraseToAnyPublisher() - } - } -} - -private extension CombineCaller.AnyURLSessionDataPublisher { - func attachMiddleware( - _ middleware: [Middleware], - for request: URLRequest - ) -> Publishers.HandleEvents { - var request = request - return handleEvents( - receiveSubscription: { _ in - middleware.forEach { $0.onRequest(&request) } - }, - receiveOutput: { data, response in - middleware.forEach { $0.onResponse(data: data, response: response) } - } - ) - } -} - -private extension Publisher where Failure == NetworkingError { - func attachCompletionMiddleware( - _ middleware: [Middleware], - request: URLRequest? - ) -> Publishers.HandleEvents { - handleEvents(receiveCompletion: { completion in - switch completion { - case .failure(let error): - middleware.forEach { $0.onError(error, request: request) } - default: - return - } - }) - } -} diff --git a/Sources/NetworkRequester/HTTP/HTTPBody.swift b/Sources/NetworkRequester/HTTP/HTTPBody.swift index fdbf880..db18b0a 100644 --- a/Sources/NetworkRequester/HTTP/HTTPBody.swift +++ b/Sources/NetworkRequester/HTTP/HTTPBody.swift @@ -16,7 +16,7 @@ public struct HTTPBody { do { return try jsonEncoder.encode(encodable) } catch let error as EncodingError { - throw NetworkingError.encoding(error: error) + throw NetworkingError.encoding(underlyingError: error) } } } diff --git a/Sources/NetworkRequester/HTTP/HTTPHeader.swift b/Sources/NetworkRequester/HTTP/HTTPHeader.swift index 52f11e3..623e358 100644 --- a/Sources/NetworkRequester/HTTP/HTTPHeader.swift +++ b/Sources/NetworkRequester/HTTP/HTTPHeader.swift @@ -1,5 +1,5 @@ /// Represents an HTTP header of a request. -public struct HTTPHeader { +public struct HTTPHeader: Hashable { /// The name of the header. public let name: String @@ -16,23 +16,21 @@ public struct HTTPHeader { } } -extension HTTPHeader { +public extension HTTPHeader { /// A JSON HTTP header that has a name of `Content-Type` and a value of `application/json`. - public static let json = Self(name: "Content-Type", value: "application/json") + static let json = Self(name: "Content-Type", value: "application/json") /// An authorization HTTP header that has a name of `Authorization` and a value of `Bearer \(token)` /// - Parameter token: A bearer token to be set as a value. /// - Returns: An `HTTPHeader` instance with authorization name and a token value. - public static func authorization(bearerToken token: String) -> Self { + static func authorization(bearerToken token: String) -> Self { authorization(token: "Bearer \(token)") } /// An authorization HTTP header that has a name of `Authorization` and a value of `token`. /// - Parameter token: A token to be set as a value. /// - Returns: An `HTTPHeader` instance with authorization name and a token value. - public static func authorization(token: String) -> Self { + static func authorization(token: String) -> Self { .init(name: "Authorization", value: token) } } - -extension HTTPHeader: Hashable { } diff --git a/Sources/NetworkRequester/HTTP/HTTPMethod.swift b/Sources/NetworkRequester/HTTP/HTTPMethod.swift index 8a958c8..ba6ceca 100644 --- a/Sources/NetworkRequester/HTTP/HTTPMethod.swift +++ b/Sources/NetworkRequester/HTTP/HTTPMethod.swift @@ -1,23 +1,24 @@ -/// Represents an HTTP method of a request. -public enum HTTPMethod { - case get, post, put, delete, patch +/// Represents an HTTP method of a request. Extend when needed others that are not listed by default. +public struct HTTPMethod: RawRepresentable, Equatable { + public typealias RawValue = String + + public let rawValue: RawValue + + public init(rawValue: RawValue) { + self.rawValue = rawValue + } } // MARK: - CustomStringConvertible extension HTTPMethod: CustomStringConvertible { - public var description: String { - switch self { - case .get: - return "GET" - case .post: - return "POST" - case .put: - return "PUT" - case .delete: - return "DELETE" - case .patch: - return "PATCH" - } - } + public var description: String { rawValue } +} + +public extension HTTPMethod { + static let get = Self(rawValue: "GET") + static let post = Self(rawValue: "POST") + static let put = Self(rawValue: "PUT") + static let delete = Self(rawValue: "DELETE") + static let patch = Self(rawValue: "PATCH") } diff --git a/Sources/NetworkRequester/HTTP/HTTPStatus.swift b/Sources/NetworkRequester/HTTP/HTTPStatus.swift index 5102b97..b3436a7 100644 --- a/Sources/NetworkRequester/HTTP/HTTPStatus.swift +++ b/Sources/NetworkRequester/HTTP/HTTPStatus.swift @@ -80,12 +80,12 @@ public enum HTTPStatus: Int, Equatable { case networkConnectTimeoutError = 599 } -extension HTTPStatus { - public var code: Int { rawValue } +public extension HTTPStatus { + var code: Int { rawValue } - public var isInformational: Bool { 100...199 ~= rawValue } - public var isSuccess: Bool { 200...299 ~= rawValue } - public var isRedirection: Bool { 300...399 ~= rawValue } - public var isClientError: Bool { 400...499 ~= rawValue } - public var isServerError: Bool { 500...599 ~= rawValue } + var isInformational: Bool { 100...199 ~= rawValue } + var isSuccess: Bool { 200...299 ~= rawValue } + var isRedirection: Bool { 300...399 ~= rawValue } + var isClientError: Bool { 400...499 ~= rawValue } + var isServerError: Bool { 500...599 ~= rawValue } } diff --git a/Sources/NetworkRequester/Middleware.swift b/Sources/NetworkRequester/Middleware.swift index ff0a34b..8dfe58a 100644 --- a/Sources/NetworkRequester/Middleware.swift +++ b/Sources/NetworkRequester/Middleware.swift @@ -1,19 +1,26 @@ import Foundation -/// Gives the ability to plug into the `call` mechanizm of `URLRequestBuilder` to perform side effects +/// Gives the ability to plug into the `call` mechanism of `URLRequestBuilder` to perform side effects public protocol Middleware { - /// Called when a request start execution - func onRequest(_ request: inout URLRequest) + /// Called when a request starts execution. + /// - Parameter request: The request that is about to be executed. Possible to modify. + func onRequest(_ request: inout URLRequest) async throws - /// Called with the raw data & respnse of from the request + /// Called when a request succeeds.. + /// - Parameters: + /// - data: The response's raw data from the request that has succeeded. + /// - response: The actual response from the request that has succeeded. func onResponse(data: Data, response: URLResponse) - /// Called with the decoded error of the request + /// Called when a request fails. + /// - Parameters: + /// - error: The error from the request that has failed. + /// - request: The actual request that has failed. func onError(_ error: NetworkingError, request: URLRequest?) } public extension Middleware { - func onRequest(_ request: inout URLRequest) { } + func onRequest(_ request: inout URLRequest) async throws { } func onResponse(data: Data, response: URLResponse) { } func onError(_ error: NetworkingError, request: URLRequest?) { } } diff --git a/Sources/NetworkRequester/NetworkingError.swift b/Sources/NetworkRequester/NetworkingError.swift index f1a4a10..c7ac061 100644 --- a/Sources/NetworkRequester/NetworkingError.swift +++ b/Sources/NetworkRequester/NetworkingError.swift @@ -6,18 +6,18 @@ public enum NetworkingError: Error { /// Thrown when constructing URL fails. case buildingURL - /// Thrown when encoding body's data fails. - case encoding(error: EncodingError) + /// Thrown when encoding request's data fails. + case encoding(underlyingError: EncodingError) /// Thrown when decoding response's data fails. - case decoding(error: DecodingError) + case decoding(underlyingError: DecodingError) - /// Thrown when the network request fails. - case networking(status: HTTPStatus, error: Error?) + /// Thrown when the network request fails (returns anything else than 200). + case networking(status: HTTPStatus, underlyingError: Error?) - /// Thrown when an unknown error is thrown (no other error from the above has been catched), - /// optionally forwarding an underlying error if there is one. - case unknown(Error?) + /// Thrown when an unknown error is encountered (no other error from the above has been catched). + /// Optionally forwarding an underlying error if there is one. + case unknown(underlyingError: Error?) } extension NetworkingError: Equatable { diff --git a/Sources/NetworkRequester/URLRequestBuilder.swift b/Sources/NetworkRequester/URLRequestBuilder.swift index 2abee0f..720206c 100644 --- a/Sources/NetworkRequester/URLRequestBuilder.swift +++ b/Sources/NetworkRequester/URLRequestBuilder.swift @@ -62,8 +62,37 @@ public struct URLRequestBuilder { queryParameters: URLQueryParameters? = nil, timeoutInterval: TimeInterval = 30 ) { - self.environment = environment.url - self.endpoint = endpoint.url + self.init( + environment: environment.url, + endpoint: endpoint.url, + httpMethod: httpMethod, + httpHeaders: httpHeaders, + httpBody: httpBody, + queryParameters: queryParameters, + timeoutInterval: timeoutInterval + ) + } + + /// Initialising the builder with the desired request properties that can build the complete `URLRequest`. + /// - Parameters: + /// - environment: The hostname of a URL. + /// - endpoint: The path to a specific resource of a URL. + /// - httpMethod: The HTTP method of the request. + /// - httpHeaders: The HTTP headers of the request. + /// - httpBody: The HTTP body of the request. Not required. + /// - queryParameters: The URL query parameters of a URL. Not required. + /// - timeoutInterval: The timeout interval of a request. Defaults to *30*. + public init( + environment: String, + endpoint: String, + httpMethod: HTTPMethod, + httpHeaders: [HTTPHeader] = [], + httpBody: HTTPBody? = nil, + queryParameters: URLQueryParameters? = nil, + timeoutInterval: TimeInterval = 30 + ) { + self.environment = environment + self.endpoint = endpoint self.httpMethod = httpMethod self.httpBody = httpBody self.httpHeaders = Set(httpHeaders) @@ -99,7 +128,8 @@ extension URLRequestBuilder: Equatable { public static func == (lhs: URLRequestBuilder, rhs: URLRequestBuilder) -> Bool { lhs.environment == rhs.environment && lhs.endpoint == rhs.endpoint && - lhs.queryParameters.sorted(by: { $0.name < $1.name }) == rhs.queryParameters.sorted(by: { $0.name < $1.name }) && + lhs.queryParameters.sorted(by: { $0.name < $1.name }) == + rhs.queryParameters.sorted(by: { $0.name < $1.name }) && lhs.httpMethod == rhs.httpMethod && lhs.httpHeaders == rhs.httpHeaders && lhs.httpBody == rhs.httpBody && diff --git a/Tests/NetworkRequesterTests/URLRequestCallerTests.swift b/Tests/NetworkRequesterTests/URLRequestCallerTests.swift index 4a07ea1..516ff55 100644 --- a/Tests/NetworkRequesterTests/URLRequestCallerTests.swift +++ b/Tests/NetworkRequesterTests/URLRequestCallerTests.swift @@ -1,471 +1,471 @@ -import XCTest -import Combine -@testable import NetworkRequester - -final class URLRequestCallerTests: XCTestCase { - - struct TestError: Error, Codable, Equatable { - let message: String - } - - private var encodedData: Data { - let testableModel = TestEncodable(name: "first name", age: 1236) - return try! JSONEncoder().encode(testableModel) - } - - private var encodedTestErrorData: Data { - let testableError = TestError(message: "Something went wrong") - return try! JSONEncoder().encode(testableError) - } - - private let decoder = JSONDecoder() - private let request = URLRequest(url: URL(string: "https://google.com")!) - private var cancellables = Set() - - func testCallSucceeds() { - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { [encodedData] _ in - Just((encodedData, .withStatus(200))) - .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) - .eraseToAnyPublisher() - } - ) - - let testObjectResponsePublisher: AnyPublisher = caller - .call(using: request, errorType: TestError.self) - - testObjectResponsePublisher.sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - XCTFail("Expected to receive value. Got error: \(error)") - case .finished: - break - } - }, - receiveValue: { data in - XCTAssertEqual(data.name, "first name") - XCTAssertEqual(data.age, 1236) - } - ) - .store(in: &cancellables) - } - - func testCallFailsDueToURLError() { - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { _ in Fail(error: URLError(.badURL)).eraseToAnyPublisher() } - ) - - caller.call(using: URLRequest(url: URL(string: "https://google.com")!), errorType: TestError.self) - .sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - switch error { - case .unknown: - break - default: - XCTFail("A wrong error is thrown. Expected NetworkingError.unknown, got \(error).") - } - case .finished: - XCTFail("Expected NetworkingError.unknown, got .finished completion.") - } - }, - receiveValue: { data in XCTFail("Expected NetworkingError.unknown, got \(data).") } - ) - .store(in: &cancellables) - } - - func testCallFailsDueToInvalidHTTPResponse() { - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { [encodedTestErrorData] _ in - Just((encodedTestErrorData, .withStatus(1000))) - .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) - .eraseToAnyPublisher() - } - ) - - caller.call(using: URLRequest(url: URL(string: "https://google.com")!), errorType: TestError.self) - .sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - switch error { - case .networking(let status, let decodedError): - switch status { - case .internalServerError: - XCTAssertEqual(decodedError as? TestError, TestError(message: "Something went wrong")) - default: - XCTFail("A wrong error is thrown. Expected NetworkingError.networking(status: .internalServerError), got \(error).") - } - default: - XCTFail("A wrong error is thrown. Expected NetworkingError.unknown, got \(error).") - } - case .finished: - XCTFail("Expected NetworkingError.unknown, got .finished completion.") - } - }, - receiveValue: { data in XCTFail("Expected NetworkingError.unknown, got \(data).") } - ) - .store(in: &cancellables) - } - - func testCallFailsDueToBadRequest() { - let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") - let didReceiveExpectation = XCTestExpectation.calledOnce(description: "onResponse called") - let didErrorExpecatation = XCTestExpectation.calledOnce(description: "onError called") - - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { [encodedTestErrorData] _ in - Just((encodedTestErrorData, .withStatus(HTTPStatus.badRequest.code))) - .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) - .eraseToAnyPublisher() - }, - middleware: [ - TestMiddleware( - onRequestExpectation: didRequestExpectation, - onResponseExpectation: didReceiveExpectation, - onErrorExpectation: didErrorExpecatation - ) - ] - ) - - caller.call(using: URLRequest(url: URL(string: "https://google.com")!), errorType: TestError.self) - .sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - switch error { - case .networking(let status, let decodedError): - switch status { - case .badRequest: - XCTAssertEqual(decodedError as? TestError, TestError(message: "Something went wrong")) - default: - XCTFail("A wrong error is thrown. Expected NetworkingError.networking(status: .badRequest), got \(error).") - } - default: - XCTFail("A wrong error is thrown. Expected NetworkingError.unknown, got \(error).") - } - case .finished: - XCTFail("Expected NetworkingError.unknown, got .finished completion.") - } - }, - receiveValue: { data in XCTFail("Expected NetworkingError.unknown, got \(data).") } - ) - .store(in: &cancellables) - - wait(for: [didRequestExpectation, didReceiveExpectation, didErrorExpecatation], timeout: 0.5) - } - - func testResponseToBeIntButGotEmpty() { - let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") - let didReceiveExpectation = XCTestExpectation.calledOnce(description: "onResponse called") - let didErrorExpecatation = XCTestExpectation.calledOnce(description: "onError called") - - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { _ in - Just((data: .init(), response: .withStatus(200))) - .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) - .eraseToAnyPublisher() - }, - middleware: [ - TestMiddleware( - onRequestExpectation: didRequestExpectation, - onResponseExpectation: didReceiveExpectation, - onErrorExpectation: didErrorExpecatation - ) - ] - ) - - let intResponsePublisher: AnyPublisher = caller.call(using: request, errorType: TestError.self) - intResponsePublisher - .sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - switch error { - case .decoding: - break - default: - XCTFail("A wrong error is thrown. Expected NetworkingError.decoding(error: DecodingError), got \(error).") - } - case .finished: - XCTFail("Expected NetworkingError.decoding(error: DecodingError), got .finished completion.") - } - }, - receiveValue: { data in XCTFail("Expected NetworkingError.decoding(error: DecodingError), got \(data).") } - ) - .store(in: &cancellables) - - wait(for: [didRequestExpectation, didReceiveExpectation, didErrorExpecatation], timeout: 0.5) - } - - func testResponseToBeEmptyButGotData() { - let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") - let didReceiveExpectation = XCTestExpectation.calledOnce(description: "onResponse called") - let didErrorExpecatation = XCTestExpectation.calledOnce(description: "onError called") - - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { [encodedData] _ in - Just((data: encodedData, response: .withStatus(200))) - .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) - .eraseToAnyPublisher() - }, - middleware: [ - TestMiddleware( - onRequestExpectation: didRequestExpectation, - onResponseExpectation: didReceiveExpectation, - onErrorExpectation: didErrorExpecatation - ) - ] - ) - - caller.call(using: request, errorType: TestError.self) - .sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - switch error { - case .decoding: - break - default: - XCTFail("A wrong error is thrown. Expected NetworkingError.decoding(error: DecodingError), got \(error).") - } - case .finished: - XCTFail("Expected NetworkingError.decoding(error: DecodingError), got .finished completion.") - } - }, - receiveValue: { data in XCTFail("Expected NetworkingError.decoding(error: DecodingError), got \(data).") } - ) - .store(in: &cancellables) - - wait(for: [didRequestExpectation, didReceiveExpectation, didErrorExpecatation], timeout: 0.5) - } - - func testResponseToBeEmptyAndGotEmpty() { - let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") - let didReceiveExpecatation = XCTestExpectation.calledOnce(description: "onResponse called") - - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { _ in - Just((data: .init(), response: .withStatus(200))) - .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) - .eraseToAnyPublisher() - }, - middleware: [ - TestMiddleware( - onRequestExpectation: didRequestExpectation, - onResponseExpectation: didReceiveExpecatation - ) - ] - ) - - caller.call(using: request, errorType: TestError.self) - .sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - XCTFail("Expected to receive value. Got error: \(error)") - case .finished: - break - } - }, - receiveValue: { _ in } - ) - .store(in: &cancellables) - - wait(for: [didRequestExpectation, didReceiveExpecatation], timeout: 0.5) - } - - func testResponseToBeEmptyAndGotEmptyUsingURLRequestBuilder() { - let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") - let didReceiveExpecatation = XCTestExpectation.calledOnce(description: "onResponse called") - - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { _ in - Just((data: .init(), response: .withStatus(200))) - .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) - .eraseToAnyPublisher() - }, - middleware: [ - TestMiddleware( - onRequestExpectation: didRequestExpectation, - onResponseExpectation: didReceiveExpecatation - ) - ] - ) - - let builder = URLRequestBuilder(environment: Environment(), endpoint: Environment(), httpMethod: .get) - caller.call(using: builder, errorType: TestError.self) - .sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - XCTFail("Expected to receive value. Got error: \(error)") - case .finished: - break - } - }, - receiveValue: { _ in } - ) - .store(in: &cancellables) - - wait(for: [didRequestExpectation, didReceiveExpecatation], timeout: 0.5) - } - - func testResponseToBeEmptyToFailDueToBuilderError() { - let shouldErrorExpectation = expectation(description: "onError called expected") - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { _ in - Just((data: .init(), response: .withStatus(200))) - .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) - .eraseToAnyPublisher() - }, - middleware: [TestMiddleware(onErrorExpectation: shouldErrorExpectation)] - ) - - let builder = URLRequestBuilder(environment: Environment(url: "dad asdas"), endpoint: Environment(url: "dad asdas"), httpMethod: .get) - caller.call(using: builder, errorType: TestError.self) - .sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - switch error { - case .buildingURL: - break - default: - XCTFail("A wrong error is thrown. Expected NetworkingError.buildingURL, got \(error).") - } - case .finished: - XCTFail("Expected NetworkingError.buildingURL, got .finished completion.") - } - }, - receiveValue: { data in XCTFail("Expected NetworkingError.buildingURL, got \(data).") } - ) - .store(in: &cancellables) - - wait(for: [shouldErrorExpectation], timeout: 0.5) - } - - func testResponseDataToFailDueToBuilderError() { - let shouldErrorExpectation = expectation(description: "onError called expected") - let caller = CombineCaller( - decoder: decoder, - getDataPublisher: { _ in - Just((data: .init(), response: .withStatus(200))) - .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) - .eraseToAnyPublisher() - }, - middleware: [TestMiddleware(onErrorExpectation: shouldErrorExpectation)] - ) - let builder = URLRequestBuilder( - environment: Environment(url: "dad asdas"), - endpoint: Environment(url: "dad asdas"), - httpMethod: .get - ) - let intResponsePublisher: AnyPublisher = caller.call( - using: builder, - errorType: TestError.self - ) - - intResponsePublisher - .sink( - receiveCompletion: { completion in - switch completion { - case .failure(let error): - switch error { - case .buildingURL: - break - default: - XCTFail("A wrong error is thrown. Expected NetworkingError.buildingURL, got \(error).") - } - case .finished: - XCTFail("Expected NetworkingError.buildingURL, got .finished completion.") - } - }, - receiveValue: { data in XCTFail("Expected NetworkingError.buildingURL, got \(data).") } - ) - .store(in: &cancellables) - - wait(for: [shouldErrorExpectation], timeout: 0.5) - } -} - -private class TestMiddleware: Middleware { - - let onRequestExpectation: XCTestExpectation - let onResponseExpectation: XCTestExpectation - let onErrorExpectation: XCTestExpectation - - init( - onRequestExpectation: XCTestExpectation = .failing(description: "onRequest called"), - onResponseExpectation: XCTestExpectation = .failing(description: "onResponse called"), - onErrorExpectation: XCTestExpectation = .failing(description: "onError called") - ) { - self.onRequestExpectation = onRequestExpectation - self.onResponseExpectation = onResponseExpectation - self.onErrorExpectation = onErrorExpectation - } - - // MARK: URLRequestPlugable - - func onRequest(_ request: URLRequest) { - onRequestExpectation.fulfill() - } - - func onResponse(data: Data, response: URLResponse) { - onResponseExpectation.fulfill() - } - - func onError(_ error: NetworkingError, request: URLRequest?) { - onErrorExpectation.fulfill() - } -} - -private extension XCTestExpectation { - static func failing(description: String) -> XCTestExpectation { - let expectation = XCTestExpectation(description: description) - expectation.expectedFulfillmentCount = 1 - expectation.fulfill() - expectation.assertForOverFulfill = true - return expectation - } - - static func calledOnce(description: String) -> XCTestExpectation { - let expectation = XCTestExpectation(description: description) - expectation.expectedFulfillmentCount = 1 - expectation.assertForOverFulfill = true - return expectation - } -} - -private extension URLRequestCallerTests { - struct TestEncodable: Codable { - let name: String - let age: Int - } - - struct Environment: URLProviding { - var url: String = "https://google.com" - } -} - -private extension URLResponse { - static func withStatus(_ status: Int) -> HTTPURLResponse { - HTTPURLResponse( - url: URL(string: "https://google.com")!, - statusCode: status, - httpVersion: nil, - headerFields: nil - )! - } -} +//import XCTest +//import Combine +//@testable import NetworkRequester +// +//final class URLRequestCallerTests: XCTestCase { +// +// struct TestError: Error, Codable, Equatable { +// let message: String +// } +// +// private var encodedData: Data { +// let testableModel = TestEncodable(name: "first name", age: 1236) +// return try! JSONEncoder().encode(testableModel) +// } +// +// private var encodedTestErrorData: Data { +// let testableError = TestError(message: "Something went wrong") +// return try! JSONEncoder().encode(testableError) +// } +// +// private let decoder = JSONDecoder() +// private let request = URLRequest(url: URL(string: "https://google.com")!) +// private var cancellables = Set() +// +// func testCallSucceeds() { +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { [encodedData] _ in +// Just((encodedData, .withStatus(200))) +// .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) +// .eraseToAnyPublisher() +// } +// ) +// +// let testObjectResponsePublisher: AnyPublisher = caller +// .call(using: request, errorType: TestError.self) +// +// testObjectResponsePublisher.sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// XCTFail("Expected to receive value. Got error: \(error)") +// case .finished: +// break +// } +// }, +// receiveValue: { data in +// XCTAssertEqual(data.name, "first name") +// XCTAssertEqual(data.age, 1236) +// } +// ) +// .store(in: &cancellables) +// } +// +// func testCallFailsDueToURLError() { +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { _ in Fail(error: URLError(.badURL)).eraseToAnyPublisher() } +// ) +// +// caller.call(using: URLRequest(url: URL(string: "https://google.com")!), errorType: TestError.self) +// .sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// switch error { +// case .unknown: +// break +// default: +// XCTFail("A wrong error is thrown. Expected NetworkingError.unknown, got \(error).") +// } +// case .finished: +// XCTFail("Expected NetworkingError.unknown, got .finished completion.") +// } +// }, +// receiveValue: { data in XCTFail("Expected NetworkingError.unknown, got \(data).") } +// ) +// .store(in: &cancellables) +// } +// +// func testCallFailsDueToInvalidHTTPResponse() { +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { [encodedTestErrorData] _ in +// Just((encodedTestErrorData, .withStatus(1000))) +// .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) +// .eraseToAnyPublisher() +// } +// ) +// +// caller.call(using: URLRequest(url: URL(string: "https://google.com")!), errorType: TestError.self) +// .sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// switch error { +// case .networking(let status, let decodedError): +// switch status { +// case .internalServerError: +// XCTAssertEqual(decodedError as? TestError, TestError(message: "Something went wrong")) +// default: +// XCTFail("A wrong error is thrown. Expected NetworkingError.networking(status: .internalServerError), got \(error).") +// } +// default: +// XCTFail("A wrong error is thrown. Expected NetworkingError.unknown, got \(error).") +// } +// case .finished: +// XCTFail("Expected NetworkingError.unknown, got .finished completion.") +// } +// }, +// receiveValue: { data in XCTFail("Expected NetworkingError.unknown, got \(data).") } +// ) +// .store(in: &cancellables) +// } +// +// func testCallFailsDueToBadRequest() { +// let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") +// let didReceiveExpectation = XCTestExpectation.calledOnce(description: "onResponse called") +// let didErrorExpecatation = XCTestExpectation.calledOnce(description: "onError called") +// +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { [encodedTestErrorData] _ in +// Just((encodedTestErrorData, .withStatus(HTTPStatus.badRequest.code))) +// .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) +// .eraseToAnyPublisher() +// }, +// middleware: [ +// TestMiddleware( +// onRequestExpectation: didRequestExpectation, +// onResponseExpectation: didReceiveExpectation, +// onErrorExpectation: didErrorExpecatation +// ) +// ] +// ) +// +// caller.call(using: URLRequest(url: URL(string: "https://google.com")!), errorType: TestError.self) +// .sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// switch error { +// case .networking(let status, let decodedError): +// switch status { +// case .badRequest: +// XCTAssertEqual(decodedError as? TestError, TestError(message: "Something went wrong")) +// default: +// XCTFail("A wrong error is thrown. Expected NetworkingError.networking(status: .badRequest), got \(error).") +// } +// default: +// XCTFail("A wrong error is thrown. Expected NetworkingError.unknown, got \(error).") +// } +// case .finished: +// XCTFail("Expected NetworkingError.unknown, got .finished completion.") +// } +// }, +// receiveValue: { data in XCTFail("Expected NetworkingError.unknown, got \(data).") } +// ) +// .store(in: &cancellables) +// +// wait(for: [didRequestExpectation, didReceiveExpectation, didErrorExpecatation], timeout: 0.5) +// } +// +// func testResponseToBeIntButGotEmpty() { +// let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") +// let didReceiveExpectation = XCTestExpectation.calledOnce(description: "onResponse called") +// let didErrorExpecatation = XCTestExpectation.calledOnce(description: "onError called") +// +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { _ in +// Just((data: .init(), response: .withStatus(200))) +// .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) +// .eraseToAnyPublisher() +// }, +// middleware: [ +// TestMiddleware( +// onRequestExpectation: didRequestExpectation, +// onResponseExpectation: didReceiveExpectation, +// onErrorExpectation: didErrorExpecatation +// ) +// ] +// ) +// +// let intResponsePublisher: AnyPublisher = caller.call(using: request, errorType: TestError.self) +// intResponsePublisher +// .sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// switch error { +// case .decoding: +// break +// default: +// XCTFail("A wrong error is thrown. Expected NetworkingError.decoding(error: DecodingError), got \(error).") +// } +// case .finished: +// XCTFail("Expected NetworkingError.decoding(error: DecodingError), got .finished completion.") +// } +// }, +// receiveValue: { data in XCTFail("Expected NetworkingError.decoding(error: DecodingError), got \(data).") } +// ) +// .store(in: &cancellables) +// +// wait(for: [didRequestExpectation, didReceiveExpectation, didErrorExpecatation], timeout: 0.5) +// } +// +// func testResponseToBeEmptyButGotData() { +// let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") +// let didReceiveExpectation = XCTestExpectation.calledOnce(description: "onResponse called") +// let didErrorExpecatation = XCTestExpectation.calledOnce(description: "onError called") +// +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { [encodedData] _ in +// Just((data: encodedData, response: .withStatus(200))) +// .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) +// .eraseToAnyPublisher() +// }, +// middleware: [ +// TestMiddleware( +// onRequestExpectation: didRequestExpectation, +// onResponseExpectation: didReceiveExpectation, +// onErrorExpectation: didErrorExpecatation +// ) +// ] +// ) +// +// caller.call(using: request, errorType: TestError.self) +// .sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// switch error { +// case .decoding: +// break +// default: +// XCTFail("A wrong error is thrown. Expected NetworkingError.decoding(error: DecodingError), got \(error).") +// } +// case .finished: +// XCTFail("Expected NetworkingError.decoding(error: DecodingError), got .finished completion.") +// } +// }, +// receiveValue: { data in XCTFail("Expected NetworkingError.decoding(error: DecodingError), got \(data).") } +// ) +// .store(in: &cancellables) +// +// wait(for: [didRequestExpectation, didReceiveExpectation, didErrorExpecatation], timeout: 0.5) +// } +// +// func testResponseToBeEmptyAndGotEmpty() { +// let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") +// let didReceiveExpecatation = XCTestExpectation.calledOnce(description: "onResponse called") +// +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { _ in +// Just((data: .init(), response: .withStatus(200))) +// .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) +// .eraseToAnyPublisher() +// }, +// middleware: [ +// TestMiddleware( +// onRequestExpectation: didRequestExpectation, +// onResponseExpectation: didReceiveExpecatation +// ) +// ] +// ) +// +// caller.call(using: request, errorType: TestError.self) +// .sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// XCTFail("Expected to receive value. Got error: \(error)") +// case .finished: +// break +// } +// }, +// receiveValue: { _ in } +// ) +// .store(in: &cancellables) +// +// wait(for: [didRequestExpectation, didReceiveExpecatation], timeout: 0.5) +// } +// +// func testResponseToBeEmptyAndGotEmptyUsingURLRequestBuilder() { +// let didRequestExpectation = XCTestExpectation.calledOnce(description: "onRequest called") +// let didReceiveExpecatation = XCTestExpectation.calledOnce(description: "onResponse called") +// +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { _ in +// Just((data: .init(), response: .withStatus(200))) +// .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) +// .eraseToAnyPublisher() +// }, +// middleware: [ +// TestMiddleware( +// onRequestExpectation: didRequestExpectation, +// onResponseExpectation: didReceiveExpecatation +// ) +// ] +// ) +// +// let builder = URLRequestBuilder(environment: Environment(), endpoint: Environment(), httpMethod: .get) +// caller.call(using: builder, errorType: TestError.self) +// .sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// XCTFail("Expected to receive value. Got error: \(error)") +// case .finished: +// break +// } +// }, +// receiveValue: { _ in } +// ) +// .store(in: &cancellables) +// +// wait(for: [didRequestExpectation, didReceiveExpecatation], timeout: 0.5) +// } +// +// func testResponseToBeEmptyToFailDueToBuilderError() { +// let shouldErrorExpectation = expectation(description: "onError called expected") +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { _ in +// Just((data: .init(), response: .withStatus(200))) +// .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) +// .eraseToAnyPublisher() +// }, +// middleware: [TestMiddleware(onErrorExpectation: shouldErrorExpectation)] +// ) +// +// let builder = URLRequestBuilder(environment: Environment(url: "dad asdas"), endpoint: Environment(url: "dad asdas"), httpMethod: .get) +// caller.call(using: builder, errorType: TestError.self) +// .sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// switch error { +// case .buildingURL: +// break +// default: +// XCTFail("A wrong error is thrown. Expected NetworkingError.buildingURL, got \(error).") +// } +// case .finished: +// XCTFail("Expected NetworkingError.buildingURL, got .finished completion.") +// } +// }, +// receiveValue: { data in XCTFail("Expected NetworkingError.buildingURL, got \(data).") } +// ) +// .store(in: &cancellables) +// +// wait(for: [shouldErrorExpectation], timeout: 0.5) +// } +// +// func testResponseDataToFailDueToBuilderError() { +// let shouldErrorExpectation = expectation(description: "onError called expected") +// let caller = CombineCaller( +// decoder: decoder, +// getDataPublisher: { _ in +// Just((data: .init(), response: .withStatus(200))) +// .setFailureType(to: CombineCaller.AnyURLSessionDataPublisher.Failure.self) +// .eraseToAnyPublisher() +// }, +// middleware: [TestMiddleware(onErrorExpectation: shouldErrorExpectation)] +// ) +// let builder = URLRequestBuilder( +// environment: Environment(url: "dad asdas"), +// endpoint: Environment(url: "dad asdas"), +// httpMethod: .get +// ) +// let intResponsePublisher: AnyPublisher = caller.call( +// using: builder, +// errorType: TestError.self +// ) +// +// intResponsePublisher +// .sink( +// receiveCompletion: { completion in +// switch completion { +// case .failure(let error): +// switch error { +// case .buildingURL: +// break +// default: +// XCTFail("A wrong error is thrown. Expected NetworkingError.buildingURL, got \(error).") +// } +// case .finished: +// XCTFail("Expected NetworkingError.buildingURL, got .finished completion.") +// } +// }, +// receiveValue: { data in XCTFail("Expected NetworkingError.buildingURL, got \(data).") } +// ) +// .store(in: &cancellables) +// +// wait(for: [shouldErrorExpectation], timeout: 0.5) +// } +//} +// +//private class TestMiddleware: Middleware { +// +// let onRequestExpectation: XCTestExpectation +// let onResponseExpectation: XCTestExpectation +// let onErrorExpectation: XCTestExpectation +// +// init( +// onRequestExpectation: XCTestExpectation = .failing(description: "onRequest called"), +// onResponseExpectation: XCTestExpectation = .failing(description: "onResponse called"), +// onErrorExpectation: XCTestExpectation = .failing(description: "onError called") +// ) { +// self.onRequestExpectation = onRequestExpectation +// self.onResponseExpectation = onResponseExpectation +// self.onErrorExpectation = onErrorExpectation +// } +// +// // MARK: URLRequestPlugable +// +// func onRequest(_ request: URLRequest) { +// onRequestExpectation.fulfill() +// } +// +// func onResponse(data: Data, response: URLResponse) { +// onResponseExpectation.fulfill() +// } +// +// func onError(_ error: NetworkingError, request: URLRequest?) { +// onErrorExpectation.fulfill() +// } +//} +// +//private extension XCTestExpectation { +// static func failing(description: String) -> XCTestExpectation { +// let expectation = XCTestExpectation(description: description) +// expectation.expectedFulfillmentCount = 1 +// expectation.fulfill() +// expectation.assertForOverFulfill = true +// return expectation +// } +// +// static func calledOnce(description: String) -> XCTestExpectation { +// let expectation = XCTestExpectation(description: description) +// expectation.expectedFulfillmentCount = 1 +// expectation.assertForOverFulfill = true +// return expectation +// } +//} +// +//private extension URLRequestCallerTests { +// struct TestEncodable: Codable { +// let name: String +// let age: Int +// } +// +// struct Environment: URLProviding { +// var url: String = "https://google.com" +// } +//} +// +//private extension URLResponse { +// static func withStatus(_ status: Int) -> HTTPURLResponse { +// HTTPURLResponse( +// url: URL(string: "https://google.com")!, +// statusCode: status, +// httpVersion: nil, +// headerFields: nil +// )! +// } +//}