All files compile-url.ts

100% Statements 31/31
100% Branches 22/22
100% Functions 5/5
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55        4x       65x   65x 37x 68x           28x 39x 28x 28x   39x 39x   41x   39x         107x 42x 42x 85x 85x 82x   18x   71x       42x 42x   42x     4x 107x  
import type { ParamValue, QueryValue } from './types'
 
type UrlBuilder = (params?: Record<string, ParamValue>, query?: Record<string, QueryValue>, hash?: ParamValue) => string
 
const PARAM = /\[([^\]]+)\]/
 
/** Prepares the base URL and path, then returns a URL builder. Does not validate values. */
export function compileUrl(baseUrl: string | undefined, path: string): UrlBuilder {
  const base = baseUrl?.endsWith('/') && path.startsWith('/') ? baseUrl.slice(0, -1) : (baseUrl ?? '')
 
  if (!path.includes('[')) {
    const url = base + path
    return (_params, query, hash) => url + stringifyQuery(query) + stringifyHash(hash)
  }
 
  // パスの分割は最初の呼び出しまで遅らせる(ルート定義を軽くするため)。
  // [静的, 名前, 静的, 名前, …, 静的]。baseUrl の [ ] は分割しない
  let parts: string[] | undefined
  return (params, query, hash) => {
    if (parts === undefined) {
      parts = path.split(PARAM)
      parts[0] = base + parts[0]
    }
    let url = parts[0]
    for (let i = 1; i < parts.length; i += 2) {
      // encodeURIComponent は値を自分で文字列化する(String() と同じ結果)
      url += encodeURIComponent(params?.[parts[i]] as ParamValue) + parts[i + 1]
    }
    return url + stringifyQuery(query) + stringifyHash(hash)
  }
}
 
function stringifyQuery(query: Record<string, QueryValue> | undefined) {
  if (query == null) return ''
  const search = new URLSearchParams()
  for (const key in query) {
    const value = query[key]
    if (value === undefined) continue
    if (Array.isArray(value)) {
      // append は値を自分で文字列化する(String() と同じ結果)
      for (const v of value) search.append(key, v as string)
    } else {
      search.append(key, value as string)
    }
  }
  // キー順を揃えて同じクエリなら同じ URL にする(型の返り値もキー順)。sort は安定なので配列の順は保たれる
  search.sort()
  const s = search.toString()
  // URLSearchParams は空白を + にする。+ という文字自体は %2B になるので置き換えてよい
  return s && `?${s.replaceAll('+', '%20')}`
}
 
const stringifyHash = (hash: ParamValue | undefined) =>
  hash === undefined || hash === '' ? '' : `#${encodeURIComponent(hash)}`