Yandex Money 核心API库
概念
ApiSession
ApiSession
- 在 URLSession
之上的抽象层。 ApiSession
可以执行 ApiMethod
,可以取消所有正在运行中的请求。
要创建一个新的会话,需要创建 HostProvider
。 HostProvider
是一个接口,它能够根据键返回所需的主机。您可以把它看作一个字典,它能存储和检索主机名和值。
如果您正在测试环境中工作,您可以实现一个单独的
HostProvider
,该HostProvider
将会话的所有方法都导向测试环境,而不是生产环境。根据不同的环境,您只需在ApiSession
中 внедр不同版本的HostProvider
。
ApiMethod
ApiMethod
- 您的请求服务器的模型。每个 ApiMethod
都应该有自己的 Response
。==> 对于每个方法,可以描述服务器的一个唯一响应。
要实现ApiMethod
,您需要定义以下属性
hostProviderKey
- 该键用于由HostProvider
返回用于请求的host。httpMethod
- HTTP协议的方法(例如:.post
,.get
)。parametersEncoding
- 参数Encoder
。在CoreApi中,已有两个现成的Encoder
:JsonParametersEncoder
和QueryParametersEncoder
。
然后是方法
urlInfo(from hostProvider: HostProvider) throws -> URLInfo
- 该方法使用HostProvider
返回请求的URL。
建议在此方法中使用
hostProviderKey
属性,除非url是从外部传入的。
可选
headers
- 存储请求头的结构。
ApiMethod
需要实现Encodable
协议。在encode(to:)
方法中编码的内容将被发送到parametersEncoding
,并嵌入到请求的相应部分。
例如,如果您使用
QueryParametersEncoder
,则数据将出现在查询参数字符串中,而使用JsonParametersEncoder
时,它们将作为json格式嵌入到请求体中。
ApiResponse
ApiResponse
- 服务器响应模型。为了方便,建议继承此接口并创建自己的。
以下是一个示例。您可能有几个不同的api,每个api都返回特定的错误或按照其自己的格式返回错误。最终,对于每个Api,您都可以创建一个继承自
ApiResponse
的协议,并在其中定义自己的process
和makeResponse
方法。这样,您就不需要在每个模型中实现相同错误处理代码了。下面是使用ApiResponse
实现自己协议的一个示例。
public protocol WalletApiResponse: ApiResponse {}
extension WalletApiResponse {
public static func process(response: HTTPURLResponse?, data: Data?, error: Error?) -> Result<Self> {
var result: Result<Self>
if let response = response,
let data = data,
let error = WalletAuthApiError.makeResponse(response: response, data: data) {
result = .left(error)
} else if let response = response,
let data = data,
let error = self.makeSpecificError(response: response, data: data) {
result = .left(error)
} else if let response = response,
let data = data,
let serializedData = self.makeResponse(response: response, data: data) {
result = .right(serializedData)
} else if let error = error {
result = .left(error)
} else {
result = .left(WalletApiError.mappingError)
}
return result
}
}
在这个例子中,我们首先
- 尝试解析通用的api错误。
- 尝试解析特定于api方法的错误。
- 尝试解析服务器的响应。
- 如果发生
ApiSession
错误,则返回该错误。 - 如果无法解析任何内容,并且没有发生
ApiSession
层的错误,则返回映射错误。
为了不在每个错误和每个响应中实现makeResponse(response:data:)
方法,有几种辅助协议可供使用
JsonApiResponse
- 尝试使用Decodable
协议从data
中获取模型。TextApiResponse
- 将data
转换为utf8字符串并调用初始化器init?(text:)
。如果response
包含textEncodingName
,则使用服务器的响应编码而不是utf8。
您也可以创建自己的协议并实现其makeResponse(response:data:)
方法,如果标准协议不适合您。例如,如果您需要解析XML。
示例
/// Модель ответа от сервера.
public struct PaymentMethod {
public let type: PaymentMethodType
public let id: String
public init(type: PaymentMethodType,
id: String) {
self.type = type
self.id = id
}
/// Модель запроса на сервер.
public struct Method {
public let oauthToken: String
public init(oauthToken: String) {
self.oauthToken = oauthToken
}
}
}
// Парсим ответ от сервера.
extension PaymentMethod: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(PaymentMethodType.self, forKey: .type)
let id = try container.decode(String.self, forKey: .id)
self.init(
type: type,
id: id
)
}
private enum CodingKeys: String, CodingKey {
case type
case id
}
}
// Реализовываем ApiResponse для модели ответа.
extension PaymentMethod: PaymentsApiResponse, JsonApiResponse {}
// Реализовываем ApiMethod для модели запроса.
extension PaymentMethod.Method: ApiMethod {
public typealias Response = PaymentMethod
public var hostProviderKey: String {
return Constants.paymentsApiMethodsKey
}
public var httpMethod: HTTPMethod {
return .get
}
public var parametersEncoding: ParametersEncoding {
return QueryParametersEncoder()
}
public var headers: Headers {
let headers = Headers([
AuthorizationConstants.authorization: AuthorizationConstants.basicAuthorizationPrefix + oauthToken,
])
return headers
}
public func urlInfo(from hostProvider: HostProvider) throws -> URLInfo {
return .components(host: try hostProvider.host(for: hostProviderKey),
path: "/payment_method")
}
}
// Перегружаем стандартные `encode(to:)` и `init(from:)` чтобы поле `oauthToken` не попало в
// query параметры.
extension PaymentMethod.Method: Encodable, Decodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.init(oauthToken: "")
}
private enum CodingKeys: String, CodingKey {}
}