Skip to content

Commit f5014ad

Browse files
committed
feat: add search params list to resource UI
Ref #3691 Editing search params separately from url can improve experience working with external CMS or databases like baserow.
1 parent 973d9df commit f5014ad

File tree

11 files changed

+412
-175
lines changed

11 files changed

+412
-175
lines changed

apps/builder/app/builder/features/settings-panel/curl.test.ts

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { generateCurl, parseCurl, type CurlRequest } from "./curl";
44
test("support url", () => {
55
const result = {
66
url: "https://my-url/hello-world",
7+
searchParams: [],
78
method: "get",
89
headers: [],
910
};
@@ -19,6 +20,7 @@ test("support multiline command with backslashes", () => {
1920
`)
2021
).toEqual({
2122
url: "https://my-url/hello-world",
23+
searchParams: [],
2224
method: "get",
2325
headers: [],
2426
});
@@ -27,6 +29,7 @@ test("support multiline command with backslashes", () => {
2729
test("forgive missing closed quotes", () => {
2830
expect(parseCurl(`curl "https://my-url/hello-world`)).toEqual({
2931
url: "https://my-url/hello-world",
32+
searchParams: [],
3033
method: "get",
3134
headers: [],
3235
});
@@ -43,6 +46,7 @@ test("skip when invalid", () => {
4346
test("support method with --request and -X flags", () => {
4447
const result = {
4548
url: "https://my-url/hello-world",
49+
searchParams: [],
4650
method: "post",
4751
headers: [],
4852
};
@@ -62,19 +66,41 @@ test("support --get and -G flags", () => {
6266
expect(
6367
parseCurl(`curl --get https://my-url --data limit=3 --data first=0`)
6468
).toEqual({
65-
url: "https://my-url?limit=3&first=0",
69+
url: "https://my-url/",
70+
searchParams: [
71+
{ name: "limit", value: "3" },
72+
{ name: "first", value: "0" },
73+
],
6674
method: "get",
6775
headers: [],
6876
});
6977
expect(parseCurl(`curl -G https://my-url -d limit=3 -d first=0`)).toEqual({
70-
url: "https://my-url?limit=3&first=0",
78+
url: "https://my-url/",
79+
searchParams: [
80+
{ name: "limit", value: "3" },
81+
{ name: "first", value: "0" },
82+
],
7183
method: "get",
7284
headers: [],
7385
});
7486
expect(
7587
parseCurl(`curl -G https://my-url?filter=1 -d limit=3 -d first=0`)
7688
).toEqual({
77-
url: "https://my-url?filter=1&limit=3&first=0",
89+
url: "https://my-url/",
90+
searchParams: [
91+
{ name: "filter", value: "1" },
92+
{ name: "limit", value: "3" },
93+
{ name: "first", value: "0" },
94+
],
95+
method: "get",
96+
headers: [],
97+
});
98+
expect(parseCurl(`curl -G https://my-url?filter -d limit`)).toEqual({
99+
url: "https://my-url/",
100+
searchParams: [
101+
{ name: "filter", value: "" },
102+
{ name: "limit", value: "" },
103+
],
78104
method: "get",
79105
headers: [],
80106
});
@@ -85,12 +111,14 @@ test("support headers with --header and -H flags", () => {
85111
parseCurl(`curl https://my-url/hello-world --header "name: value"`)
86112
).toEqual({
87113
url: "https://my-url/hello-world",
114+
searchParams: [],
88115
method: "get",
89116
headers: [{ name: "name", value: "value" }],
90117
});
91118
expect(parseCurl(`curl https://my-url/hello-world -H "name: value"`)).toEqual(
92119
{
93120
url: "https://my-url/hello-world",
121+
searchParams: [],
94122
method: "get",
95123
headers: [{ name: "name", value: "value" }],
96124
}
@@ -101,6 +129,7 @@ test("support headers with --header and -H flags", () => {
101129
)
102130
).toEqual({
103131
url: "https://my-url/hello-world",
132+
searchParams: [],
104133
method: "get",
105134
headers: [
106135
{ name: "name", value: "value1" },
@@ -119,7 +148,8 @@ test("default to post method and urlencoded header when data is specified", () =
119148
--data-raw param=4
120149
`)
121150
).toEqual({
122-
url: "https://my-url",
151+
url: "https://my-url/",
152+
searchParams: [],
123153
method: "post",
124154
headers: [
125155
{ name: "content-type", value: "application/x-www-form-urlencoded" },
@@ -132,7 +162,8 @@ test("encode data for get request", () => {
132162
expect(
133163
parseCurl(`curl -G https://my-url --data-urlencode param=привет`)
134164
).toEqual({
135-
url: "https://my-url?param=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82",
165+
url: "https://my-url/",
166+
searchParams: [{ name: "param", value: "привет" }],
136167
method: "get",
137168
headers: [],
138169
});
@@ -142,7 +173,8 @@ test("encode data for post request", () => {
142173
expect(
143174
parseCurl(`curl https://my-url --data-urlencode param=привет`)
144175
).toEqual({
145-
url: "https://my-url",
176+
url: "https://my-url/",
177+
searchParams: [],
146178
method: "post",
147179
headers: [
148180
{ name: "content-type", value: "application/x-www-form-urlencoded" },
@@ -160,6 +192,7 @@ test("support text body", () => {
160192
`)
161193
).toEqual({
162194
url: "https://my-url/hello-world",
195+
searchParams: [],
163196
method: "post",
164197
headers: [{ name: "content-type", value: "plain/text" }],
165198
body: `{"param":"value"}`,
@@ -170,6 +203,7 @@ test("support text body", () => {
170203
)
171204
).toEqual({
172205
url: "https://my-url/hello-world",
206+
searchParams: [],
173207
method: "post",
174208
headers: [{ name: "content-type", value: "plain/text" }],
175209
body: `{"param":"value"}`,
@@ -186,6 +220,7 @@ test("support text body with explicit method", () => {
186220
`)
187221
).toEqual({
188222
url: "https://my-url/hello-world",
223+
searchParams: [],
189224
method: "put",
190225
headers: [{ name: "content-type", value: "plain/text" }],
191226
body: `{"param":"value"}`,
@@ -199,6 +234,7 @@ test("support json body", () => {
199234
)
200235
).toEqual({
201236
url: "https://my-url/hello-world",
237+
searchParams: [],
202238
method: "post",
203239
headers: [{ name: "content-type", value: "application/json" }],
204240
body: { param: "value" },
@@ -213,12 +249,13 @@ test("generate curl with json body", () => {
213249
expect(
214250
generateCurl({
215251
url: "https://my-url.com",
252+
searchParams: [],
216253
method: "post",
217254
headers: [{ name: "content-type", value: "application/json" }],
218255
body: { param: "value" },
219256
})
220257
).toMatchInlineSnapshot(`
221-
"curl "https://my-url.com" \\
258+
"curl "https://my-url.com/" \\
222259
--request post \\
223260
--header "content-type: application/json" \\
224261
--data "{\\"param\\":\\"value\\"}""
@@ -229,12 +266,13 @@ test("generate curl with text body", () => {
229266
expect(
230267
generateCurl({
231268
url: "https://my-url.com",
269+
searchParams: [],
232270
method: "post",
233271
headers: [],
234272
body: "my data",
235273
})
236274
).toMatchInlineSnapshot(`
237-
"curl "https://my-url.com" \\
275+
"curl "https://my-url.com/" \\
238276
--request post \\
239277
--data "my data""
240278
`);
@@ -244,18 +282,38 @@ test("generate curl without body", () => {
244282
expect(
245283
generateCurl({
246284
url: "https://my-url.com",
285+
searchParams: [],
247286
method: "post",
248287
headers: [],
249288
})
250289
).toMatchInlineSnapshot(`
251-
"curl "https://my-url.com" \\
290+
"curl "https://my-url.com/" \\
252291
--request post"
253292
`);
254293
});
255294

295+
test("generate curl with search params", () => {
296+
expect(
297+
generateCurl({
298+
url: "https://my-url.com",
299+
searchParams: [
300+
{ name: "search", value: "term1" },
301+
{ name: "search", value: "term2" },
302+
{ name: "filter", value: "привет" },
303+
],
304+
method: "get",
305+
headers: [],
306+
})
307+
).toMatchInlineSnapshot(`
308+
"curl "https://my-url.com/?search=term1&search=term2&filter=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82" \\
309+
--request get"
310+
`);
311+
});
312+
256313
test("multiline graphql is idempotent", () => {
257314
const request: CurlRequest = {
258315
url: "https://eu-central-1-shared-euc1-02.cdn.hygraph.com/content/clorhpxi8qx7r01t6hfp1b5f6/master",
316+
searchParams: [],
259317
method: "post",
260318
headers: [{ name: "Content-Type", value: "application/json" }],
261319
body: {
@@ -276,7 +334,8 @@ test("multiline graphql is idempotent", () => {
276334

277335
test("support basic http authentication", () => {
278336
expect(parseCurl(`curl https://my-url.com -u "user:password"`)).toEqual({
279-
url: "https://my-url.com",
337+
url: "https://my-url.com/",
338+
searchParams: [],
280339
method: "get",
281340
headers: [
282341
{

apps/builder/app/builder/features/settings-panel/curl.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const getMethod = (value: string): ResourceRequest["method"] => {
2626

2727
export type CurlRequest = Pick<
2828
ResourceRequest,
29-
"url" | "method" | "headers" | "body"
29+
"url" | "searchParams" | "method" | "headers" | "body"
3030
>;
3131

3232
const encodeSearchParams = (data: string[]) => {
@@ -84,12 +84,18 @@ export const parseCurl = (curl: string): undefined | CurlRequest => {
8484
return;
8585
}
8686
// curl url
87-
let url = args._[1].toString();
87+
const url = new URL(args._[1].toString());
8888
const defaultMethod = args.data ? "post" : "get";
8989
const method: CurlRequest["method"] = args.get
9090
? "get"
9191
: getMethod(args.request ?? defaultMethod);
9292
let contentType: undefined | string;
93+
const searchParams: NonNullable<ResourceRequest["searchParams"]> = [];
94+
for (const [name, value] of url.searchParams) {
95+
searchParams.push({ name, value });
96+
}
97+
// remove all search params from url
98+
url.search = "";
9399
const headers: ResourceRequest["headers"] = (
94100
(args.header as string[]) ?? []
95101
).map((header) => {
@@ -105,9 +111,10 @@ export const parseCurl = (curl: string): undefined | CurlRequest => {
105111
}
106112
let body: undefined | unknown;
107113
if (args.get && args.data) {
108-
const separator = url.includes("?") ? "&" : "?";
109-
const search = encodeSearchParams(args.data);
110-
url = `${url}${separator}${search}`;
114+
for (const pair of args.data) {
115+
const [name, value = ""] = pair.split("=");
116+
searchParams.push({ name, value });
117+
}
111118
} else if (args.data) {
112119
body = args.data[0];
113120
if (contentType === "application/json") {
@@ -126,18 +133,20 @@ export const parseCurl = (curl: string): undefined | CurlRequest => {
126133
}
127134
}
128135
return {
129-
url: url as string,
136+
url: url.toString(),
137+
searchParams,
130138
method,
131139
headers,
132140
body,
133141
};
134142
};
135143

136144
export const generateCurl = (request: CurlRequest) => {
137-
const args = [
138-
`curl ${JSON.stringify(request.url)}`,
139-
`--request ${request.method}`,
140-
];
145+
const url = new URL(request.url);
146+
for (const { name, value } of request.searchParams) {
147+
url.searchParams.append(name, value);
148+
}
149+
const args = [`curl ${JSON.stringify(url)}`, `--request ${request.method}`];
141150
for (const header of request.headers) {
142151
args.push(`--header "${header.name}: ${header.value}"`);
143152
}

0 commit comments

Comments
 (0)