Изменить модель / форму ответа в зависимости от пути - PullRequest
0 голосов
/ 28 декабря 2018

У меня есть сетевой уровень, который я использую для вызова нескольких конечных точек.Я хотел бы уменьшить количество повторяющегося кода и подумал, что, возможно, я смогу передать свою модель ответа как часть моей конечной точки.

Идея состоит в том, чтобы вместо необходимости использовать несколько функций, которые просто отличаются по ответу, я мог бы простовызовите мой сетевой уровень и получите этот набор на основе пути.

Текущая ошибка, которую я вижу,

Var 'responseType' не является типом элемента 'IdentityEndpoint'

Я надеялся достичь чего-то подобного

mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void)

вместо этого

mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<OAuthToken>) -> Void) 

APIClient

struct APIClient: APIClientProtocol {
    var task: URLSessionDataTask = URLSessionDataTask()
    var session: SessionProtocol = URLSession.shared
    var request: URLRequest?

    mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void) {
        dispatch(endpoint: endpoint, completion: completion)
    }
}

extension APIClient {
    fileprivate mutating func dispatch<T: Codable>(endpoint: EndpointProtocol, completion: @escaping (Either<T>) -> Void) {
        do {
            request = try constructRequest(from: endpoint)
            guard let request = request else { return }
            call(with: request, completion: completion)
        } catch {}
    }

    fileprivate func constructRequest(from route: EndpointProtocol) throws -> URLRequest {
        var request = URLRequest(url: route.baseUrl.appendingPathComponent(route.path), cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 10.0)
        request.httpMethod = route.httpMethod.rawValue
        do {
            switch route.task {
            case .request(let headers):
                addAdditionalHeaders(headers, request: &request)
            case .requestParams(let bodyParams, let encoding, let urlParams, let headers):
                addAdditionalHeaders(headers, request: &request)
                try configureParameters(bodyParams: bodyParams, encoding: encoding, urlParams: urlParams, request: &request)
            }
            return request
        } catch {
            throw NSError(domain: "Could not create request task for \(route.task)", code: 0, userInfo: nil)
        }
    }

    fileprivate func configureParameters(bodyParams: Parameters?, encoding: ParameterEncoding, urlParams: Parameters?, request: inout URLRequest) throws {
        do {
            try encoding.encode(urlRequest: &request, bodyParams: bodyParams, urlParams: urlParams)
        } catch {
            throw NSError(domain: "Could not configure params for request", code: 0, userInfo: nil)
        }
    }

    fileprivate func addAdditionalHeaders(_ additionalHeaders: HTTPHeaders?, request: inout URLRequest) {
        guard let headers = additionalHeaders else { return }
        for (key, value) in headers {
            request.setValue(value, forHTTPHeaderField: key)
        }
    }
}

IdentityEndPoint

protocol EndpointProtocol {
    var baseUrl: URL { get }
    var path: String { get }
    var httpMethod: HTTPMethod { get }
    var task: HTTPTask { get }
    var headers: HTTPHeaders? { get }
}


public enum IdentityEndpoint {
    case accessToken(company: String, code: String)

    func getDomain(forService service: String) -> URL {
        return URL(string: "https://{SERVICE}.foo.bar".replacingOccurrences(of: "{SERVICE}", with: service))!
    }
}

extension IdentityEndpoint: EndpointProtocol {
    var baseUrl: URL {
        return getDomain(forService: "identity")
    }

    var responseType: Codable {
        switch self {
        default:
            return OAuthToken.self as! Codable
        }
    }

    var path: String {
        switch self {
        case .accessToken(let props):
            return "/auth/realms/\(props.company)/protocol/openid-connect/token"
        }
    }

    var httpMethod: HTTPMethod {
        switch self {
        case .accessToken:
            return .POST
        }
    }

    var headers: HTTPHeaders? {
        switch self {
        case .accessToken:
            return ["Content-Type": "application/x-www-form-urlencoded"]
        }
    }

    var task: HTTPTask {
        switch self {
        case .accessToken(let props):
            return .requestParams(bodyParams: [
                "grant_type": "authorization_code", "code": "\(props.code)", "redirect_uri": "homedev://oauth-callback", "client_id": "mobile-home"
            ], encoding: .jsonEncoding, urlParams: nil, headers: headers)
        }
    }
}

1 Ответ

0 голосов
/ 08 января 2019

Добавьте associatedtype к вашему EndpointProtocol.Затем укажите это в IdentityEndpoint как это

protocol EndpointProtocol {
    associatedtype ResponseType

    ...
}

extension IdentityEndpoint: EndpointProtocol {
    typealias ResponseType = OAuthToken

    ...
}

Теперь вы сможете написать

mutating func identity(
   with endpoint: IdentityEndpoint,
   completion: @escaping (Either<IdentityEndpoint.ResponseType>) -> Void
)
...