Skip to content

Commit

Permalink
Add methods to request raw data skipping decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeehut committed Oct 14, 2021
1 parent af63ee3 commit 4493010
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a

## [Unreleased]
### Added
- None.
- New methods for requesting raw `Data` response to allow for using alternative decoding methods such as [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON): `performRawDataRequest(AndWait)` (classic approach), `rawDataPublisher` (Combine) and `rawDataResponse` (Swift concurrency).
### Changed
- None.
### Deprecated
Expand Down
40 changes: 40 additions & 0 deletions Sources/Microya/Core/ApiProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ open class ApiProvider<EndpointType: Endpoint> {
self.mockingBehavior = mockingBehavior
}

/// Returns a publisher which performs a request to the server when new values are requested.
/// Returns the raw response `Data` on success for custom handling, skipping the decoding logic.
///
/// - NOTE: Do not use this if you expect a `Decodable` response, use `publisher(on:decodeBodyTo:)` instead.
public func rawDataPublisher(
on endpoint: EndpointType
) -> AnyPublisher<Data, ApiError<EndpointType.ClientErrorType>> {
self.publisher(on: endpoint, decodeBodyTo: Data.self)
}

/// Returns a publisher which performs a request to the server when new values are requested.
/// Returns a `EmptyBodyResponse` on success.
///
Expand Down Expand Up @@ -137,6 +147,13 @@ open class ApiProvider<EndpointType: Endpoint> {
}
}

/// - NOTE: Do not use this if you expect a `Decodable` response, use `response(on:decodeBodyTo:)` instead.
@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
public func rawDataResponse(on endpoint: EndpointType) async -> TypedResult<Data> {
await self.response(on: endpoint, decodeBodyTo: Data.self)
}

/// - WARNING: Do not use this if you expect a body response, use `response(on:decodeBodyTo:)` instead.
@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
public func response(on endpoint: EndpointType) async -> TypedResult<EmptyBodyResponse> {
await self.response(on: endpoint, decodeBodyTo: EmptyBodyResponse.self)
Expand Down Expand Up @@ -199,6 +216,25 @@ open class ApiProvider<EndpointType: Endpoint> {
}
}

/// Performs the asynchronous request for the chosen write-only endpoint and calls the completion closure with the result.
/// Returns the raw response `Data` on success for custom handling, skipping the decoding logic.
///
/// - NOTE: Do not use this if you expect a `Decodable` response, use `performRequest(on:decodeBodyTo:completion:)` instead.
public func performRawDataRequest(
on endpoint: EndpointType,
completion: @escaping (TypedResult<Data>) -> Void
) {
self.performRequest(on: endpoint, decodeBodyTo: Data.self, completion: completion)
}

/// Performs the request for the chosen write-only endpoint synchronously (waits for the result).
/// Returns the raw response `Data` on success for custom handling, skipping the decoding logic.
///
/// - NOTE: Do not use this if you expect a `Decodable` response, use `performRequestAndWait(on:decodeBodyTo:)` instead.
public func performRawDataRequestAndWait(on endpoint: EndpointType) -> TypedResult<Data> {
self.performRequestAndWait(on: endpoint, decodeBodyTo: Data.self)
}

/// Performs the asynchronous request for the chosen write-only endpoint and calls the completion closure with the result.
/// Returns a `EmptyBodyResponse` on success.
///
Expand Down Expand Up @@ -335,6 +371,10 @@ open class ApiProvider<EndpointType: Endpoint> {
throw ApiError<EndpointType.ClientErrorType>.noDataInResponse(statusCode: httpResponse.statusCode)
}

guard ResultType.self != Data.self else {
return data as! ResultType
}

do {
return try endpoint.decoder.decode(ResultType.self, from: data)
}
Expand Down
99 changes: 99 additions & 0 deletions Tests/MicroyaTests/MicroyaIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,35 @@ class MicroyaIntegrationTests: XCTestCase {
XCTAssertEqual(typedResponseBody.url, "https://postman-echo.com/post")
}

