-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
61429f2
commit 9538ec6
Showing
4 changed files
with
131 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
--- | ||
path: /firestore-schema-with-zod | ||
created: "2023-04-21" | ||
title: firestore を zod でバリデーションする | ||
visual: "./visual.png" | ||
tags: [zod, firebase, firestore] | ||
userId: sadnessOjisan | ||
isFavorite: false | ||
isProtect: false | ||
--- | ||
|
||
[encraft #2](https://knowledgework.connpass.com/event/279962/) までの間、スキーマスキーマした話をたくさん書きたい。 | ||
|
||
OGP は昨日食べた火鍋だ。Fire 感があるのでこれを使おうと思った。([Firebase の記事を書く時は炎の画像を使っていた](/tags/firebase/)のに、炎系のフリー素材をたくさん使いすぎて似た画像ばかりになりストックがなくなったことは秘密) | ||
|
||
## firestore は withConverter で validation できる | ||
|
||
なんか似たようなブログを書いた気がしていたのだが、どうやら [firestore の入出力に型をつける](https://blog.ojisan.io/typed-firestore/) で `withConverter` を紹介していた。なので詳しくはそれを見てほしい。 | ||
|
||
```tsx | ||
export const sitemapConverter: FirestoreDataConverter<SitemapSchema> = { | ||
toFirestore(sitemapDto: SitemapSchema): DocumentData { | ||
const record: SitemapSchema = { | ||
origin: sitemapDto.origin, | ||
url: sitemapDto.url, | ||
created_at: sitemapDto.created_at, | ||
updated_at: sitemapDto.updated_at, | ||
}; | ||
const parsed = sitemapSchema.parse(record); | ||
return parsed; | ||
}, | ||
fromFirestore(snapshot: QueryDocumentSnapshot): SitemapSchema { | ||
const data = snapshot.data(); | ||
const parsed = sitemapSchema.parse(data); | ||
return { | ||
url: parsed.url, | ||
origin: parsed.origin, | ||
updated_at: parsed.updated_at, | ||
created_at: parsed.created_at, | ||
}; | ||
}, | ||
}; | ||
``` | ||
|
||
さて、先の例ではしれっと sitemapSchema という zod schema が登場しているのだが、この fromFirestore で validation すれば firestore からの戻り値を検証して型を付けられる。 | ||
|
||
## Firebase 独特のものをバリデーションしたい | ||
|
||
で、number やら string を firestore に入れている限りでは普通に zod の `z.string()` `z.number()` を使うだけでスキーマを定義できるのだが、firestore の組み込み型には Timestamp や Reference 型といったものが登場する。そしてこれは利用することがほぼ確定しているようなものだ。頑張ってこれのスキーマを定義しよう。 | ||
|
||
### Timestamp | ||
|
||
Firestore における日付表現だ。この型でデータを保存しておくと日付順のソートができるようになるので重宝する。 | ||
|
||
Timestamp は Date 型とはまた違うので `z.date()` ができない。そこで `z.instanceof` を使おう。 | ||
|
||
```tsx | ||
z.instanceof(Timestamp); | ||
``` | ||
|
||
FYI: [https://github.com/colinhacks/zod#instanceof](https://github.com/colinhacks/zod#instanceof) | ||
|
||
### DocumentReference | ||
|
||
DocumentReference は別 collection の Document に対する参照だ。ドキュメント ID だけ文字列で保存していると、そのドキュメントにたどり着くまでのパスを作らないといけないが、参照を保存しておけば一発でそのドキュメントまで辿れる。それを実現する特別な型が DocumentReference だ。テーブルの正規化などでとくにお世話になるだろう。 | ||
|
||
これも instanceof なんていう便利なものがあれば DocumentReference に対しても型を付けられそうだ。が、実は使えないのである! | ||
|
||
FYI: [https://stackoverflow.com/questions/74346759/use-zod-to-validate-schema-with-firestore-documentreferences-in-it-with-default](https://stackoverflow.com/questions/74346759/use-zod-to-validate-schema-with-firestore-documentreferences-in-it-with-default) | ||
|
||
実は private constructor を持っている物に対してはこの手法は使えないのである。どうやら `FieldValue` に対しても同様の問題があるようだ。(え、constructor が private ってなんやねんと思った方は Static Factory Method を調べたり [Effective Java](https://www.maruzen-publishing.co.jp/item/?book_no=303408) の第 1 章を読んでみよう) | ||
|
||
FYI: [https://github.com/colinhacks/zod/issues/384](https://github.com/colinhacks/zod/issues/384) | ||
|
||
こうなると無理やりに突破するしかない。そこで refine の出番である。僕も最近まで知らなかったのだが篩型のサポートがあった。といってもここでは篩篩したようなことはせず、any 型で一旦型検査をパスさせて、JS 本来の instanceof で検証して通ればユーザー定義ガードで型を付けさせる。refine がそういう API なのでそうする。 | ||
|
||
```tsx | ||
page_ref: z.any().refine( | ||
(x: object): x is DocumentReference => x instanceof DocumentReference | ||
); | ||
``` | ||
|
||
便利〜〜〜 | ||
|
||
zod は最悪の場合に TS の世界で型を付けられるのは便利ですね。Ajv にはできない芸当だ・・・ | ||
|
||
## 最後に | ||
|
||
ここまで書いておいてアレですが、同日に 「[zod 使わない!](/i-use-ajv-instead-of-zod)」というブログ書いているので読んでみてください。 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
--- | ||
path: /i-use-ajv-instead-of-zod | ||
created: "2023-04-21" | ||
title: zod ではなく ajv を使っている話 | ||
visual: "./visual.png" | ||
tags: [zod, ajv] | ||
userId: sadnessOjisan | ||
isFavorite: false | ||
isProtect: false | ||
--- | ||
|
||
[encraft #2](https://knowledgework.connpass.com/event/279962/) までの間、スキーマスキーマした話をたくさん書きたい。好き好きスキーマと言いたいところだが、zod に対しては人気に対して逆張り意見的なのを持っているのでそれを書いていきたい。 | ||
|
||
OGP は Ajv ユーザーと焼肉をしたときの画像だ。網もスキーマが大事ってことですね。 | ||
|
||
## 独自性の高いスキーマを使うのは危険だと思っている | ||
|
||
zod は便利だ。とても流行っている。その結果 [yup](https://github.com/jquense/yup) や [joi](https://github.com/hapijs/joi) で作られたものが負債扱いされているような気までする。だが思い出してほしいのだが、yup だって出てきた当初はとても便利なものとして人気があった気がする。特に [Formik](https://formik.org/) と組み合わせるのは一種のパターンになっていたような気もする。しかし最近はそれらが zod に取って代わられてしまったと思っている。エコシステムの選択や対応を見ていると zod 一強だ。 | ||
|
||
(ちなみに npm trends でみると joi 一強です。 Server FW から HTTP Client まで hapi family は至る所に生きている。) | ||
|
||
だが、zod より便利なものが今後出たらどうなるのだろうか。zod も負債扱いされる未来が来るような気がしている。これまでの傾向からしてユーザーはスキーマライブラリを気軽に乗り換えていく。だが僕はスキーマには本当に長生きしてほしい(後でマジで首絞まるので・・・)。なので長生きするような技術選定をしたい。 | ||
|
||
## 長生きするスキーマ | ||
|
||
zod に限らず正直なところ何を使っても負債扱いされると思っているので、移植性を重きに置いた技術選定をしようと思った。そこで JS/TS に依存しない IDL として | ||
|
||
- JSON Schema | ||
- GraphQL | ||
- protobuf | ||
|
||
などが良いと思っている。このうち自分は表現力の都合で JSON Schema を使うことが多く、その validator として Ajv を使っている。 | ||
|
||
**長生きしてほしい、だから僕は JSON Schema を使い続ける。** | ||
|
||
## zod が嬉しいとき | ||
|
||
一方で zod が得意なことや zod にしかできないこともある。例えば zod は クラスなど、plain でないもののバリデーションがしやすい。また TS first なのを生かして [Brand 型の対応](https://github.com/colinhacks/zod#brand)などもできる。 | ||
|
||
もしかしたら今は Ajv もできるかもしれない(実際できるけど型推論が大変なことになる)が [zod は再帰型のバリデーションもできる](https://github.com/colinhacks/zod#recursive-types)。そして[Firestore に対する Validation は zod 一択](https://blog.ojisan.io/firestore-schema-with-zod)だと思っている。 | ||
|
||
zod も最高!なので結局は何を使う時はその時にあったものを使いましょう(完) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.