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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | 1x 139x 139x 31x 31x 31x 31x 14x 8x 139x 139x 21x 21x 21x 21x 21x 7x 139x 139x 54x 54x 54x 54x 54x 19x 139x 139x 1x 122x 122x 122x 122x 122x 1x 118x 118x 1x 134x 40x 40x 68x 68x 52x 40x 40x 134x 134x 16x 16x 16x 20x 20x 20x 1x 119x 119x 7x 7x 1x 37x 37x 37x | import type { Options } from "./types";
/**
* Replaces path parameters in the given path with actual values from params.
*
* @param path - The endpoint path, which can include path parameters. (e.g., "/users/[id]")
* @param params - An object containing the values for the path parameters. (e.g., { id: "1" })
* @param mocking - If true, missing parameters are replaced with colon syntax (e.g., ":id") instead of throwing an error, and the output will not be URL-encoded.
*
* @throws Error - Will throw an error if a required parameter is missing or if the parameter type is incorrect.
* @returns The path with encoded path parameters replaced with actual values. (e.g., "/users/1")
*
* @example
* replacePathParams("/users/[id]", { id: "1" });
* // => "/users/1"
*
* replacePathParams("/posts/[[...slug]]", { slug: ["hoge", "fuga"] });
* // => "/posts/hoge/fuga"
*/
export function replacePathParams(path: string, params: Options["params"], mocking = false): string {
return path
.replace(/\/\[\[\.\.\.(.+?)]]/g, (_, key) => {
// 1. /[[...slug]] - Optional Catch-all Parameters
const values = params?.[key];
if (mocking && values === undefined) return `/:${key}*`;
if (values === undefined) return "";
if (!Array.isArray(values)) throw new Error(`"${key}" must be an array`);
if (mocking) return `/${values.join("/")}`;
return `/${values.map(encodeURIComponent).join("/")}`;
})
.replace(/\[\.\.\.(.+?)]/g, (_, key) => {
// 2. [...slug] - Catch-all Parameters
const values = params?.[key];
if (mocking && values === undefined) return `:${key}+`;
if (values === undefined) throw new Error(`"${key}" is required`);
if (!Array.isArray(values)) throw new Error(`"${key}" must be an array`);
if (mocking) return values.join("/");
return values.map(encodeURIComponent).join("/");
})
.replace(/\[(.+?)]/g, (_, key) => {
// 3. [slug] - Path Parameter
const value = params?.[key];
if (mocking && value === undefined) return `:${key}`;
if (value === undefined) throw new Error(`"${key}" is required`);
if (Array.isArray(value)) throw new Error(`"${key}" must not be an array`);
if (mocking) return String(value);
return encodeURIComponent(String(value));
});
}
/**
* Collapses repeated slashes and removes a trailing slash from the path, but preserves `://` separators.
*
* @param path - The path to trim. (e.g., "/users//1/")
* @returns The trimmed path. (e.g., "/users/1")
*
* @example
* trimPath("/users//1/");
* // => "/users/1"
*
* trimPath("/");
* // => ""
*
* trimPath("myapp://path/");
* // => "myapp://path"
*/
export function trimPath(path: string): string {
return path
.split("://")
.map((segment) => segment.replace(/\/{2,}/g, "/").replace(/\/$/, ""))
.join("://");
}
/**
* Prepends the base URL to the path, falling back to "/" when both are empty.
*
* @param baseUrl - The base URL to prepend. (e.g., "https://example.com")
* @param path - The path to append. (e.g., "/users")
* @returns The joined URL. (e.g., "https://example.com/users")
*
* @example
* joinBaseUrl("https://example.com", "");
* // => "https://example.com"
*
* joinBaseUrl("", "");
* // => "/"
*/
export function joinBaseUrl(baseUrl: string, path: string): string {
return `${baseUrl}${path}` || "/";
}
/**
* Converts an object of query parameters into a URL-encoded query string.
* Handles array values by repeating the key for each value.
*
* @param queries - An object of query parameters.
* @param mocking - If true, the query string will not be URL-encoded and will use a simple key=value format.
* @returns A URL-encoded and sorted query string.
*
* @example
* stringifyQueries({ z: "last", a: "first", n: "middle" });
* // => "?a=first&n=middle&z=last"
*
* stringifyQueries({ search: ["apple", "banana"], page: 2 });
* // => "?page=2&search=apple&search=banana"
*/
export function stringifyQueries(queries: Options["queries"], mocking = false): string {
if (!queries || Object.keys(queries).length === 0) return "";
const entries = Object.entries(queries)
.flatMap(([key, value]) => {
if (value === null || value === undefined) return [];
if (Array.isArray(value)) return value.filter((v) => v !== null && v !== undefined).map((v) => [key, String(v)]);
return [[key, String(value)]];
})
.sort(([a], [b]) => a.localeCompare(b));
if (entries.length === 0) return "";
if (mocking) {
const queryString = entries.map(([key, value]) => `${key}=${value}`).join("&");
return `?${queryString}`;
}
const searchParams = new URLSearchParams(entries).toString().replace(/\+/g, "%20");
return `?${searchParams}`;
}
/**
* Converts a hash value into a string representation.
*
* @param hash - The hash value to be stringified.
* @param mocking - A flag indicating whether to mock the hash value.
* @return The stringified hash value.
*/
export function stringifyHash(hash: Options["hash"], mocking = false): string {
if (!hash) return "";
if (mocking) return `#${hash}`;
return `#${encodeURIComponent(String(hash))}`;
}
/**
* Type guard to check if a key is one of the keys in the Options type.
* @param key - The key to check.
*/
export function isOptions(key: string): key is keyof Options {
const optionsKeys: (keyof Options)[] = ["params", "queries", "hash"];
return optionsKeys.includes(key as (typeof optionsKeys)[number]);
}
|