スキーマベースのダミーデータ生成ツールをリリースした

# スキーマベースのダミーデータ生成クレートをリリースした
以前のランダムデータ生成クレート[fakes-gen](https://crates.io/crates/fakes-gen )の反省を生かして新しいダミーデータ生成クレート[sbrd-gen](https://crates.io/crates/sbrd-gen )を作成してリリースしました。
以前リリースしたfakes-genも[Qiitaで紹介](https://qiita.com/ogata-k/items/2198f863987fe67860f1 )しています。今回リリースしたsbrd-genは記事投稿時点ではv0.1.0となっており、READMEは[こちら](https://github.com/ogata-k/sbrd-gen/blob/v0.1.x/README-ja.md )となります。

## どんなクレートか
[JSON Generator](https://json-generator.com/ )のようにスキーマを指定してダミーデータを生成することができるクレートです。sbrd-genではCLIツールとしてもライブラリクレートとしても指定できます。[Serialize](https://serde.rs/ )を使って出力を整形しているので、CSVやTSV、Json、Yamlと様々に出力が可能となっています。

CLIツールの時はスキーマはexampleにあるような[Yaml形式のスキーマ](https://github.com/ogata-k/sbrd-gen/blob/v0.1.x/examples/schema/all.yaml )や[Json形式のスキーマ](https://github.com/ogata-k/sbrd-gen/blob/v0.1.x/examples/schema/all.json )が利用できます。
ライブラリクレートの時は[このexample](https://github.com/ogata-k/sbrd-gen/blob/v0.1.x/examples/all_builder.rs )のようにビルダー形式で指定できます。

他のダミーデータ生成ツールにあるような住所や名前・固有名詞などの地域・言語依存のデータはサポートしていません。その代わりに数式の評価や動的なフォーマット、動的な構築をサポートしています。詳しくは[READMEの指定可能なジェネレーターの一覧](https://github.com/ogata-k/sbrd-gen/blob/v0.1.x/README-ja.md#%E6%8C%87%E5%AE%9A%E5%8F%AF%E8%83%BD%E3%81%AA%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%AE%E4%B8%80%E8%A6%A7 )にて利用可能なジェネレーターを確認してください。

## fakes-genのどこが問題だったか
[fakes-gen](https://crates.io/crates/fakes-gen )では、問題が少なくとも5点ありました。

1点目は正しいエスケープ処理を出力していなかったことです。この後でも挙げていますが全て文字列としてダミーデータを出力していたために、引用符で囲って出力するといった雑な対応しかしていませんでした。それこそSeralizeを利用することもしていない状態でした。これではCSVなら辛うじて良くても、Jsonを出力するとなると正しい形式のJsonにならない可能性が高くなってしまいます。

2点目は[ここ](https://github.com/ogata-k/fakes-gen-cli/tree/master/src/faker/each_locale )で用意したデータしか取得できなかったことです。利用者が用意されていない言語のデータを生成することができず、生成するにはプルリクを出して追加してもらわないといけない状態でした。まだ、言語データセットを指定できる機能があれば良かったかもしれません。

3点目は出力するデータがすべて文字列になっていたことです。実装当時、Serializeを知らなかったか、出力結果をフォーマットするクレートを見つけられなかったか、フォーマット処理の簡略化のためか、で文字列に限定してしまいました。同様に文字列のみに制限した出力からなるサイトもあったからかもしれません。

4点目は出力する整数の範囲など特殊なオプションがあった場合に通常のものと別の名前で宣言しないといけないようになっており利用時の拡張性が低かったことです。整数の範囲指定付きと範囲指定なしで指定する名前が違ったりしていました。これでは指定するオプションも変わってくるのかもなどといった余計な心配を与えることになったり、覚える名前が増えてしまう状況になってしまいます。検索性も低くなりますね。

5点目はCLIとして指定するときの用法が分かりにくい上に、CLIとして使いにくかったことです。fakes-genがCLIの引数としてダミーデータに関するオプション全てを指定するようになっているため、1コマンドが長くなって読みにくくなってしまっていました。加えて、CLI上の引数の制約もあり読みにくくなっていました。ドキュメントに複数指定の例がないのも一因かもしれません。ただ、コマンドだけで何を出力してくれるかがわかるのは良かったことかもしれません。

## 生成できる種類
v0.1.0現在24種類のジェネレーターが用意されています。以下でタイプと簡単な説明を列挙（各行の形式は、\<タイプ\>：\<説明\>）しています。

詳しくは[こちら](https://github.com/ogata-k/sbrd-gen/blob/v0.1.x/README-ja.md#%E6%8C%87%E5%AE%9A%E5%8F%AF%E8%83%BD%E3%81%AA%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%AE%E4%B8%80%E8%A6%A7 )を参照してください。

* duplicate-permutation：指定したオプションを元に、区切り文字で区切られた文字列を生成するジェネレーター
* format：これまで生成したキーと値のペアを参照して、指定したRust風のフォーマットの文字列を生成するジェネレーター
* dist-normal：正規分布にしたがって実数を生成するジェネレーター。デフォルトは標準正規分布。
* eval-int：これまで生成したキーと値のペアを参照して、指定したRust風のフォーマットで書かれたスクリプトを整数型として評価して出力するジェネレーター
* eval-real：これまで生成したキーと値のペアを参照して、指定したRust風のフォーマットで書かれたスクリプトを実数型として評価して出力するジェネレーター
* eval-bool：これまで生成したキーと値のペアを参照して、指定したRust風のフォーマットで書かれたスクリプトを真偽型として評価して出力するジェネレーター
* eval-string：これまで生成したキーと値のペアを参照して、指定したRust風のフォーマットで書かれたスクリプトを文字列型として評価して出力するジェネレーター
* increment-id：順番に値をインクリメントして出力するジェネレーター。デフォルトは0始まりの1増加。
* int：整数を出力するジェネレーター。範囲指定が可能。
* real：実数を出力するジェネレーター。範囲指定が可能。
* bool：真偽を出力するジェネレーター。範囲指定が可能。
* date-time：日時を指定したフォーマットで文字列型として出力するジェネレーター。範囲指定が可能。
* date：日にちを指定したフォーマットで文字列型として出力するジェネレーター。範囲指定が可能。
* time：時刻を指定したフォーマットで文字列型として出力するジェネレーター。範囲指定が可能。
* always-null：常にnullを出力するジェネレーター
* case-when：指定した条件に従って選択した子ジェネレーターを使って、ダミーデータを生成するジェネレーター。
* random-child：乱択した子ジェネレーターを使って、ダミーデータを生成するジェネレーター。
* select-int：指定された整数のリストから乱択して出力するジェネレーター
* select-real：指定された実数のリストから乱択して出力するジェネレーター
* select-string：指定された文字列のリストから乱択して出力するジェネレーター
* get-int-value-at：これまで生成したキーと値のペアを参照して指定したスクリプトを整数として評価し、その評価で得られた値をインデックスとして選択される値を整数として出力するジェネレーター
* get-real-value-at：これまで生成したキーと値のペアを参照して指定したスクリプトを整数として評価し、その評価で得られた値をインデックスとして選択される値を実数として出力するジェネレーター
* get-string-value：これまで生成したキーと値のペアを参照して指定したスクリプトを整数として評価し、その評価で得られた値をインデックスとして選択される値を文字列として出力するジェネレーター
* get-value-index：指定した値のリストのうち乱択として利用できるインデックスを整数として出力するジェネレーター。タイプがget-XXXX-value-at（XXXXは可変）といったジェネレーターと共に利用することを想定している。


## どこが難しかったか
ここからはsbrd-genを実装するにあたってどこが難しかったかを話そうと思います。

### Serialize & Deserialize
今回の開発で初めて[serde](https://serde.rs/ )を使いました。そのため、Serialize系のクレートで用意されていないエラーを出す方法がわからず、エラーを出さないようにどうSerializeするかで悩んでいた時期がありました。悩み始めてからしばらくした後独自のエラーをStringにしてからSerializer::Error::customでラップするという方法を見つけて事なきを得ました。

また、[CSVクレート](https://crates.io/crates/csv )Serializeも苦戦しました。このクレートの[Writer](https://docs.rs/csv/latest/csv/struct.Writer.html)は[ここの記述](https://docs.rs/csv/latest/csv/struct.Writer.html#method.from_writer )にあるように自動的にバッファリングしてくれます。ほかのWriter系と合わせようとする対応が思いのほかめんどくさく使いにくかったです。この自動的にバッファリングされるという実装はCSVの一行ずつフォーマットするという特性と改行のたびにバッファを吐き出すバッファ付きWriterの特性を生かした実装だと思われるため、出力がCSVクレートを使った出力だけの場合はこのような苦労することなく利用できる気がします。

### 動的フォーマット
現在Rustではコンパイル時に決めることのできるフォーマット用のformat!マクロなどが提供されていますが、これらは動的つまりランタイムのフォーマットに対応していません。もちろん置換するキーと値のペアをMap構造のデータに詰めて渡すということもできません。しかし、sbrd-genは「名前が"hoge:"や":hoge"、"hoge "といったケース」、「名前として日本語などの非アルファベット文字が使われるケース」があり得る仕様でした。そのため、今回はこれらの問題を対応する必要がありました。
今回は、[human-string-filler](https://crates.io/crates/human-string-filler )と[rt-format](https://crates.io/crates/rt-format )を用いることにしました。
human-string-fillerクレートはRust風のフォーマットで指定されるキーとそのフォーマットオプション付きキーをともにパースして取得してくれる関数とフォーマットして代入してくれる関数を提供してくれますが、エラーが少し使いにくいです。rt-formatクレートはformat!マクロとあらかた同様なことができますが、名前の指定にはa-zA-Z0-9の文字しか使えなく、[この例](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5f2d260cc0929a5f89aa5d1f677a620f)のように日本語を名前に指定できません。これら抽出機能と動的フォーマット機能を組み合わせてsbrd-genの仕様を満たすことができました。
実は、動的にフォーマットしてくれるクレートはほかにもありました。しかし、どれも"{key}"という文字列を置換する程度の機能しかなく、フォーマットオプションまではサポートしてくれていませんでした。

### リリース用バイナリ生成自動化
今回は静的バイナリを[リリースページ](https://github.com/ogata-k/sbrd-gen/releases )で配布することにしました。作成したGitHub Actionsは[こちら](https://github.com/ogata-k/sbrd-gen/blob/master/.github/workflows/release-draft.yml )にあります。
作成したGitHub Actionsを用いると、以下のようにリリースできるようになっています。
1. CHANGE_LOG.mdとCHANGE_LOG-ja.mdをリリースに向けて編集する
1. vX.X.Xというタグを作る。（X.X.Xは0.1.0などに置き換える）
1. GitHubにプッシュする
1. GitHub Actionsが走るので、実行が完了するまで待つ
1. リリースページにドラフトが作成されるので編集する
1. 編集したドラフトを公開する
1. 完了

GitHub Actionsとしては、以下の動きをしています。
5のようにアップロードしたのをダウンロードしてから圧縮しているのは、圧縮してからリリースドラフトにアップロードしたかったがWindowsだけ圧縮方法が違ってめんどかったので、ubuntuでまとめて対応するようにしたためです。

1. 公開したい環境とそのRustターゲットを用意してテストする
1. 公開したい環境とそのRustターゲットを用意して静的バイナリファイルをビルドする
1. 公開したい環境ごとにアップロードする
1. リリースドラフトを用意する（環境で変化すると上書きされるので、共通で一つのみ作成）
1. 公開したい環境ごとにアップロードしたファイルをダウンロードする
1. ダウンロードしたファイルを圧縮して、リリースドラフトにアップロードする

参考にしたサイトですが、色々なサイトを参考にしたのでどこを参考にしたのか忘れてしまいました。すみません。

## おわりに
個人的に欲しかった使いやすいランダムデータ生成CLIツールができたので満足しています。
改善点の要望や不具合があれば、リポジトリにてissueを投げてください。

以上です。
ここまでありがとうございました。