RustからCloud FireStoreにアクセスする

# まずは環境
Rust 1.57
firestore-db-and-auth 0.6.1
serde 1.0

# FireBase最高だな、兄者（´<_｀　 ）

Freeプランだけでなく、中間プランが固定料金、これがとても素敵なわけです。

昔FireBaseを使おうと思って結局ちょっと触って放置していたのですが、最近見直してみてとても良いサービスだと改めて思ったので、今後はFireBaseを使っていきたいなぁ、と。

# めんどくせぇ、めんどくせぇ。息をするのもめんどくせぇ

皆さーん？　データ永続化してますかー！？

( 厂˙ω˙ )厂うぇーい

うぇーい乁( ˙ω˙ 乁)

DBの管理、めんどくさいですよね。もうクラウドでいいじゃないか、そう思うことありますよね？

仕事をしてたらハードウェア障害もそれなりに発生するし、OSやストレージミドルウェアの更新ともなれば毎回半泣きになるレベルのトラブルがでるわけで、そういったトラブル対応ばっかりしていると、もう、管理したくなーい(ノ｀Д´)ノ彡┻━┻ってなるわけです。

めんどくせぇ、めんどくせぇ……

というわけで、プライベートではめんどくさいことをしたくないのです。

めんどくせぇ、めんどくせぇ……

# 昔はDBを渡り歩く冒険者だったのだが、膝に矢をうけてしまってな

サーバで遊び始めた頃：　MySQL
MySQLに飽きてきた： PostgreSQL
Vaccumeメンドクセ：　MySQL
MySQL買われたってよ：　MariaDB
お仕事でAWS： AmazonRDB
お仕事でBlueMix(現IBM Cloud)： MySQL-CE
自営業、スモールスタート＆スケールだ：　MariaDB+MongoDB
MongoDBにトランザクション入ったぞ：　MongoDB
--- 膝に矢を受ける ---
めんどくせぇ、めんどくせぇ：　Redis
息をするのもめんどくせぇ：　FireBase

昔は普通にRDBを使ってましたが結局管理が面倒になって、よく考えたら自分の規模程度ならRedisあたりをストレージとして使ったら十分じゃないか、と思ったのもつかの間、FireBaseタダだしもうこれでいいんじゃね？っていうのが最近の私です。

ベンダーロックインを避けるため、FireBaseがメインですがRedis/Memcachedは引き続きサブで使う感じにしています。

初手はRedisの方が楽ですが、運用にかかるコストはちょっとしたコストでも積み重なって最終的に大きなコストになってしまいます。開発に多少コストがかかっても、運用コストが下がるならそちらを選びたいですね。

# firestore-db-and-authの使い方

FireBaseをageたところで本題に入ります。

FireBase自体は普通にWeb上でも本屋さんにも情報がある&まだFireBase初心者なのでここではあまり触れません。仕事で使うなら、セキュリティ周りはしっかり理解してからご利用ください。

まぁこれはFireBaseに限った話ではありませんが。

## FireStoreを用意する

プロジェクトをtest1って名前で作って(test1-1b250というIDになった)、デバッグモードでFireStoreのDB作って、sample1っていうコレクションを作って、適当にドキュメント作って、適当な値を突っ込んだ、というところまでやった状態から開始です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/6890a7ac-f2f9-9b56-8218-3c017941c4e7.png)

アクセスできるか試してみます。デバッグモードで作っていれば認証が一定期間不要な状態のセキュリティルールになっているので、普通にブラウザからアクセスできます。

`https://firestore.googleapis.com/v1/projects/{プロジェクトID}/databases/(default)/documents/{コレクション名}`

今回プロジェクトIDはtest1-1b250で、コレクション名はsample1なので、次のURLでアクセスできます。

`https://firestore.googleapis.com/v1/projects/test1-1b250/databases/(default)/documents/sample1`

## Rustのプロジェクトを用意する

とりあえず公式置いておきますね
https://crates.io/crates/firestore-db-and-auth

※ドキュメントにRocketをsupportしているよって書いてますね(\*´ω｀\*)

### tomlの修正

cargo.tomlに次のdependenciesを追加します。

```toml
[dependencies]
firestore-db-and-auth = "0.6.1"
serde = { version = "1.0", features = ["derive"] }

```
### useで取り込み

基本的にはプログラムを書きながらアレ追加しよう、コレ追加しようってやってますが、今回最終的に次のような形になりました。

```rust
use firestore_db_and_auth::{documents, documents::List, Credentials, ServiceSession};
use serde::{Serialize, Deserialize};
```

認証用
`firestore_db_and_auth::{Credentials, ServiceSession}`

データ取得用
`firestore_db_and_auth::{documents, documents::List}`
`serde::{Serialize, Deserialize}`

### データ取得用構造体の用意

すでに作ってあるFireStoreのドキュメントの受け取るフィールドを持った構造体を用意します。データが必ずあるのであれば、そのまま変数名と型を、データがない場合があるなら型にOptionを付けておきます。

Optionを付けていないフィールドにデータが存在しない場合、そのドキュメントごと取得に失敗します(ResultでErrorが返る)。

