コード化可能: 不正な列挙値の処理

May 09 2023
コードベースの保護: 予期しない列挙値の処理 アプリがどこからともなくクラッシュし始めた状況に直面したことはありませんか?調査の結果、バックエンドがコードの列挙内に存在しない特定の列挙ベースのフィールドに値を提供したことが判明しましたか? 次の例を考えてみましょう: Ohhh I love some Tea しかし、JSON データの favouriteBeverage を Tea から Juice (列挙型 Beverage 内に存在しない値) に変更するとどうなるでしょうか? メッセージの「無効な文字列値 Juice から Beverage を初期化できません」という部分に注目してください。これが Playground コードではなく実際のアプリであった場合、アプリはクラッシュしていたでしょう。では、バックエンドが応答で無効な値を送信した場合でも、アプリが完全にクラッシュするのではなく、それを処理できるようにするにはどうすればよいでしょうか? 方法の 1 つで、私の個人的なお気に入りは次のとおりです。

コードベースの保護: 予期しない列挙値の処理

UnsplashのKostiantyn Liによる写真

アプリがどこからともなくクラッシュし始めたという状況に直面したことはありませんか? 調査の結果、バックエンドがコードの列挙型内に存在しない特定の列挙型フィールドに値を提供したことが判明しましたか?

次の例を検討してください。

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

ああ、私はお茶が大好きです

しかし、favouriteBeverageJSON データをTeaからJuice (列挙型Beverage内に存在しない値) に変更するとどうなるでしょうか?

let jsonString = """
{
    "name": "Shubham Bakshi",
    "favouriteBeverage": "Juice"
}
""".data(using: .utf8)!

メッセージの部分に注目してください: 「無効な文字列値 Juice から Beverage を初期化できません」

プレイグラウンド コードではなく実際のアプリであった場合、アプリはクラッシュしていたでしょう。では、バックエンドが応答で無効な値を送信した場合でも、アプリが完全にクラッシュするのではなく、それを処理できるようにするにはどうすればよいでしょうか?

方法の 1 つであり、私の個人的なお気に入り: 正しくない Enum フォールバック メカニズム。

メカニズムと言っていますが、多かれ少なかれすべてを処理するプロトコルです。では、いざ…。

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

待って!それは何ですか?

バックエンドが として何を送信してもfavouriteBeverage、それを処理したり、飲んだりすることができます。

したがって、完全なコードは次のようになります。

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

それだけです、皆さん!ハッピーコーディング!

LinkedIn で私とつながるか、他のチャネルで連絡を取ることができます