Skip to content

Commit 1052740

Browse files
committed
fix: Refactor FormData, querystring and websocket utils
1 parent 2f6b4c5 commit 1052740

10 files changed

Lines changed: 205 additions & 238 deletions

src/adapters/serializers/flatten-record.spec.ts

Lines changed: 0 additions & 62 deletions
This file was deleted.

src/adapters/serializers/flatten-record.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/* eslint-disable unicorn/no-null */
2+
import { flattenForFormData, flattenForRailsQueryString } from "./flatten";
3+
4+
describe("form data", () => {
5+
test("flat value", () => {
6+
const result = flattenForFormData({
7+
apple: "red",
8+
mandarin: "orange",
9+
grapes: "purple",
10+
});
11+
12+
expect(result).toStrictEqual({
13+
apple: "red",
14+
mandarin: "orange",
15+
grapes: "purple",
16+
});
17+
});
18+
19+
test("array", () => {
20+
const result = flattenForFormData({
21+
animals: ["lion", "giraffe", "elephant"],
22+
});
23+
24+
expect(result).toStrictEqual({
25+
"animals[0]": "lion",
26+
"animals[1]": "giraffe",
27+
"animals[2]": "elephant",
28+
});
29+
});
30+
31+
test("nested object", () => {
32+
const result = flattenForFormData({
33+
a: "string",
34+
b: 123,
35+
c: [1, 2, 3],
36+
e: {
37+
e1: "string",
38+
e2: {
39+
e21: {
40+
e211: "string",
41+
},
42+
e22: [{ value: 1 }, { value: 2 }, { value: 3 }],
43+
},
44+
},
45+
});
46+
47+
expect(result).toStrictEqual({
48+
a: "string",
49+
b: 123,
50+
"c[0]": 1,
51+
"c[1]": 2,
52+
"c[2]": 3,
53+
"e[e1]": "string",
54+
"e[e2][e21][e211]": "string",
55+
"e[e2][e22][0][value]": 1,
56+
"e[e2][e22][1][value]": 2,
57+
"e[e2][e22][2][value]": 3,
58+
});
59+
});
60+
});
61+
62+
describe("flattenForRailsQueryString", () => {
63+
it("encodes null", () => {
64+
const result = flattenForRailsQueryString(null);
65+
expect(result).toBe("");
66+
});
67+
68+
it("encodes an empty object", () => {
69+
const result = flattenForRailsQueryString({});
70+
expect(result).toBe("");
71+
});
72+
73+
it("encodes a basic record", () => {
74+
const result = flattenForRailsQueryString({ key: "value" });
75+
expect(result).toBe("key=value");
76+
});
77+
78+
it("encodes a record with multiple values", () => {
79+
const result = flattenForRailsQueryString({
80+
key1: "value1",
81+
key2: "value2",
82+
});
83+
expect(result).toBe("key1=value1&key2=value2");
84+
});
85+
86+
it("encodes string safely", () => {
87+
const result = flattenForRailsQueryString({
88+
q: "https://neet.love",
89+
});
90+
// cspell:disable-next-line
91+
expect(result).toBe("q=https%3A%2F%2Fneet.love");
92+
});
93+
94+
it("encodes an array inside a record", () => {
95+
const result = flattenForRailsQueryString({
96+
key1: "value1",
97+
key2: "value2",
98+
key3: ["apple", "facebook", "microsoft"],
99+
});
100+
expect(result).toBe(
101+
"key1=value1&key2=value2&key3[]=apple&key3[]=facebook&key3[]=microsoft",
102+
);
103+
});
104+
105+
it("encodes null", () => {
106+
const result = flattenForRailsQueryString({
107+
foo: null,
108+
bar: "baz",
109+
});
110+
111+
expect(result).toBe("bar=baz");
112+
});
113+
114+
it("encodes undefined", () => {
115+
const result = flattenForRailsQueryString({
116+
foo: undefined,
117+
bar: "baz",
118+
});
119+
120+
expect(result).toBe("bar=baz");
121+
});
122+
123+
it("encodes tested object", () => {
124+
expect(
125+
flattenForRailsQueryString({
126+
title: "some group",
127+
context: ["notifications"],
128+
keywordsAttributes: [
129+
{
130+
keyword: "my keyword",
131+
},
132+
],
133+
}),
134+
).toMatchInlineSnapshot(
135+
`"title=some%20group&context[]=notifications&keywordsAttributes[][keyword]=my%20keyword"`,
136+
);
137+
});
138+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { isRecord } from "../../utils";
2+
3+
interface KeyMapper {
4+
readonly onArray: (parent: unknown, index: number) => string;
5+
readonly onObject: (parent: string, key: string) => string;
6+
}
7+
8+
const ObjectFlattener = (keyMapper: KeyMapper) => {
9+
const flatten = (object: unknown, parent = ""): [string, unknown][] => {
10+
if (Array.isArray(object)) {
11+
return object.flatMap((value, i) =>
12+
flatten(
13+
value,
14+
parent == "" ? i.toString() : keyMapper.onArray(parent, i),
15+
),
16+
);
17+
}
18+
19+
if (isRecord(object)) {
20+
return Object.entries(object).flatMap(([key, value]) =>
21+
flatten(value, parent === "" ? key : keyMapper.onObject(parent, key)),
22+
);
23+
}
24+
25+
return [[parent, object]];
26+
};
27+
28+
return flatten;
29+
};
30+
31+
export const flattenForFormData = (
32+
object: unknown,
33+
): Record<string, unknown> => {
34+
const flatten = ObjectFlattener({
35+
onArray: (parent, index) => `${parent}[${index}]`,
36+
onObject: (parent, key) => `${parent}[${key}]`,
37+
});
38+
39+
return Object.fromEntries(flatten(object));
40+
};
41+
42+
export const flattenForRailsQueryString = (object: unknown): string => {
43+
const flatten = ObjectFlattener({
44+
onArray: (parent) => `${parent}[]`,
45+
onObject: (parent, key) => `${parent}[${key}]`,
46+
});
47+
48+
return flatten(object)
49+
.filter(([, v]) => v != undefined)
50+
.map(([k, v]) => `${k}=${encodeURIComponent(v as string)}`)
51+
.join("&");
52+
};

src/adapters/serializers/rails-query-string.spec.ts

Lines changed: 0 additions & 80 deletions
This file was deleted.

src/adapters/serializers/rails-query-string.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)