根据路径更改响应模型/形状

时间:2018-12-28 16:23:20

标签: swift generics codable urlsession

我有一个网络层,可用来呼叫多个端点。我想减少重复代码的数量,并认为也许可以将响应模型作为端点的一部分来传递。

我的想法不是要使用因响应而有所不同的多个功能,而是可以调用我的网络层并根据路径进行设置。

我看到的当前错误是

  

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 :(得分:0)

在您的EndpointProtocol中添加一个associatedtype。然后像这样在IdentityEndpoint中指定它

protocol EndpointProtocol {
    associatedtype ResponseType

    ...
}

extension IdentityEndpoint: EndpointProtocol {
    typealias ResponseType = OAuthToken

    ...
}

现在您将可以写

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