func testRawDataPost() throws {
let dataResponseBody =
try sampleApiProvider.performRawDataRequestAndWait(
on: .post(fooBar: FooBar(foo: "Lorem", bar: "Ipsum"))
)
.get()

XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Content-Type"], "application/json")
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Accept"], "application/json")
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Accept-Language"], "en")
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Authorization"], "Basic abc123")

XCTAssertEqual(TestDataStore.request?.httpMethod, "POST")
XCTAssertEqual(TestDataStore.request?.url?.path, "/post")
XCTAssertNil(TestDataStore.request?.url?.query)

XCTAssertNotNil(TestDataStore.urlSessionResult?.data)
XCTAssertNil(TestDataStore.urlSessionResult?.error)
XCTAssertNotNil(TestDataStore.urlSessionResult?.response)

let typedResponseBody = try JSONDecoder().decode(PostmanEchoResponse.self, from: dataResponseBody)

XCTAssertEqual(typedResponseBody.args, [:])
XCTAssertEqual(typedResponseBody.headers["content-type"], "application/json")
XCTAssertEqual(typedResponseBody.headers["accept"], "application/json")
XCTAssertEqual(typedResponseBody.headers["accept-language"], "en")
XCTAssertEqual(typedResponseBody.url, "https://postman-echo.com/post")
}

func testGet() throws {
let expectation = XCTestExpectation()

Expand Down Expand Up @@ -237,6 +266,46 @@ class MicroyaIntegrationTests: XCTestCase {
#endif
}

func testPostRawDataCombine() throws {
#if canImport(Combine)
let expectation = XCTestExpectation()

sampleApiProvider.rawDataPublisher(
on: .post(fooBar: FooBar(foo: "Lorem", bar: "Ipsum"))
)
.sink(
receiveCompletion: { _ in },
receiveValue: { dataResponseBody in
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Content-Type"], "application/json")
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Accept"], "application/json")
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Accept-Language"], "en")
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Authorization"], "Basic abc123")

XCTAssertEqual(TestDataStore.request?.httpMethod, "POST")
XCTAssertEqual(TestDataStore.request?.url?.path, "/post")
XCTAssertNil(TestDataStore.request?.url?.query)

XCTAssertNotNil(TestDataStore.urlSessionResult?.data)
XCTAssertNil(TestDataStore.urlSessionResult?.error)
XCTAssertNotNil(TestDataStore.urlSessionResult?.response)

let typedResponseBody = try! JSONDecoder().decode(PostmanEchoResponse.self, from: dataResponseBody)

XCTAssertEqual(typedResponseBody.args, [:])
XCTAssertEqual(typedResponseBody.headers["content-type"], "application/json")
XCTAssertEqual(typedResponseBody.headers["accept"], "application/json")
XCTAssertEqual(typedResponseBody.headers["accept-language"], "en")
XCTAssertEqual(typedResponseBody.url, "https://postman-echo.com/post")

expectation.fulfill()
}
)
.store(in: &cancellables)

wait(for: [expectation], timeout: 10)
#endif
}

func testGetCombine() throws {
#if canImport(Combine)
let expectation = XCTestExpectation()
Expand Down Expand Up @@ -525,6 +594,36 @@ class MicroyaIntegrationTests: XCTestCase {
XCTAssertEqual(typedResponseBody.url, "https://postman-echo.com/post")
}

@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
func testPostRawDataAsync() async throws {
let dataResponseBody =
try await sampleApiProvider.rawDataResponse(
on: .post(fooBar: FooBar(foo: "Lorem", bar: "Ipsum"))
)
.get()

XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Content-Type"], "application/json")
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Accept"], "application/json")
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Accept-Language"], "en")
XCTAssertEqual(TestDataStore.request?.allHTTPHeaderFields?["Authorization"], "Basic abc123")

XCTAssertEqual(TestDataStore.request?.httpMethod, "POST")
XCTAssertEqual(TestDataStore.request?.url?.path, "/post")
XCTAssertNil(TestDataStore.request?.url?.query)

XCTAssertNotNil(TestDataStore.urlSessionResult?.data)
XCTAssertNil(TestDataStore.urlSessionResult?.error)
XCTAssertNotNil(TestDataStore.urlSessionResult?.response)

let typedResponseBody = try JSONDecoder().decode(PostmanEchoResponse.self, from: dataResponseBody)

XCTAssertEqual(typedResponseBody.args, [:])
XCTAssertEqual(typedResponseBody.headers["content-type"], "application/json")
XCTAssertEqual(typedResponseBody.headers["accept"], "application/json")
XCTAssertEqual(typedResponseBody.headers["accept-language"], "en")
XCTAssertEqual(typedResponseBody.url, "https://postman-echo.com/post")
}

@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
func testGetAsync() async {
let response = await sampleApiProvider.response(
Expand Down

0 comments on commit 4493010

Please sign in to comment.