Codierbar: Umgang mit falschen Enum-Werten
Schutz Ihrer Codebasis: Umgang mit unerwarteten Enum-Werten
Haben Sie jemals eine Situation erlebt, in der Ihre App aus dem Nichts abstürzte, was bei einer Untersuchung ergab, dass das Backend einen Wert für ein bestimmtes Enum- basiertes Feld bereitgestellt hat, das nicht innerhalb des Enums im Code vorhanden ist?
Betrachten Sie das folgende Beispiel:
import Foundation
struct Person: Codable {
let name: String
let favouriteBeverage: Beverage
}
enum Beverage: String, Codable {
case Tea
case Coffee
}
let jsonString = """
{
"name": "Shubham Bakshi",
"favouriteBeverage": "Tea"
}
""".data(using: .utf8)!
let decodedJSON = try! JSONDecoder().decode(Person.self, from: jsonString)
switch decodedJSON.favouriteBeverage {
case .Tea:
print("Ohhh I love some Tea")
case .Coffee:
print("Coffee is no Tea but meh, I'll take it")
}
Ohhh, ich liebe etwas Tee
favouriteBeverage
Aber was ist, wenn wir die JSON-Daten von Tea in Juice ändern (ein Wert, der in der Aufzählung Beverage nicht vorhanden ist )?
let jsonString = """
{
"name": "Shubham Bakshi",
"favouriteBeverage": "Juice"
}
""".data(using: .utf8)!
Konzentrieren Sie sich auf den Teil der Meldung: „Beverage kann nicht mit ungültigem String-Wert Juice initialisiert werden“
Wenn es sich um eine echte App statt um einen Playground-Code gehandelt hätte, wäre die App abgestürzt. Wie sorgen wir also dafür, dass selbst wenn das Backend einen ungültigen Wert in der Antwort sendet, unsere App in der Lage sein sollte, damit umzugehen, anstatt direkt abzustürzen ?
Einer der Wege und mein persönlicher Favorit: Falscher Enum-Fallback-Mechanismus.
Es sagt Mechanismus, ist aber mehr oder weniger ein Protokoll, das sich um alles kümmert. Auf geht's ….
/// This is useful in case the backend specifies some value that does not
/// exist inside the enum as in such cases the app crashes since it fails to
/// parse the enum.
///
/// This ensures that in case an invalid value is specified, the app will
/// not crash but rather resort to a value specified for
/// `fallbackForIncorrectEnumValue`
///
protocol IncorrectEnumFallbackMechanism: RawRepresentable, Codable {
/// Fallback value in case the backend returns an incorrect value for
/// the enum
static var fallbackForIncorrectEnumValue: Self { get }
}
extension IncorrectEnumFallbackMechanism where RawValue: Codable {
init(from decoder: Decoder) throws {
// Fetching the value specified inside the JSON
let value = try decoder.singleValueContainer().decode(RawValue.self)
// Creating the instance based on the provided value or falling back
// to a default value in case the provided value for creating is
// invalid
self = Self(rawValue: value) ?? Self.fallbackForIncorrectEnumValue
}
/// Since `RawRepresentable` declares a `encode(coder:)` by default so we
/// don't need to implement that explicitly
}
enum Beverage: String, IncorrectEnumFallbackMechanism {
static var fallbackForIncorrectEnumValue: Beverage = .SomeWeirdBeverage
case Tea
case Coffee
case SomeWeirdBeverage
}
Warten! Was ist das?
Egal, was das Backend uns als sendet favouriteBeverage
, wir können damit umgehen oder es trinken, was auch immer!
Unser vollständiger Code sieht nun also so aus:
import Foundation
/// This is useful in case the backend specifies some value that does not
/// exist inside the enum as in such cases the app crashes since it fails to
/// parse the enum.
///
/// This ensures that in case an invalid value is specified, the app will
/// not crash but rather resort to a value specified for
/// `fallbackForIncorrectEnumValue`
///
protocol IncorrectEnumFallbackMechanism: RawRepresentable, Codable {
/// Fallback value in case the backend returns an incorrect value for
/// the enum
static var fallbackForIncorrectEnumValue: Self { get }
}
extension IncorrectEnumFallbackMechanism where RawValue: Codable {
init(from decoder: Decoder) throws {
// Fetching the value specified inside the JSON
let value = try decoder.singleValueContainer().decode(RawValue.self)
// Creating the instance based on the provided value or falling back
// to a default value in case the provided value for creating is
// invalid
self = Self(rawValue: value) ?? Self.fallbackForIncorrectEnumValue
}
/// Since `RawRepresentable` declares a `encode(coder:)` by default so we
/// don't need to implement that explicitly
}
struct Person: Codable {
let name: String
let favouriteBeverage: Beverage
}
enum Beverage: String, IncorrectEnumFallbackMechanism {
static var fallbackForIncorrectEnumValue: Beverage = .SomeWeirdBeverage
case Tea
case Coffee
case SomeWeirdBeverage
}
let jsonString = """
{
"name": "Shubham Bakshi",
"favouriteBeverage": "Juice"
}
""".data(using: .utf8)!
let decodedJSON = try! JSONDecoder().decode(Person.self, from: jsonString)
switch decodedJSON.favouriteBeverage {
case .Tea:
print("Ohhh I love some Tea")
case .Coffee:
print("Coffee is no Tea but meh, I'll take it")
case .SomeWeirdBeverage:
print("Wait! What is that?")
}
Das ist es, Leute! Viel Spaß beim Codieren!
Sie können sich mit mir auf LinkedIn verbinden oder über andere Kanäle mit mir in Kontakt treten