🫥

Firebase-iOS-SDKなどで使われるAuthErrorCodeについて

2023/06/03に公開

はじめに

Firebase-iOS-SDKのフレームワークFirebaseAuthを使ったログインのサンプルを見ると、エラー型のAuthErrorCodeについてとても不思議なエラーハンドリングのコードをたくさん目にしまして、それは間違ってるんじゃないかなというのを書いておきます。

ほんとにたくさんこの変な書き方を使った記事が多い。

最初に結論

  • AuthErrorCodeにキャストできたらそれはAuthErrorCodeだよ
    • 準拠してるかどうかチェックするだけでいいじゃないの
    • AuthErrorCode.Code(rawValue: error.code)なんてわけわからんよ
  • そもそもAuthErrorCodeっていう命名がおかしいのも混乱する。AuthErrorのほうがいい

経緯

Firebase-ios-SDKを使って認証認可のコードを書こうとサンプルを探すと、だいたいこんな感じになっています。

Auth.auth().signIn(withEmail: email, password: password) {result, error in
    if let error = error {
        let err = error as NSError
        if let authErrorCode = AuthErrorCode.Code(rawValue: err.code) {

            switch authErrorCode {

            case .wrongPassword:
                print("wrong password")
            case .invalidEmail:
                print("invalid email")
            // ... other cases
            @unknown default:
                print("unknown error")
            }            
        }

        return
    }
    ...
}

めちゃくちゃわけがわからないんですよね、これ。

前提としてsignInWithEmailは次のような定義で、エラーはNSError型です。

- (void)signInWithEmail:(NSString *)email
               password:(NSString *)password
             completion:
 	         (nullable void (^)(
		     FIRAuthDataResult *_Nullable authResult,
                     NSError *_Nullable error)
		 )completion;

つまり、エラーはAuthErrorCode型を受け取るわけじゃない。

そして何がわけわからんのかというと

  • if let authErrorCode = AuthErrorCode.Code(rawValue: err.code)
    • domainを無視してcodeからAuthErrorCode.Codeを作成?
      • っていうかCode.Codeに2重の感じがもう意味がわからん
        • それは変な命名のせいとして一旦保留
      • これができるのはdomainがエラーの早出元で固定されていることが約束されてないと、たまたまcodeの整数値が同じならそのエラーになる

ほんとはどう書けるの?

AuthErrorCodeはErrorに準拠しているのでコールバックのerrorがそれにキャストできるかどうかを見れば良くて、キャストできたらあとはcodeを検証すれば良いはずです。

Auth.auth().signIn(withEmail: email, password: password) {result, error in
    if ler error = error as? AuthErrorCode {
        switch error.code {
            case .wrongPassword:
                print("wrong password")
            case .invalidEmail:
                print("invalid email")
            // ... other cases
            @unknown default:
                print("unknown error")
            }            
        }
        return
    } else if let error {
        // AuthErrorCodeではない想定外のエラー    
    }
    ...
}

なんでこんなことに?

AuthErrorCodeとは何か

https://github.com/firebase/firebase-ios-sdk/blob/9099f7fba6d506f95a6fbbc402c89a4165496e8a/FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthErrors.h#L84-L437

typedef NS_ERROR_ENUM(FIRAuthErrorDomain, FIRAuthErrorCode){}というのがポイントで、これは次のようなコードがSwiftとして自動で生成されます。

public struct AuthErrorCode : CustomNSError, Hashable, Error {
    public init(_nsError: NSError)
    public static var errorDomain: String { get }
    /**
        @brief Error codes used by Firebase Auth.
     */
    public enum Code : Int, @unchecked Sendable, Equatable {
        /**
            @brief Error codes used by Firebase Auth.
         */
        public typealias _ErrorType = AuthErrorCode
        /** Indicates a validation error with the custom token.
         */
...

ざっくりいうと、domainとcodeをObjective-Cで整理したら、structとして使えるようになってる。codeはenumにもなってるということ。

CustomNSError

これのもう一つ重要なのがCustomNSErrorに準拠していることでしょう。

/// Describes an error type that specifically provides a domain, code,
/// and user-info dictionary.
public protocol CustomNSError : Error {

    /// The domain of the error.
    static var errorDomain: String { get }

    /// The error code within the given domain.
    var errorCode: Int { get }

    /// The user-info dictionary.
    var errorUserInfo: [String : Any] { get }
}

CustomNSErrorはプロトコルで指定のプロパティがあるかどうかです。これはNSError型のプロパティとプロパティ名においては同様なはずです。じゃあなんでNSError型使わないの?となるとNSError型はクラスだからです。protocolとしてCustomNSErrorがあることで、AuthErrorCodeが準拠させられます。

その上で

  • NSError型のオブジェクトをAuthErrorCodeにキャストできるか
    • できるならそれは
      • CustomNSErrorも満たしている(あのプロパティがある)
      • domainはFIRAuthErrorDomain
      • codeはAuthErrorCode.Codeenum

ということになり、
as?でキャストできればハンドリングしたいエラーを指定することになります(キャストするという表現が正しいかは自信がないですが)。

Appleのドキュメント

Objective-CのNS_ERROR_ENUMをSwiftコードとして扱うことのドキュメントはこれっぽいです
https://developer.apple.com/documentation/swift/handling-cocoa-errors-in-swift

Discussion