Skip to content

Commit

Permalink
PIA-1842: Add sign up native endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
kp-said-rehouni committed Jun 6, 2024
1 parent 94f1ab8 commit 80598b1
Show file tree
Hide file tree
Showing 16 changed files with 349 additions and 23 deletions.
41 changes: 18 additions & 23 deletions Sources/PIALibrary/Account/Data/ClientErrorMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,53 @@

import Foundation

enum UseCaseType {
case login
case logout
}

/// Maps an Network Error with a ClientError
/// The idea is to use this mapper on `PIAWebServices` to map the errors returned from the Swift implementation of the Accounts Lib with the ones that the app expects
struct ClientErrorMapper {
static func map(networkRequestError: NetworkRequestError, useCase: UseCaseType) -> ClientError {
switch (networkRequestError, useCase) {
case (.connectionError(let statusCode, let message), _):
return getClientError(from: statusCode, useCase: useCase) ?? .unexpectedReply
static func map(networkRequestError: NetworkRequestError) -> ClientError {
switch networkRequestError {
case .connectionError(let statusCode, let message):
return getClientError(from: statusCode) ?? .unexpectedReply

case (.allConnectionAttemptsFailed(let statusCode), _):
return getClientError(from: statusCode, useCase: useCase) ?? .unexpectedReply
case .allConnectionAttemptsFailed(let statusCode):
return getClientError(from: statusCode) ?? .unexpectedReply

case (.noDataContent, _):
case .noDataContent:
return .malformedResponseData

case (.noErrorAndNoResponse, _):
case .noErrorAndNoResponse:
return .unexpectedReply

case (.unableToSaveVpnToken, _):
case .unableToSaveVpnToken:
return .unexpectedReply

case (.unableToSaveAPIToken, _):
case .unableToSaveAPIToken:
return .unexpectedReply

case (.connectionCompletedWithNoResponse, _):
case .connectionCompletedWithNoResponse:
return .malformedResponseData

case (.unknown(message: let message), _):
case .unknown(message: let message):
return .unexpectedReply

case (.unableToDecodeAPIToken, _):
case .unableToDecodeAPIToken, .unableToDecodeDataContent:
return .malformedResponseData

case (.unableToDecodeVpnToken, _):
case .unableToDecodeVpnToken:
return .malformedResponseData
}
}

static func getClientError(from statusCode: Int?, useCase: UseCaseType) -> ClientError? {
static func getClientError(from statusCode: Int?) -> ClientError? {

guard let statusCode,
let httpStatusCode = HttpResponseStatusCode(rawValue: statusCode) else {
return nil
}
switch (httpStatusCode, useCase) {
case (.unauthorized, _):
switch httpStatusCode {
case .unauthorized:
return .unauthorized
case (.throttled, _):
case .throttled:
return .throttled(retryAfter: 60)
default:
return nil
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

import Foundation
import NWHttpConnection

struct SignupRequestConfiguration: NetworkRequestConfigurationType {
let networkRequestModule: NetworkRequestModule = .account
let path: RequestAPI.Path = .signup
let httpMethod: NWHttpConnection.NWConnectionHTTPMethod = .post
let inlcudeAuthHeaders: Bool = false

// Refreshing the auth tokens is not needed before executing the refresh API token request
let refreshAuthTokensIfNeeded: Bool = false
var contentType: NetworkRequestContentType = .json
let urlQueryParameters: [String : String]? = nil
let responseDataType: NWDataResponseType = .jsonData
var body: Data? = nil
let timeout: TimeInterval = 10
let requestQueue: DispatchQueue? = DispatchQueue(label: "signup.queue")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

import Foundation

class SignupInformationDataCoverter: SignupInformationDataCoverterType {
func callAsFunction(signup: Signup) -> Data? {
let signupInformation = SignupInformation(store: "apple_app_store",
receipt: signup.receipt.base64EncodedString(),
email: signup.email,
marketing: stringify(json: signup.marketing),
debug: stringify(json: signup.debug))

return signupInformation.toData()
}

private func stringify(json: [String: Any]?, prettyPrinted: Bool = false) -> String? {
guard let json else {
return nil
}

var options: JSONSerialization.WritingOptions = []
if prettyPrinted {
options = JSONSerialization.WritingOptions.prettyPrinted
}

do {
let data = try JSONSerialization.data(withJSONObject: json, options: options)
if let string = String(data: data, encoding: String.Encoding.utf8) {
return string
}
} catch {
print(error)
}

return nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

import Foundation

struct SignUpAccountnformation: Codable {
let status: String
let username: String
let password: String
}

extension SignUpAccountnformation {
func toDomainModel() -> Credentials {
Credentials(username: username, password: password)
}

static func makeWith(data: Data) -> SignUpAccountnformation? {
try? JSONDecoder().decode(SignUpAccountnformation.self, from: data)
}
}
16 changes: 16 additions & 0 deletions Sources/PIALibrary/Account/Domain/Entities/SignupInformation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

import Foundation

struct SignupInformation: Encodable {
let store: String
let receipt: String
let email: String
let marketing: String?
let debug: String?
}

extension SignupInformation {
func toData() -> Data? {
try? JSONEncoder().encode(self)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

import Foundation

protocol SignupInformationDataCoverterType {
func callAsFunction(signup: Signup) -> Data?
}
51 changes: 51 additions & 0 deletions Sources/PIALibrary/Account/Domain/UseCases/SignupUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

import Foundation

public protocol SignupUseCaseType {
typealias Completion = ((Result<Credentials, NetworkRequestError>) -> Void)
func callAsFunction(signup: Signup, completion: @escaping SignupUseCaseType.Completion)
}

class SignupUseCase: SignupUseCaseType {
private let networkClient: NetworkRequestClientType
private let signupInformationDataCoverter: SignupInformationDataCoverterType

init(networkClient: NetworkRequestClientType, signupInformationDataCoverter: SignupInformationDataCoverterType) {
self.networkClient = networkClient
self.signupInformationDataCoverter = signupInformationDataCoverter
}

func callAsFunction(signup: Signup, completion: @escaping SignupUseCaseType.Completion) {
var configuration = SignupRequestConfiguration()
configuration.body = signupInformationDataCoverter(signup: signup)

networkClient.executeRequest(with: configuration) { [weak self] error, dataResponse in
guard let self else { return }
if let error {
completion(.failure(error))
} else if let dataResponse {
self.handleDataResponse(dataResponse, completion: completion)
} else {
completion(.failure(NetworkRequestError.allConnectionAttemptsFailed()))
}
}
}
}

private extension SignupUseCase {
private func handleDataResponse(_ dataResponse: NetworkRequestResponseType, completion: @escaping SignupUseCaseType.Completion) {
guard let dataResponseContent = dataResponse.data else {
completion(.failure(NetworkRequestError.noDataContent))
return
}

guard let dto = SignUpAccountnformation.makeWith(data: dataResponseContent) else {
completion(.failure(NetworkRequestError.unableToDecodeDataContent))
return
}

completion(.success(dto.toDomainModel()))
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public enum NetworkRequestError: Error, Equatable {
case unableToSaveAPIToken
case unableToDecodeAPIToken
case unableToDecodeVpnToken
case unableToDecodeDataContent
case connectionCompletedWithNoResponse
case unknown(message: String? = nil)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

import Foundation
@testable import PIALibrary

struct NetworkRequestResponseStub: NetworkRequestResponseType {
let statusCode: Int? = 200
let data: Data?

init(data: Data?) {
self.data = data
}
}
Loading

0 comments on commit 80598b1

Please sign in to comment.