2017-11-24 4 views
0

Ich versuche, einen Currency Typen zu definieren, die von numerischen und alphabetischen Währungscodes Verwechslungen verhindern würden:Dekodierung Generika mit Phantomtypen

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 
    } 
} 

Dies funktioniert gut. Ist es jetzt möglich, eine Codable Konformität hinzuzufügen, die einen Dekodierungsfehler auslösen würde, wenn versucht wird, einen Währungscode mit der falschen Nutzlast zu dekodieren? (Zum Beispiel Decodierung USD als numerischer Währungscode.)

Antwort

0

Der Schlüssel Offenbarung war, dass es möglich ist, das Verhalten mit statischen Funktionen auf dem Phantomtyp anpassen:

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) 
    } 
}