```rust
#[derive(Serialize, Deserialize, Debug)]
struct MyDTD {
   count: i32,
   on: Option<bool>,
}
```
## FireStoreでの認証の準備

ドキュメントをきっちり読めば書いているのですが、最初読み飛ばしてアレ？ってなってたところです(´・ω・｀)

公式のDocument access via service accountの章にちゃんとかいてました。
ちゃんと嫁、と(´・ω:;.:...
独り身にちゃんと嫁は辛いですね。

書いてあるとおり、まずFireBaseのプロジェクトの概要横の歯車マークからプロジェクトの設定画面を開き、サービスアカウントタブを選択してサービスアカウントの設定画面にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/86102644-95ae-7eb3-686c-5ff3d6dfb0c3.png)

下の方に「新しい秘密鍵の生成」ボタンがあるので押すと、jsonファイルがダウンロードできます。これをRustのプロジェクトディレクトリに配置します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/7089a7a0-38fc-e2c7-84dc-50cab56219b3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/53a42a81-05b7-cfe7-fe52-ce01096cda75.png)

次にYOUR_API_KEYをAdd another fieldしろって書いてあるので、YOUR_API_KEYをプロジェクトの設定画面の全般タブの下の方のSDKの設定と構成のあたりから拾ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/f90ec3d8-3190-b3f5-19e3-f067bc62387b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/17d64741-6a20-27d7-87ac-d59565a44db3.png)

apiKeyと書いてある行がそれです。

先程ダウンロードしたjsonファイルを開いて最後とか適当な場所にapi_key **(名前が違うので注意)** というフィールドを作って貼り付けます。

,の付け忘れに気をつけてください（VSCodeなら構文チェックでエラー出してくれてますけどね)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/8f6e89db-3dec-3ebd-074d-4b6ad5e2f85c.png)

認証の準備はこれで完了です。

## Rustで認証する

準備ができていれば割と簡単。とりあえずunwrapを使って動くところまで確認していきましょう。

いきなりif let Ok()等を使って自前でエラーハンドリングするほど慣れてないときは、unwrapでパニクってくれる方が、エラーメッセージもちゃんと出て試行錯誤しやすいかなって思います。

まず認証用ファイルを読み込みます。足りない情報があったらエラーメッセージで○○のフィールドがないよ！って教えてくれます。が、今までの手順通りやってたら足りないフィールドはないはず。

```rust
    let cred = Credentials::from_file("test1-1b250-firebase-adminsdk-c6bbv-982973df3c.json").unwrap();
```

続いて接続。成功すれば認証済みセッション情報が返ってきます。

```rust
let auth = ServiceSession::new(cred).unwrap();
```

このセッション情報を使ってDBの読み書きを行います。ここまでエラーが出なかったらもうほぼ成功しています。

## データの読み書き

authを引数にしたread/write関数を実行するだけです。

ここではデータの一覧を出してみます。先程の認証済みセッションと読み込むコレクション名を引数にしてdocuments::listを実行します。戻り値の型がunknownになりますので、自分で型情報を設定して受け取る必要があります。

ここでは、最初に用意したデータ受け取り用構造体(とSession情報)の型を指定します。

```rust
    let documents: List<MyDTD,ServiceSession> = documents::list(&auth, "sample1");
```

後はforでぶん回しながら出力してみたのがこちら。list関数は全体での成否ではなくドキュメントごとのResultがリストで返ってくるので、それぞれの要素のOkの値を拾って使います。

ドキュメントデータはドキュメント情報とのタプルになって返ってくるので、ドキュメントの名前が知りたいとかドキュメント情報側を見れば取得できます。

```rust
    for doc in documents {
        let (data, _document) = doc.unwrap();
        println!("{:?}", data);
    }
```

Optionで設定したフィールドはSomeかNoneが返ってくるので、値を使うときは剥がしてやる必要があります。

```shell
MyDTD { count: 0, on: Some(true) }
MyDTD { count: 234, on: None }
```

# セキュリティを高める

認証が通ったので、アクセスルールをデバッグモードから本番モードに変更しておきます。

FireStore Databaseのルールのタブを開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/9b33221d-3fdb-f3c0-8a09-117df6d77c68.png)

allowの条件が、デバッグだと一定期間は無条件Trueになっているので本番モードで立てた時と同じ`if false`にしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/044b9706-0427-ba5d-70e5-d39c22621431.png)

これで、最初の方でやったRESTでブラウザからアクセスすると403(Forbidden)が返るようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140718/aacf3acf-76aa-cf75-d706-542c9a2f77c7.png)

この状態でRustから普通にアクセスできていれば成功です。

ぶっちゃけ、一人開発でロールとか、アレよね(´・ω:;.:... 

※仕事の時はロールが便利。とても、便利(\*´ω｀\*)ｾﾞﾋﾂｶｯﾃ

#ここまで読んでくれたあなたに

初手でめんどくさいと思った？

でも、これだけやっておいたら、サーバのメンテが今後一切発生しないんやで？(\*´ω｀\*)

立ち上げたまま放置しても電気代すらかからない。つまり、いつでもエターなる（制作が止まることをいいます）！

……Firebaseさんはずっと立ち上がったまま開発復帰を待っててくれてるのよ？\_(:3」∠)\_