Firebase-iOS-SDKなどで使われるAuthErrorCodeについて
はじめに
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
の整数値が同じならそのエラーになる
- っていうか
- domainを無視してcodeからAuthErrorCode.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とは何か
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.Code
のenum
だ
-
- できるならそれは
ということになり、
as?
でキャストできればハンドリングしたいエラーを指定することになります(キャストするという表現が正しいかは自信がないですが)。
Appleのドキュメント
Objective-CのNS_ERROR_ENUM
をSwiftコードとして扱うことのドキュメントはこれっぽいです
Discussion