FirebaseAnalytics イベント名のバリデーション


FirebaseAnalyticsのイベント名は使用できる文字に制約があるが、無効な文字列が使われてても気づかないので、Unit Testで検知するようにした。
FirebaseAnalyiticsの導入に関しては記載しない。

  • Xcode11.1
  • Swift5

イベント名はenumでどこかに纏めて記述しておく。
送信箇所が多いと結構長いリストになる。

enum EventName: String, CaseIterable {
    case tapHoge = "tap_hoge"
    case tapFuga = "tap_fuga"
    ...
}

バリデーション

制約はframework内の FIRAnalytics に以下の記述がある。

Should contain 1 to 40 alphanumeric characters or underscores.
The name must start with an alphabetic character.
Some event names are reserved. See FIREventNames.h for the list of reserved event names.
The "firebase_", "google_", and "ga_" prefixes are reserved and should not be used.
  • 1 ~ 40文字の英数字か_(アンダースコア)で構成されていないとならない
  • アルファベットで始まらなければならない
  • いくつかのイベント名は FIREventNames.h で定義されている
    • 被っても問題なさそうなのでスルー
  • "firebase_", "google_", "ga_" から始まってはいけない

テストコード

上記条件をそのままテストコードにした。

func testLogEventNames() {
    let analyticsReservedPrefix = ["firebase_", "google_", "ga_"]
    EventName.allCases.forEach { (name) in
        // !length.isEmpty && length <= 40
        XCTAssertTrue(!name.rawValue.isEmpty && name.rawValue.count <= 40, "Length error: \(name.rawValue)")
        // alphanumerics or "_"
        XCTAssertTrue(name.rawValue.isAlphanumericOrUnderscore, "String error: \(name.rawValue)")
        // has prefix alphabetic
        XCTAssertTrue(name.rawValue.hasPrefixLetter, "Prefix error: \(name.rawValue)")
        // not has prefix "firebase_", "google_", and "ga_"
        XCTAssertTrue(analyticsReservedPrefix.filter({ name.rawValue.hasPrefix($0) }).isEmpty, "Prefix error: \(name.rawValue)")
    }
}
private extension String {
    var isAlphanumericOrUnderscore: Bool {
        // _でCharacterSetを用意
        let underscoreCharacterSet = CharacterSet(charactersIn: "_")
        // 英数字と_のCharacterSetを結合
        let characterSet = CharacterSet.alphanumerics.union(underscoreCharacterSet)
        // 空じゃない、且つ用意したCharacterSet以外が含まれない
        return !isEmpty && rangeOfCharacter(from: characterSet.inverted) == nil
    }

    var hasPrefixLetter: Bool {
        let prefix = self[startIndex...index(startIndex, offsetBy: 1)]
        // 空じゃない、且つアルファベット以外が先頭文字ではない
        return !isEmpty && prefix.rangeOfCharacter(from: CharacterSet.letters.inverted) == nil
    }
}

Unit TestはCIでプルリク時点で走るようになってるので
新規にenumにイベント名を追加したプルリク生成時にバリデーションされるようになった。

FirebaseAnalyticsのイベント名の制約仕様に変更があった場合に追随できないのでそこはご注意。