Files
ShadowsocksX-NG/ShadowsocksX-NG2/Domain/Subscription/SubscriptionRefreshFailure.swift
T

97 lines
2.9 KiB
Swift

import Foundation
/// 持久化的订阅失败白名单,不携带 URL、秘密或任意平台错误描述。
enum SubscriptionRefreshFailure: Error, Codable, Equatable, Sendable {
enum TransportCategory: String, Codable, Equatable, Sendable {
case timedOut, tls, connection, unknown
}
enum ContentTypeCategory: String, Codable, Equatable, Sendable {
case missing, unsupported
}
enum RecordField: String, Codable, Equatable, Sendable {
case address, port, method, password, identity
}
case invalidURL
case unsupportedScheme
case insecureRedirect
case transport(TransportCategory)
case httpStatus(code: Int)
case contentType(ContentTypeCategory)
case decodingFailure
case unsupportedSchemaVersion
case missingServers
case recordValidation(index: Int, field: RecordField)
case duplicateIdentity
case commit
case unknown
}
extension SubscriptionRefreshFailure {
/// The only conversion point for transport failures entering durable state.
static func from(fetchError: SubscriptionFetchError) -> Self {
switch fetchError {
case .invalidURL:
.invalidURL
case .unsupportedScheme:
.unsupportedScheme
case .insecureRedirect:
.insecureRedirect
case .transport(let detail):
.transport(transportCategory(for: detail))
case .httpStatus(let code):
.httpStatus(code: code)
case .contentType(let received):
.contentType(received == nil ? .missing : .unsupported)
}
}
/// The only conversion point for parser failures entering durable state.
static func from(parseError: SubscriptionParseError) -> Self {
switch parseError {
case .decodingFailure:
.decodingFailure
case .unsupportedSchemaVersion:
.unsupportedSchemaVersion
case .missingServers:
.missingServers
case .recordValidation(let index, let reason):
.recordValidation(index: index, field: recordField(for: reason))
case .duplicateServerID:
.duplicateIdentity
}
}
private static func transportCategory(for detail: String) -> TransportCategory {
let normalized = detail.lowercased()
if normalized.contains("timedout") || normalized.contains("timeout") {
return .timedOut
}
if normalized.contains("tls") || normalized.contains("secure") {
return .tls
}
if normalized.contains("connection") || normalized.contains("network")
|| normalized.contains("cannotconnect")
{
return .connection
}
return .unknown
}
private static func recordField(for reason: String) -> RecordField {
switch reason {
case let value where value.contains("server_port"):
.port
case let value where value.contains("server"):
.address
case let value where value.contains("method"):
.method
case let value where value.contains("password"):
.password
case let value where value.contains("id"):
.identity
default:
.identity
}
}
}