Skip to content

Commit

Permalink
Use URLSearchParams to handle + characters (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoontek authored Mar 18, 2024
1 parent 266b946 commit 53ae890
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 41 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@swan-io/chicane",
"version": "2.0.0-rc.1",
"version": "2.0.0-rc.2",
"license": "MIT",
"description": "A simple and safe router for React and TypeScript",
"author": "Mathieu Acthernoene <[email protected]>",
Expand Down
22 changes: 12 additions & 10 deletions src/concatRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ export const concatRoutes = (
routeA: RouteObject,
routeB: RouteObject,
): string => {
const fixedPathA = ensureSlashPrefix(routeA["path"]);
const fixedPathB = ensureSlashPrefix(routeB["path"]);
const prefixedPathA = ensureSlashPrefix(routeA["path"]);
const prefixedPathB = ensureSlashPrefix(routeB["path"]);
const unprefixedSearchA = routeA.search.substring(1);
const unprefixedSearchB = routeB.search.substring(1);

const path =
fixedPathA === "/"
? fixedPathB
: fixedPathB === "/"
? fixedPathA
: fixedPathA + fixedPathB;
prefixedPathA === "/"
? prefixedPathB
: prefixedPathB === "/"
? prefixedPathA
: prefixedPathA + prefixedPathB;

const search =
routeA["search"] === ""
? routeB["search"]
: routeA["search"] + addPrefixOnNonEmpty(routeB["search"], "&");
unprefixedSearchA === ""
? unprefixedSearchB
: unprefixedSearchA + addPrefixOnNonEmpty(unprefixedSearchB, "&");

return path + addPrefixOnNonEmpty(search, "?");
};
7 changes: 3 additions & 4 deletions src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
last,
noop,
} from "./helpers";
import { decodeUnprefixedSearch, encodeSearch } from "./search";
import { decodeSearch, encodeSearch } from "./search";
import { Blocker, Listener, Location, RouteObject, Search } from "./types";

let initialLocationHasChanged = false;
Expand All @@ -26,7 +26,7 @@ export const parseRoute = (route: string): RouteObject => {

return {
path: cleanRoute.substring(0, searchIndex),
search: cleanRoute.substring(searchIndex + 1),
search: cleanRoute.substring(searchIndex),
};
};

Expand All @@ -41,8 +41,7 @@ export const decodeLocation = (url: string): Location => {
: path.split("/").filter(isNonEmpty).map(decodeURIComponent)
: [];

const parsedSearch =
route.search !== "" ? decodeUnprefixedSearch(route.search) : {};
const parsedSearch = route.search !== "" ? decodeSearch(route.search) : {};

const rawPath = "/" + parsedPath.map(encodeURIComponent).join("/");
const rawSearch = encodeSearch(parsedSearch);
Expand Down
6 changes: 3 additions & 3 deletions src/matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
isParam,
} from "./helpers";
import { parseRoute } from "./history";
import { decodeUnprefixedSearch, encodeSearch } from "./search";
import { encodeSearch } from "./search";
import { Location, Matcher, Params, Search } from "./types";

// Kudos to https://reach.tech/router/ranking
Expand Down Expand Up @@ -48,9 +48,9 @@ export const getMatcher = (name: string, route: string): Matcher => {

if (parsed.search !== "") {
matcher.search = {};
const params = decodeUnprefixedSearch(parsed.search);
const params = new URLSearchParams(parsed.search);

for (const key in params) {
for (const [key] of params) {
if (isParam(key)) {
const multiple = key.endsWith("[]");

Expand Down
37 changes: 14 additions & 23 deletions src/search.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { isNonEmpty } from "./helpers";
import { Search } from "./types";

export const decodeUnprefixedSearch = (search: string): Search => {
const params = search.split("&").filter(isNonEmpty);
export const decodeSearch = (search: string): Search => {
const params = new URLSearchParams(search);
const output: Search = {};

for (const param of params) {
const [head = "", tail = ""] = param.split("=");
const key = decodeURIComponent(head);
const value = decodeURIComponent(tail);

for (const [key, value] of params) {
const existing = output[key];

if (existing != null) {
Expand All @@ -25,17 +20,8 @@ export const decodeUnprefixedSearch = (search: string): Search => {
return output;
};

export const decodeSearch = (search: string): Search =>
decodeUnprefixedSearch(search[0] === "?" ? search.substring(1) : search);

export const appendParam = (
acc: string,
key: string,
value: string,
): string => {
const output = acc + (acc !== "" ? "&" : "") + encodeURIComponent(key);
return value !== "" ? output + "=" + encodeURIComponent(value) : output;
};
const NO_VALUE_PARAM_REGEXP = /=&/g;
const FINISH_BY_EQUAL_REGEXP = /=$/g;

export const encodeSearch = (search: Search): string => {
const keys = Object.keys(search);
Expand All @@ -44,7 +30,7 @@ export const encodeSearch = (search: Search): string => {
return "";
}

let output = "";
const params = new URLSearchParams();
keys.sort(); // keys are sorted in place

for (const key of keys) {
Expand All @@ -55,16 +41,21 @@ export const encodeSearch = (search: Search): string => {
}

if (typeof value === "string") {
output = appendParam(output, key, value);
params.append(key, value);
} else {
for (const item of value) {
output = appendParam(output, key, item);
params.append(key, item);
}
}
}

const output = params
.toString()
.replace(NO_VALUE_PARAM_REGEXP, "&")
.replace(FINISH_BY_EQUAL_REGEXP, "");

if (output === "") {
return ""; // params are empty arrays
return "";
}

return "?" + output;
Expand Down

0 comments on commit 53ae890

Please sign in to comment.