如何使用 Swift 泛型处理成功和错误 API 响应?
Posted
技术标签:
【中文标题】如何使用 Swift 泛型处理成功和错误 API 响应?【英文标题】:How to handle success and error API responses with Swift Generics? 【发布时间】:2021-11-03 00:28:35 【问题描述】:我正在尝试编写一个简单的函数来处理返回 JWT 令牌的身份验证 POST
请求。
我的LoopBack 4 API 将令牌作为 JSON 数据包返回,格式如下:
"token": "my.jwt.token"
如果发生错误,则返回以下内容:
"error":
"statusCode": 401,
"name": "UnauthorizedError",
"message": "Invalid email or password."
如您所见,这些类型完全不同,它们没有任何共同的属性。
我定义了以下 Swift 结构来表示它们:
// Success
struct Token: Decodable
let token: String
// Error
struct TokenError: Decodable
let error: ApiError
struct ApiError: Decodable
let statusCode: Int
let name: String
let message: String
返回 Swift 泛型的身份验证请求的签名:
@available(ios 15.0.0, *)
func requestToken<T: Decodable>(_ user: String, _ password: String) async throws -> T
我一直在尝试对这个函数进行单元测试,但 Swift 要求我预先声明结果的类型:
let result: Token = try await requestToken(login, password)
这对于快乐路径非常有效,但如果身份验证不成功,则会引发 The data couldn’t be read because it is missing.
错误。我当然可以抓住它,但我无法将结果转换为我的 TokenError
类型以访问其属性。
我在 *** 上遇到了一些线程,一般建议是通过通用协议表示成功和错误类型,但由于与 Decodable
协议的冲突,我没有运气。响应类型已经符合。
所以问题是我的requestToken
函数返回的result
变量是否可以同时使用成功和错误。
【问题讨论】:
【参考方案1】:最自然的方式,IMO 是抛出 ApiErrors,这样就可以像处理其他错误一样处理它们。看起来像这样:
将 ApiError 标记为错误类型:
extension ApiError: Error
现在你可以直接解码 Token,如果 API 错误会抛出 ApiError,如果数据损坏会抛出 DecodingError。 (注意在第一次解码中使用try?
,在else
解码中使用try
。这样如果数据根本无法解码就会抛出。)
extension Token: Decodable
enum CodingKeys: CodingKey
case token
init(from decoder: Decoder) throws
if let container = try? decoder.container(keyedBy: CodingKeys.self),
let token = try? container.decode(String.self, forKey: .token)
self.init(token: token)
else
throw try TokenError(from: decoder).error
// Usage if you want to handle ApiErrors specially
do
try JSONDecoder().decode(Token.self, from: data)
catch let error as ApiError
// Handle ApiErrors
catch let error
// Handle other errors
另一种方法是将 ApiErrors 与其他错误分开,在这种情况下,requestToken
可以通过三种可能的方式返回。它可以返回一个 Token,也可以返回一个 TokenError,也可以抛出一个解析错误。抛出错误由throws
处理。 Token/TokenError 需要一个“或”类型,它是一个枚举。这可以通过Result 完成,但这可能有点令人困惑,因为例程也会抛出。相反,我会很明确。
enum TokenRequestResult
case token(Token)
case error(ApiError)
现在您可以通过首先尝试将其解码为 Token 来使其成为可解码,如果失败,请尝试将其解码为 TokenError 并从中提取 ApiError:
extension TokenRequestResult: Decodable
init(from decoder: Decoder) throws
let container = try decoder.singleValueContainer()
if let token = try?
container.decode(Token.self)
self = .token(token)
else
self = .error(try container.decode(TokenError.self).error)
要使用这个,你只需要切换:
let result = try JSONDecoder().decode(TokenRequestResult.self, from: token)
switch result
case .token(let token): // use token
case .error(let error): // use error
【讨论】:
非常感谢您快速详细的回复,Rob。我真的很喜欢你的第一个建议,因为我认为它比另一个更优雅。尽管如此,我还是没有运气,因为代码在Token
扩展中的if
语句上中断(它抛出EXC_BAD_ACCESS
)。我在那条线上打了一个断点,当我尝试跨过它时,它在一个网络线程下创建了另一个“SingleValueDecodingContainer.decode 的调度重击”条目,并且一遍又一遍地发生。我正在测试有效和无效请求,这两种情况都会发生。
很抱歉。我创建了一个我应该注意到的无限循环。我测试这个太快了。代码已更新。以上是关于如何使用 Swift 泛型处理成功和错误 API 响应?的主要内容,如果未能解决你的问题,请参考以下文章
子类化 NSObject 和使用泛型时 Swift 编译错误
如何在 swift 4 中处理 HTTP 加载失败(错误代码:-1009 [1:50])?