Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow returning complete api response #101

Merged
merged 3 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 42 additions & 11 deletions Sources/InfomaniakCore/Networking/ApiFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public protocol RefreshTokenDelegate: AnyObject {

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
open class ApiFetcher {
enum ErrorDomain: Error {
case noServerResponse
}

public typealias RequestModifier = (inout URLRequest) throws -> Void

/// All status except 401 are handled by our code, 401 status is handled by Alamofire's Authenticator code
Expand Down Expand Up @@ -136,24 +140,51 @@ open class ApiFetcher {
)
}

@available(*, deprecated, message: "Use perform with ValidServerResponse instead")
adrien-coye marked this conversation as resolved.
Show resolved Hide resolved
open func perform<T: Decodable>(request: DataRequest,
decoder: JSONDecoder = ApiFetcher.decoder) async throws -> (data: T, responseAt: Int?) {
let validServerResponse: ValidServerResponse<T> = try await perform(request: request, decoder: decoder)
return (validServerResponse.validApiResponse.data, validServerResponse.validApiResponse.responseAt)
}

open func perform<T: Decodable>(request: DataRequest,
decoder: JSONDecoder = ApiFetcher.decoder) async throws -> T {
return try await perform(request: request, decoder: decoder).validApiResponse.data
}

open func perform<T: Decodable>(request: DataRequest,
decoder: JSONDecoder = ApiFetcher.decoder) async throws -> ValidServerResponse<T> {
let validatedRequest = request.validate(statusCode: ApiFetcher.handledHttpStatus)
let response = await validatedRequest.serializingDecodable(ApiResponse<T>.self,
automaticallyCancelling: true,
decoder: decoder).response
let apiResponse = try response.result.get()
return try handleApiResponse(apiResponse, responseStatusCode: response.response?.statusCode ?? -1)
let dataResponse = await validatedRequest.serializingDecodable(ApiResponse<T>.self,
automaticallyCancelling: true,
decoder: decoder).response
return try handleApiResponse(dataResponse)
}

open func handleApiResponse<T: Decodable>(_ response: ApiResponse<T>,
responseStatusCode: Int) throws -> (data: T, responseAt: Int?) {
if let responseData = response.data {
return (responseData, response.responseAt)
} else if let apiError = response.error {
open func handleApiResponse<T: Decodable>(_ dataResponse: DataResponse<ApiResponse<T>, AFError>) throws
-> ValidServerResponse<T> {
let apiResponse = try dataResponse.result.get()

// This value should not be null because dataResponse.result.get should throw before
guard let serverResponse = dataResponse.response else {
throw ErrorDomain.noServerResponse
}

if let responseData = apiResponse.data {
let validApiResponse = ValidApiResponse(
result: apiResponse.result,
data: responseData,
total: apiResponse.total,
pages: apiResponse.pages,
page: apiResponse.page,
itemsPerPage: apiResponse.itemsPerPage,
responseAt: apiResponse.responseAt
)
return ValidServerResponse(responseHeaders: serverResponse.headers, validApiResponse: validApiResponse)
} else if let apiError = apiResponse.error {
throw InfomaniakError.apiError(apiError)
} else {
throw InfomaniakError.serverError(statusCode: responseStatusCode)
throw InfomaniakError.serverError(statusCode: serverResponse.statusCode)
}
}

Expand Down
34 changes: 34 additions & 0 deletions Sources/InfomaniakCore/Networking/ValidApiResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
Infomaniak Core - iOS
Copyright (C) 2024 Infomaniak Network SA

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import Alamofire

public struct ValidServerResponse<ValidApiResponseContent> {
public let responseHeaders: HTTPHeaders
public let validApiResponse: ValidApiResponse<ValidApiResponseContent>
}

public struct ValidApiResponse<ResponseContent> {
public let result: ApiResult
public let data: ResponseContent
public let total: Int?
public let pages: Int?
public let page: Int?
public let itemsPerPage: Int?
public let responseAt: Int?
}
31 changes: 22 additions & 9 deletions Tests/InfomaniakCoreTests/UTDecodeApiResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,23 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

@testable import Alamofire
@testable import InfomaniakCore
import XCTest

@available(iOS 13.0, *)
final class UTDecodeApiResponse: XCTestCase {
func fakeDataResponse<T: Decodable>(decodedResponse: ApiResponse<T>) -> DataResponse<ApiResponse<T>, AFError> {
DataResponse(
request: nil,
response: HTTPURLResponse(),
data: nil,
metrics: nil,
serializationDuration: 0,
result: .success(decodedResponse)
)
}

func testDecodeNullDataResponse() throws {
// GIVEN
let apiFetcher = ApiFetcher()
Expand All @@ -33,12 +45,12 @@ final class UTDecodeApiResponse: XCTestCase {

// WHEN
let decodedResponse = try? JSONDecoder().decode(ApiResponse<NullableResponse>.self, from: jsonData)
let dataResponse = fakeDataResponse(decodedResponse: decodedResponse!)

// THEN
XCTAssertNotNil(decodedResponse, "Response shouldn't be nil")
XCTAssertNoThrow(
try apiFetcher.handleApiResponse(decodedResponse!, responseStatusCode: 0),
"handleApiResponse shouldn't throw"
try apiFetcher.handleApiResponse(dataResponse), "handleApiResponse shouldn't throw"
)
}

Expand All @@ -54,13 +66,11 @@ final class UTDecodeApiResponse: XCTestCase {

// WHEN
let decodedResponse = try? JSONDecoder().decode(ApiResponse<Int>.self, from: jsonData)
let dataResponse = fakeDataResponse(decodedResponse: decodedResponse!)

// THEN
XCTAssertNotNil(decodedResponse, "Response shouldn't be nil")
XCTAssertNoThrow(
try apiFetcher.handleApiResponse(decodedResponse!, responseStatusCode: 0),
"handleApiResponse shouldn't throw"
)
XCTAssertNoThrow(try apiFetcher.handleApiResponse(dataResponse), "handleApiResponse shouldn't throw")
}

func testDecodeErrorNullApiResponse() throws {
Expand All @@ -74,11 +84,12 @@ final class UTDecodeApiResponse: XCTestCase {

// WHEN
let decodedResponse = try? JSONDecoder().decode(ApiResponse<Int>.self, from: jsonData)
let dataResponse = fakeDataResponse(decodedResponse: decodedResponse!)

// THEN
XCTAssertNotNil(decodedResponse, "Response shouldn't be nil")
do {
let _ = try apiFetcher.handleApiResponse(decodedResponse!, responseStatusCode: 0)
let _ = try apiFetcher.handleApiResponse(dataResponse)
} catch {
let ikError = error as? InfomaniakError
XCTAssertNotNil(ikError, "Error should be InfomaniakError")
Expand All @@ -97,11 +108,12 @@ final class UTDecodeApiResponse: XCTestCase {

// WHEN
let decodedResponse = try? JSONDecoder().decode(ApiResponse<Int>.self, from: jsonData)
let dataResponse = fakeDataResponse(decodedResponse: decodedResponse!)

// THEN
XCTAssertNotNil(decodedResponse, "Response shouldn't be nil")
do {
let _ = try apiFetcher.handleApiResponse(decodedResponse!, responseStatusCode: 0)
let _ = try apiFetcher.handleApiResponse(dataResponse)
} catch {
let ikError = error as? InfomaniakError
XCTAssertNotNil(ikError, "Error should be InfomaniakError")
Expand All @@ -123,11 +135,12 @@ final class UTDecodeApiResponse: XCTestCase {

// WHEN
let decodedResponse = try? JSONDecoder().decode(ApiResponse<Int>.self, from: jsonData)
let dataResponse = fakeDataResponse(decodedResponse: decodedResponse!)

// THEN
XCTAssertNotNil(decodedResponse, "Response shouldn't be nil")
do {
let _ = try apiFetcher.handleApiResponse(decodedResponse!, responseStatusCode: 0)
let _ = try apiFetcher.handleApiResponse(dataResponse)
} catch {
let ikError = error as? InfomaniakError
XCTAssertNotNil(ikError, "Error should be InfomaniakError")
Expand Down
Loading