用幻像类型解码泛型
Decoding generics with phantom types
我正在尝试定义一个 Currency
类型,以防止混淆数字和字母货币代码:
public protocol ISO4217Type {}
public enum ISO4217Alpha: ISO4217Type {}
public enum ISO4217Num: ISO4217Type {}
public struct Currency<T: ISO4217Type> {
public let value: String
}
extension Currency where T == ISO4217Alpha {
public init?(value: String) {
let isLetter = CharacterSet.letters.contains
guard value.unicodeScalars.all(isLetter) else { return nil }
self.value = value
}
}
extension Currency where T == ISO4217Num {
public init?(value: String) {
let isDigit = CharacterSet.decimalDigits.contains
guard value.unicodeScalars.all(isDigit) else { return nil }
self.value = value
}
}
效果很好。现在,是否可以添加 Codable
一致性,当尝试使用错误的有效负载解码货币代码时会抛出解码错误? (例如,将 USD
解码为数字货币代码。)
关键的启示是可以在幻像类型上使用静态函数自定义行为:
public protocol ISO4217Type {
static func isValidCode(_ code: String) -> Bool
}
public enum ISO4217Alpha: ISO4217Type {
public static func isValidCode(_ code: String) -> Bool {
let isLetter = CharacterSet.letters.contains
return code.unicodeScalars.all(isLetter)
}
}
public enum ISO4217Num: ISO4217Type {
public static func isValidCode(_ code: String) -> Bool {
let isDigit = CharacterSet.decimalDigits.contains
return code.unicodeScalars.all(isDigit)
}
}
public struct Currency<T: ISO4217Type> {
public let value: String
private init(uncheckedValue value: String) {
self.value = value
}
public init?(value: String) {
guard T.isValidCode(value) else { return nil }
self.value = value
}
}
extension Currency: Codable {
public func encode(to encoder: Encoder) throws {
var c = encoder.singleValueContainer()
try c.encode(value)
}
public init(from decoder: Decoder) throws {
let c = try decoder.singleValueContainer()
let value = try c.decode(String.self)
guard T.isValidCode(value) else {
throw DecodingError.dataCorruptedError(in: c,
debugDescription: "Invalid \(type(of: T.self)) code")
}
self.init(uncheckedValue: value)
}
}
我正在尝试定义一个 Currency
类型,以防止混淆数字和字母货币代码:
public protocol ISO4217Type {}
public enum ISO4217Alpha: ISO4217Type {}
public enum ISO4217Num: ISO4217Type {}
public struct Currency<T: ISO4217Type> {
public let value: String
}
extension Currency where T == ISO4217Alpha {
public init?(value: String) {
let isLetter = CharacterSet.letters.contains
guard value.unicodeScalars.all(isLetter) else { return nil }
self.value = value
}
}
extension Currency where T == ISO4217Num {
public init?(value: String) {
let isDigit = CharacterSet.decimalDigits.contains
guard value.unicodeScalars.all(isDigit) else { return nil }
self.value = value
}
}
效果很好。现在,是否可以添加 Codable
一致性,当尝试使用错误的有效负载解码货币代码时会抛出解码错误? (例如,将 USD
解码为数字货币代码。)
关键的启示是可以在幻像类型上使用静态函数自定义行为:
public protocol ISO4217Type {
static func isValidCode(_ code: String) -> Bool
}
public enum ISO4217Alpha: ISO4217Type {
public static func isValidCode(_ code: String) -> Bool {
let isLetter = CharacterSet.letters.contains
return code.unicodeScalars.all(isLetter)
}
}
public enum ISO4217Num: ISO4217Type {
public static func isValidCode(_ code: String) -> Bool {
let isDigit = CharacterSet.decimalDigits.contains
return code.unicodeScalars.all(isDigit)
}
}
public struct Currency<T: ISO4217Type> {
public let value: String
private init(uncheckedValue value: String) {
self.value = value
}
public init?(value: String) {
guard T.isValidCode(value) else { return nil }
self.value = value
}
}
extension Currency: Codable {
public func encode(to encoder: Encoder) throws {
var c = encoder.singleValueContainer()
try c.encode(value)
}
public init(from decoder: Decoder) throws {
let c = try decoder.singleValueContainer()
let value = try c.decode(String.self)
guard T.isValidCode(value) else {
throw DecodingError.dataCorruptedError(in: c,
debugDescription: "Invalid \(type(of: T.self)) code")
}
self.init(uncheckedValue: value)
}
}