prismaでmysqlに繋ぐ時、DATABASER_URLに渡す文字のエンコードの話

## どういう記事？
prismaのdb urlを設定する時にpasswordとusernameをpercent encodingする必要があるが、percent encodingも色々あるみたいなので、調べたログ。あと、windows(powershell)で手軽に手元でpercent encodingする方法を知りたかったのでそれも書いた。


作成日：2025年2月22日

## 概要
prismaを使用する時に設定するdatabse urlは以下のurlだが、usernameとpasswordのspecial characterはpercent encodingする必要がある。
```
DATABASE_URL=postgresql://janedoe:mypassword@localhost:5432/mydb
```

prismaのドキュメントからリンクされているのはMDNのpercent encodingの記事

https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding

これによると、変換についてはRFC 3986のsection 2.1に定義されているとのこと。

powershellでpercent encodingをする場合、以下の3つの関数があるっぽい。調べた感じ、一番上のEscapeDataStringを使うのが良さげではある。
```
1. [uri]::EscapeDataString("text")
2. [System.Net.WebUtility]::UrlEncode("text")
3. [System.Web.HttpUtility]::UrlEncode("text")
```
### 1. [uri]::EscapeDataString("text")
`[uri]::EscapeDataString`は[ドキュメント](https://learn.microsoft.com/en-us/dotnet/api/system.uri.escapedatastring?view=net-9.0)によるとデフォルトでRFC 2396に則ったencodingが行われるが、International Resource Identifiers(IRIs)とInternationalized Domain Name(IDN) parsingがenableならRFC 3986とRFC 3987に則ってencodingが行われるとのこと。

chatGPT曰く最近の環境(powershell 7.1)以降ではIRIとIDNはデフォルトでオンらしいが、それらしい情報は見つけられなかったので、不明。

### 2. [System.Net.WebUtility]::UrlEncode("text")
[ドキュメント](https://learn.microsoft.com/en-us/dotnet/api/system.net.webutility.urlencode?view=net-8.0#:~:text=Use%20EscapeDataString%20for%20RFC%20compliant%20transformation.)によると、
> Use EscapeDataString for RFC compliant transformation.

とのこと。これは`[System.Net.WebUtility]::UrlEncode()`には慣習的な(web標準ではない)『spaceを+に変える』みたいな処理も入っているから、という事らしい。因みに、このRemarkはドキュメントを英語に切り替えないと出ない。

例えば、`te xt`という間にスペースが入った文字列をpercent encodingする時、変換後の文字列に以下のような差が生じる
```
PS C:> [System.Net.WebUtility]::UrlEncode("te xt")
te+xt
PS C:> [uri]::EscapeDataString("te xt")
te%20xt
```

prisma側では+をspaceに戻す処理は無いので、もしusernameやpasswordにspaceが入っていて、WebUtilityのUrlEncodeを使ってしまうと、DBに接続できない(そもそもspaceを使うなという感じだが、技術的に使えない訳ではないっぽい)


### 3. [System.Web.HttpUtility]::UrlEncode("test string")

 HttpUtilityはそもそも古いくて、実行前に`Add-Type -AssemblyName System.Web`しないと今の環境では使えない。それに、実行結果は`[System.Net.WebUtility]::UrlEncode()`と同じなので、spaceが入ると+になる。率先して使う理由は無さそうではある。

## そもそもなぜpercent encodingが必要なのか？
prismaがurlの形式でDBの情報を受け取る時にパスワードに特殊文字が含まれていると正しく接続できないため、ユーザー名やパスワードはurl encodingする必要がある。

## どう処理されている？
prismaのコアはprisma engineという別リポジトリが存在し、その中に[Quaint](https://github.com/prisma/prisma-engines/tree/main/quaint)というRust製の種々のDBのための抽象化レイヤーみたいなものがある。元は別repoだったみたいだが、今はQuaintはprisma-engineの一部になっている。

prisma engineはこのquaintを用いてDBに接続する。

そのQuaintの中にmysqlに接続するconnecterがあり、その中でpasswordのdecodeを行っている。具体的には以下のコード

```rust:prisma-engines/quaint/src/connector/mysql/url.rs
    pub fn password(&self) -> Option<Cow<str>> {
        match self
            .url
            .password()
            .and_then(|pw| percent_decode(pw.as_bytes()).decode_utf8().ok())
        {
            Some(password) => Some(password),
            None => self.url.password().map(|s| s.into()),
        }
    }
    
```
で、この`percent_decode`に関しては同じファイルの上の方で

```rust
use percent_encoding::percent_decode;
```
として宣言されている。

このQuaintの[cargo.toml](https://github.com/prisma/prisma-engines/blob/main/quaint/Cargo.toml)を見ると`percent-encoding.workspace = true`と書いてあったので、上の階層から探すと、prisma-engineのrootの[cargo.toml](https://github.com/prisma/prisma-engines/blob/main/Cargo.toml)に`percent-encoding = "2"`と書いてあった。普通にRUSTの[percent-encoding](https://crates.io/crates/percent-encoding)を使っているっぽい。

で、そのRustのpercent_encodingにおける、`percent_decode`の[ドキュメント](https://docs.rs/percent-encoding/2.3.1/percent_encoding/fn.percent_decode.html)には、[whatwgのpercent_decodeのリンク](https://docs.rs/percent-encoding/2.3.1/percent_encoding/fn.percent_decode.html)が貼ってあるので、どうやらこのライブラリはWHATWG(Web Hypertext Application Technology Working Group)の標準に従ってるっぽい。

また、
> Any sequence of % followed by two hexadecimal digits is decoded. The return type:

とも書いてあるので、純粋に「%と16進数の数字」の組み合わせがあればそれをdecodeして値を返すだけっぽい。


## 結論
powershellでprisma用のpercent encodingを作る時は`[uri]::EscapeDataString("text")`を使おう。
あとなんか間違えてる事あれば教えてください。

## 独り言
これ調べてたら土曜日の午後が終わった。こういうのに時間かけすぎで他の事が進まないんだよな。うーーーーーーーむ。