elm/urlで?を含むクエリパラメータを正しくパースする

2 min読了の目安(約2400字TECH技術記事

ElmというAltJSではelm/urlというパッケージを使ってURLをパースし、その内容に応じた状態遷移を行わせるのが一般的です。(多分。筆者はElmにそこまで詳しくないです。)

elm/urlを使ってURLのパース処理を行っているSPAを開発している際に ?をクエリパラメータに含んだURLを正しくパースできない という現象に若干時間を溶かしたので、起きた事象と解決方法を書いておきます。(微妙にレアケースな気もするので誰かの役に立つかはわからん。)

環境

  • Elm 0.19.1
  • elm/url 1.0.0

事象

?をクエリパラメータに含んだURL」というのは https://example.com/auth?redirect_to=a/b?fuga=piyo みたいなURLです。
この例ではredirect_toというクエリパラメータにリダイレクト先のURLを渡したいみたいな想定で、リダイレクト先URLにもクエリパラメータが付与されているため?が登場するといった感じです。

解決策

parserに食わせるURLのクエリパラメータ部分をURLエンコードし https://example.com/auth?redirect_to=a%2Fb%3Ffuga%3Dpiyo こんな感じにしておくとうまくいきます。

elm replで実験してみるとこんな感じです。

import Url
import Url.Parser exposing (..)
import Url.Parser.Query as Query

parser =
   s "auth" <?> Query.string "redirect_to"

-- URLエンコードしていないURL
url =
    { fragment = Nothing, host = "example.com", path = "/auth", port_ = Nothing, protocol = Url.Https, query = Just "redirect_to=a/b?fuga=piyo" }

-- パースを実行する
parse parser url 

上記を実行すると最後のparse関数の戻り値はJust Nothing : Maybe (Maybe String)となり、redirect_toにはなにも入っていないという結果が得られます。

import Url
import Url.Parser exposing (..)
import Url.Parser.Query as Query

parser =
   s "auth" <?> Query.string "redirect_to"

-- クエリパラメータ部分をURLエンコードしたURL
-- (この例ではレコードを直接作っていますが、Url.fronStringでも同じ結果が得られます)
url =
   { fragment = Nothing, host = "example.com", path = "/auth", port_ = Nothing, protocol = Url.Https, query = Just "redirect_to=a%2Fb%3Ffuga%3Dpiyo" } 

-- パースを実行する
parse parser url 

今度はこちらのクエリパラメータをURLエンコードしたものでparseを実行すると、最後の関数の戻り値はJust (Just "a/b?fuga=piyo") : Maybe (Maybe String)となり、redirect_toに入れた値が取れていることに加え、URLエンコードした値もelm/urlの内部でデコードして返してくれていることがわかりました。

おまけ

ちなみにこのようなURLエンコードの処理をElm内でやりたい場合もelm/urlパッケージに含まれる関数で対応できます。

単純なencode/decodeであればUrlモジュールに含まれているpercentEncode/percentDecode関数を使ってあげると

import Url exposing (percentEncode, percentDecode)

encodedString = percentEncode "ハローワールド"
-- "%E3%83%8F%E3%83%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%AB%E3%83%89"

percentDecode encodeString
-- Just "ハローワールド" : Maybe String

という感じでイケます。

また、Url.Builderモジュールには今回のようなクエリパラメータをうまく扱うための関数も用意されているようです。

import Url.Builder exposing (string, toQuery)

[string "redirect_to" "a/b?fuga=piyo", string "hoge" "ほげ"] |> toQuery
-- "?redirect_to=a%2Fb%3Ffuga%3Dpiyo&hoge=%E3%81%BB%E3%81%92" : String

という感じでURLエンコード含めて、めちゃくちゃイイ感じにやってくれます。アプリケーション内でURLを組み立てて何かする必要があるときはこちらを使ってやると、何も気にすることなく安全に書けて良い感じなのではないでしょうか。