Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 69 additions & 10 deletions apps/builder/app/builder/features/settings-panel/curl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { generateCurl, parseCurl, type CurlRequest } from "./curl";
test("support url", () => {
const result = {
url: "https://my-url/hello-world",
searchParams: [],
method: "get",
headers: [],
};
Expand All @@ -19,6 +20,7 @@ test("support multiline command with backslashes", () => {
`)
).toEqual({
url: "https://my-url/hello-world",
searchParams: [],
method: "get",
headers: [],
});
Expand All @@ -27,6 +29,7 @@ test("support multiline command with backslashes", () => {
test("forgive missing closed quotes", () => {
expect(parseCurl(`curl "https://my-url/hello-world`)).toEqual({
url: "https://my-url/hello-world",
searchParams: [],
method: "get",
headers: [],
});
Expand All @@ -43,6 +46,7 @@ test("skip when invalid", () => {
test("support method with --request and -X flags", () => {
const result = {
url: "https://my-url/hello-world",
searchParams: [],
method: "post",
headers: [],
};
Expand All @@ -62,19 +66,41 @@ test("support --get and -G flags", () => {
expect(
parseCurl(`curl --get https://my-url --data limit=3 --data first=0`)
).toEqual({
url: "https://my-url?limit=3&first=0",
url: "https://my-url/",
searchParams: [
{ name: "limit", value: "3" },
{ name: "first", value: "0" },
],
method: "get",
headers: [],
});
expect(parseCurl(`curl -G https://my-url -d limit=3 -d first=0`)).toEqual({
url: "https://my-url?limit=3&first=0",
url: "https://my-url/",
searchParams: [
{ name: "limit", value: "3" },
{ name: "first", value: "0" },
],
method: "get",
headers: [],
});
expect(
parseCurl(`curl -G https://my-url?filter=1 -d limit=3 -d first=0`)
).toEqual({
url: "https://my-url?filter=1&limit=3&first=0",
url: "https://my-url/",
searchParams: [
{ name: "filter", value: "1" },
{ name: "limit", value: "3" },
{ name: "first", value: "0" },
],
method: "get",
headers: [],
});
expect(parseCurl(`curl -G https://my-url?filter -d limit`)).toEqual({
url: "https://my-url/",
searchParams: [
{ name: "filter", value: "" },
{ name: "limit", value: "" },
],
method: "get",
headers: [],
});
Expand All @@ -85,12 +111,14 @@ test("support headers with --header and -H flags", () => {
parseCurl(`curl https://my-url/hello-world --header "name: value"`)
).toEqual({
url: "https://my-url/hello-world",
searchParams: [],
method: "get",
headers: [{ name: "name", value: "value" }],
});
expect(parseCurl(`curl https://my-url/hello-world -H "name: value"`)).toEqual(
{
url: "https://my-url/hello-world",
searchParams: [],
method: "get",
headers: [{ name: "name", value: "value" }],
}
Expand All @@ -101,6 +129,7 @@ test("support headers with --header and -H flags", () => {
)
).toEqual({
url: "https://my-url/hello-world",
searchParams: [],
method: "get",
headers: [
{ name: "name", value: "value1" },
Expand All @@ -119,7 +148,8 @@ test("default to post method and urlencoded header when data is specified", () =
--data-raw param=4
`)
).toEqual({
url: "https://my-url",
url: "https://my-url/",
searchParams: [],
method: "post",
headers: [
{ name: "content-type", value: "application/x-www-form-urlencoded" },
Expand All @@ -132,7 +162,8 @@ test("encode data for get request", () => {
expect(
parseCurl(`curl -G https://my-url --data-urlencode param=привет`)
).toEqual({
url: "https://my-url?param=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82",
url: "https://my-url/",
searchParams: [{ name: "param", value: "привет" }],
method: "get",
headers: [],
});
Expand All @@ -142,7 +173,8 @@ test("encode data for post request", () => {
expect(
parseCurl(`curl https://my-url --data-urlencode param=привет`)
).toEqual({
url: "https://my-url",
url: "https://my-url/",
searchParams: [],
method: "post",
headers: [
{ name: "content-type", value: "application/x-www-form-urlencoded" },
Expand All @@ -160,6 +192,7 @@ test("support text body", () => {
`)
).toEqual({
url: "https://my-url/hello-world",
searchParams: [],
method: "post",
headers: [{ name: "content-type", value: "plain/text" }],
body: `{"param":"value"}`,
Expand All @@ -170,6 +203,7 @@ test("support text body", () => {
)
).toEqual({
url: "https://my-url/hello-world",
searchParams: [],
method: "post",
headers: [{ name: "content-type", value: "plain/text" }],
body: `{"param":"value"}`,
Expand All @@ -186,6 +220,7 @@ test("support text body with explicit method", () => {
`)
).toEqual({
url: "https://my-url/hello-world",
searchParams: [],
method: "put",
headers: [{ name: "content-type", value: "plain/text" }],
body: `{"param":"value"}`,
Expand All @@ -199,6 +234,7 @@ test("support json body", () => {
)
).toEqual({
url: "https://my-url/hello-world",
searchParams: [],
method: "post",
headers: [{ name: "content-type", value: "application/json" }],
body: { param: "value" },
Expand All @@ -213,12 +249,13 @@ test("generate curl with json body", () => {
expect(
generateCurl({
url: "https://my-url.com",
searchParams: [],
method: "post",
headers: [{ name: "content-type", value: "application/json" }],
body: { param: "value" },
})
).toMatchInlineSnapshot(`
"curl "https://my-url.com" \\
"curl "https://my-url.com/" \\
--request post \\
--header "content-type: application/json" \\
--data "{\\"param\\":\\"value\\"}""
Expand All @@ -229,12 +266,13 @@ test("generate curl with text body", () => {
expect(
generateCurl({
url: "https://my-url.com",
searchParams: [],
method: "post",
headers: [],
body: "my data",
})
).toMatchInlineSnapshot(`
"curl "https://my-url.com" \\
"curl "https://my-url.com/" \\
--request post \\
--data "my data""
`);
Expand All @@ -244,18 +282,38 @@ test("generate curl without body", () => {
expect(
generateCurl({
url: "https://my-url.com",
searchParams: [],
method: "post",
headers: [],
})
).toMatchInlineSnapshot(`
"curl "https://my-url.com" \\
"curl "https://my-url.com/" \\
--request post"
`);
});

test("generate curl with search params", () => {
expect(
generateCurl({
url: "https://my-url.com",
searchParams: [
{ name: "search", value: "term1" },
{ name: "search", value: "term2" },
{ name: "filter", value: "привет" },
],
method: "get",
headers: [],
})
).toMatchInlineSnapshot(`
"curl "https://my-url.com/?search=term1&search=term2&filter=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82" \\
--request get"
`);
});

test("multiline graphql is idempotent", () => {
const request: CurlRequest = {
url: "https://eu-central-1-shared-euc1-02.cdn.hygraph.com/content/clorhpxi8qx7r01t6hfp1b5f6/master",
searchParams: [],
method: "post",
headers: [{ name: "Content-Type", value: "application/json" }],
body: {
Expand All @@ -276,7 +334,8 @@ test("multiline graphql is idempotent", () => {

test("support basic http authentication", () => {
expect(parseCurl(`curl https://my-url.com -u "user:password"`)).toEqual({
url: "https://my-url.com",
url: "https://my-url.com/",
searchParams: [],
method: "get",
headers: [
{
Expand Down
29 changes: 19 additions & 10 deletions apps/builder/app/builder/features/settings-panel/curl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const getMethod = (value: string): ResourceRequest["method"] => {

export type CurlRequest = Pick<
ResourceRequest,
"url" | "method" | "headers" | "body"
"url" | "searchParams" | "method" | "headers" | "body"
>;

const encodeSearchParams = (data: string[]) => {
Expand Down Expand Up @@ -84,12 +84,18 @@ export const parseCurl = (curl: string): undefined | CurlRequest => {
return;
}
// curl url
let url = args._[1].toString();
const url = new URL(args._[1].toString());
const defaultMethod = args.data ? "post" : "get";
const method: CurlRequest["method"] = args.get
? "get"
: getMethod(args.request ?? defaultMethod);
let contentType: undefined | string;
const searchParams: NonNullable<ResourceRequest["searchParams"]> = [];
for (const [name, value] of url.searchParams) {
searchParams.push({ name, value });
}
// remove all search params from url
url.search = "";
const headers: ResourceRequest["headers"] = (
(args.header as string[]) ?? []
).map((header) => {
Expand All @@ -105,9 +111,10 @@ export const parseCurl = (curl: string): undefined | CurlRequest => {
}
let body: undefined | unknown;
if (args.get && args.data) {
const separator = url.includes("?") ? "&" : "?";
const search = encodeSearchParams(args.data);
url = `${url}${separator}${search}`;
for (const pair of args.data) {
const [name, value = ""] = pair.split("=");
searchParams.push({ name, value });
}
} else if (args.data) {
body = args.data[0];
if (contentType === "application/json") {
Expand All @@ -126,18 +133,20 @@ export const parseCurl = (curl: string): undefined | CurlRequest => {
}
}
return {
url: url as string,
url: url.toString(),
searchParams,
method,
headers,
body,
};
};

export const generateCurl = (request: CurlRequest) => {
const args = [
`curl ${JSON.stringify(request.url)}`,
`--request ${request.method}`,
];
const url = new URL(request.url);
for (const { name, value } of request.searchParams) {
url.searchParams.append(name, value);
}
const args = [`curl ${JSON.stringify(url)}`, `--request ${request.method}`];
for (const header of request.headers) {
args.push(`--header "${header.name}: ${header.value}"`);
}
Expand Down
Loading
Loading