From 1563bf571df8277481c60575798aff6757c97de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 5 Jun 2024 10:43:09 +0200 Subject: [PATCH] refactor(core): Move curl endpoint to its own controller (no-changelog) (#9605) --- packages/cli/src/ResponseHelper.ts | 10 - packages/cli/src/Server.ts | 23 +- .../cli/src/controllers/curl.controller.ts | 19 + packages/cli/src/requests.ts | 8 - .../curl.service.ts} | 355 +++++++++--------- .../unit/controllers/curl.controller.test.ts | 51 +++ .../curl.service.test.ts} | 50 +-- packages/editor-ui/src/api/curlHelper.ts | 2 +- 8 files changed, 283 insertions(+), 235 deletions(-) create mode 100644 packages/cli/src/controllers/curl.controller.ts rename packages/cli/src/{CurlConverterHelper.ts => services/curl.service.ts} (51%) create mode 100644 packages/cli/test/unit/controllers/curl.controller.test.ts rename packages/cli/test/unit/{CurlConverterHelper.test.ts => services/curl.service.test.ts} (90%) diff --git a/packages/cli/src/ResponseHelper.ts b/packages/cli/src/ResponseHelper.ts index 42dcdee3533..3d172521d97 100644 --- a/packages/cli/src/ResponseHelper.ts +++ b/packages/cli/src/ResponseHelper.ts @@ -167,13 +167,3 @@ export function send( } }; } - -export const flattenObject = (obj: { [x: string]: any }, prefix = '') => - Object.keys(obj).reduce((acc, k) => { - const pre = prefix.length ? prefix + '.' : ''; - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k)); - //@ts-ignore - else acc[pre + k] = obj[k]; - return acc; - }, {}); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 446ec367a59..fa30e8392d2 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -22,10 +22,11 @@ import { Queue } from '@/Queue'; import { WorkflowsController } from '@/workflows/workflows.controller'; import { EDITOR_UI_DIST_DIR, inDevelopment, inE2ETests, N8N_VERSION, Time } from '@/constants'; import { CredentialsController } from '@/credentials/credentials.controller'; -import type { APIRequest, CurlHelper } from '@/requests'; +import type { APIRequest } from '@/requests'; import { registerController } from '@/decorators'; import { AuthController } from '@/controllers/auth.controller'; import { BinaryDataController } from '@/controllers/binaryData.controller'; +import { CurlController } from '@/controllers/curl.controller'; import { DynamicNodeParametersController } from '@/controllers/dynamicNodeParameters.controller'; import { MeController } from '@/controllers/me.controller'; import { MFAController } from '@/controllers/mfa.controller'; @@ -45,7 +46,6 @@ import type { ICredentialsOverwrite } from '@/Interfaces'; import { CredentialsOverwrites } from '@/CredentialsOverwrites'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; import * as ResponseHelper from '@/ResponseHelper'; -import { toHttpNodeParameters } from '@/CurlConverterHelper'; import { EventBusController } from '@/eventbus/eventBus.controller'; import { EventBusControllerEE } from '@/eventbus/eventBus.controller.ee'; import { LicenseController } from '@/license/license.controller'; @@ -69,7 +69,6 @@ import { OrchestrationController } from './controllers/orchestration.controller' import { WorkflowHistoryController } from './workflows/workflowHistory/workflowHistory.controller.ee'; import { InvitationController } from './controllers/invitation.controller'; // import { CollaborationService } from './collaboration/collaboration.service'; -import { BadRequestError } from './errors/response-errors/bad-request.error'; import { OrchestrationService } from '@/services/orchestration.service'; import { ProjectController } from './controllers/project.controller'; import { RoleController } from './controllers/role.controller'; @@ -150,6 +149,7 @@ export class Server extends AbstractServer { AIController, ProjectController, RoleController, + CurlController, ]; if ( @@ -266,23 +266,6 @@ export class Server extends AbstractServer { this.logger.warn(`Source Control initialization failed: ${error.message}`); } - // ---------------------------------------- - // curl-converter - // ---------------------------------------- - this.app.post( - `/${this.restEndpoint}/curl-to-json`, - ResponseHelper.send(async (req: CurlHelper.ToJson) => { - const curlCommand = req.body.curlCommand ?? ''; - - try { - const parameters = toHttpNodeParameters(curlCommand); - return ResponseHelper.flattenObject(parameters, 'parameters'); - } catch (e) { - throw new BadRequestError('Invalid cURL command'); - } - }), - ); - // ---------------------------------------- // Options // ---------------------------------------- diff --git a/packages/cli/src/controllers/curl.controller.ts b/packages/cli/src/controllers/curl.controller.ts new file mode 100644 index 00000000000..91338b3eadc --- /dev/null +++ b/packages/cli/src/controllers/curl.controller.ts @@ -0,0 +1,19 @@ +import { Request } from 'express'; +import { Post, RestController } from '@/decorators'; +import { BadRequestError } from '@/errors/response-errors/bad-request.error'; +import { CurlService, flattenObject } from '@/services/curl.service'; + +@RestController('/curl') +export class CurlController { + constructor(private readonly curlService: CurlService) {} + + @Post('/to-json') + toJson(req: Request<{}, {}, { curlCommand: string }>) { + try { + const parameters = this.curlService.toHttpNodeParameters(req.body.curlCommand); + return flattenObject(parameters, 'parameters'); + } catch (e) { + throw new BadRequestError('Invalid cURL command'); + } + } +} diff --git a/packages/cli/src/requests.ts b/packages/cli/src/requests.ts index 2f325af09b8..378afa3648b 100644 --- a/packages/cli/src/requests.ts +++ b/packages/cli/src/requests.ts @@ -456,14 +456,6 @@ export declare namespace NodeRequest { type Update = Post; } -// ---------------------------------- -// /curl-to-json -// ---------------------------------- - -export declare namespace CurlHelper { - type ToJson = AuthenticatedRequest<{}, {}, { curlCommand?: string }>; -} - // ---------------------------------- // /license // ---------------------------------- diff --git a/packages/cli/src/CurlConverterHelper.ts b/packages/cli/src/services/curl.service.ts similarity index 51% rename from packages/cli/src/CurlConverterHelper.ts rename to packages/cli/src/services/curl.service.ts index 5e1e3fa16b2..01c32868638 100644 --- a/packages/cli/src/CurlConverterHelper.ts +++ b/packages/cli/src/services/curl.service.ts @@ -1,3 +1,4 @@ +import { Service } from 'typedi'; import curlconverter from 'curlconverter'; import get from 'lodash/get'; import type { IDataObject } from 'n8n-workflow'; @@ -35,7 +36,7 @@ interface Parameter { value: string; } -export interface HttpNodeParameters { +interface HttpNodeParameters { url?: string; method: string; sendBody?: boolean; @@ -109,10 +110,6 @@ const DOWNLOAD_FILE_FLAGS = ['-O', '-o']; const IGNORE_SSL_ISSUES_FLAGS = ['-k', '--insecure']; -const curlToJson = (curlCommand: string): CurlJson => { - return jsonParse(curlconverter.toJsonString(curlCommand)); -}; - const isContentType = (headers: CurlJson['headers'], contentType: ContentTypes): boolean => { return get(headers, CONTENT_TYPE_KEY) === contentType; }; @@ -154,7 +151,7 @@ const isBinaryRequest = (curlJson: CurlJson): boolean => { return false; }; -const sanatizeCurlCommand = (curlCommand: string) => +const sanitizeCurlCommand = (curlCommand: string) => curlCommand .replace(/\r\n/g, ' ') .replace(/\n/g, ' ') @@ -262,206 +259,220 @@ const mapCookies = (cookies: CurlJson['cookies']): { cookie: string } | {} => { }; }; -// eslint-disable-next-line complexity -export const toHttpNodeParameters = (curlCommand: string): HttpNodeParameters => { - const curlJson = curlToJson(curlCommand); +export const flattenObject = (obj: { [x: string]: any }, prefix = '') => + Object.keys(obj).reduce((acc, k) => { + const pre = prefix.length ? prefix + '.' : ''; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k)); + //@ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + else acc[pre + k] = obj[k]; + return acc; + }, {}); - if (!curlJson.headers) curlJson.headers = {}; +@Service() +export class CurlService { + // eslint-disable-next-line complexity + toHttpNodeParameters(curlCommand: string): HttpNodeParameters { + const curlJson = jsonParse(curlconverter.toJsonString(curlCommand)); - lowerCaseContentTypeKey(curlJson.headers); + if (!curlJson.headers) curlJson.headers = {}; - // set basic authentication - if (curlJson.auth) { - const { user, password: pass } = curlJson.auth; - Object.assign(curlJson.headers, { - authorization: `Basic ${encodeBasicAuthentication(user, pass)}`, - }); - } + lowerCaseContentTypeKey(curlJson.headers); - const httpNodeParameters: HttpNodeParameters = { - url: curlJson.url, - authentication: 'none', - method: curlJson.method.toUpperCase(), - ...extractHeaders({ ...curlJson.headers, ...mapCookies(curlJson.cookies) }), - ...extractQueries(curlJson.queries), - options: { - redirect: { - redirect: {}, + // set basic authentication + if (curlJson.auth) { + const { user, password: pass } = curlJson.auth; + Object.assign(curlJson.headers, { + authorization: `Basic ${encodeBasicAuthentication(user, pass)}`, + }); + } + + const httpNodeParameters: HttpNodeParameters = { + url: curlJson.url, + authentication: 'none', + method: curlJson.method.toUpperCase(), + ...extractHeaders({ ...curlJson.headers, ...mapCookies(curlJson.cookies) }), + ...extractQueries(curlJson.queries), + options: { + redirect: { + redirect: {}, + }, + response: { + response: {}, + }, }, - response: { - response: {}, - }, - }, - }; + }; - //attempt to get the curl flags not supported by the library - const curl = sanatizeCurlCommand(curlCommand); + //attempt to get the curl flags not supported by the library + const curl = sanitizeCurlCommand(curlCommand); - //check for follow redirect flags - if (FOLLOW_REDIRECT_FLAGS.some((flag) => curl.includes(` ${flag}`))) { - Object.assign(httpNodeParameters.options.redirect?.redirect, { followRedirects: true }); + //check for follow redirect flags + if (FOLLOW_REDIRECT_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + Object.assign(httpNodeParameters.options.redirect?.redirect, { followRedirects: true }); - if (curl.includes(` ${MAX_REDIRECT_FLAG}`)) { - const extractedValue = Array.from( - extractGroup(curl, new RegExp(` ${MAX_REDIRECT_FLAG} (\\d+)`, 'g')), - ); - if (extractedValue.length) { - const [_, maxRedirects] = extractedValue[0]; - if (maxRedirects) { - Object.assign(httpNodeParameters.options.redirect?.redirect, { maxRedirects }); + if (curl.includes(` ${MAX_REDIRECT_FLAG}`)) { + const extractedValue = Array.from( + extractGroup(curl, new RegExp(` ${MAX_REDIRECT_FLAG} (\\d+)`, 'g')), + ); + if (extractedValue.length) { + const [_, maxRedirects] = extractedValue[0]; + if (maxRedirects) { + Object.assign(httpNodeParameters.options.redirect?.redirect, { maxRedirects }); + } } } } - } - //check for proxy flags - if (PROXY_FLAGS.some((flag) => curl.includes(` ${flag}`))) { - const foundFlag = PROXY_FLAGS.find((flag) => curl.includes(` ${flag}`)); - if (foundFlag) { - const extractedValue = Array.from( - extractGroup(curl, new RegExp(` ${foundFlag} (\\S*)`, 'g')), - ); - if (extractedValue.length) { - const [_, proxy] = extractedValue[0]; - Object.assign(httpNodeParameters.options, { proxy }); + //check for proxy flags + if (PROXY_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = PROXY_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { + const extractedValue = Array.from( + extractGroup(curl, new RegExp(` ${foundFlag} (\\S*)`, 'g')), + ); + if (extractedValue.length) { + const [_, proxy] = extractedValue[0]; + Object.assign(httpNodeParameters.options, { proxy }); + } } } - } - // check for "include header in output" flag - if (INCLUDE_HEADERS_IN_OUTPUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) { - Object.assign(httpNodeParameters.options?.response?.response, { - fullResponse: true, - responseFormat: 'autodetect', - }); - } + // check for "include header in output" flag + if (INCLUDE_HEADERS_IN_OUTPUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + Object.assign(httpNodeParameters.options?.response?.response, { + fullResponse: true, + responseFormat: 'autodetect', + }); + } - // check for request flag - if (REQUEST_FLAGS.some((flag) => curl.includes(` ${flag}`))) { - const foundFlag = REQUEST_FLAGS.find((flag) => curl.includes(` ${flag}`)); - if (foundFlag) { - const extractedValue = Array.from( - extractGroup(curl, new RegExp(` ${foundFlag} (\\w+)`, 'g')), - ); - if (extractedValue.length) { - const [_, request] = extractedValue[0]; - httpNodeParameters.method = request.toUpperCase(); + // check for request flag + if (REQUEST_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = REQUEST_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { + const extractedValue = Array.from( + extractGroup(curl, new RegExp(` ${foundFlag} (\\w+)`, 'g')), + ); + if (extractedValue.length) { + const [_, request] = extractedValue[0]; + httpNodeParameters.method = request.toUpperCase(); + } } } - } - // check for timeout flag - if (TIMEOUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) { - const foundFlag = TIMEOUT_FLAGS.find((flag) => curl.includes(` ${flag}`)); - if (foundFlag) { - const extractedValue = Array.from( - extractGroup(curl, new RegExp(` ${foundFlag} (\\d+)`, 'g')), - ); - if (extractedValue.length) { - const [_, timeout] = extractedValue[0]; + // check for timeout flag + if (TIMEOUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = TIMEOUT_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { + const extractedValue = Array.from( + extractGroup(curl, new RegExp(` ${foundFlag} (\\d+)`, 'g')), + ); + if (extractedValue.length) { + const [_, timeout] = extractedValue[0]; + Object.assign(httpNodeParameters.options, { + timeout: parseInt(timeout, 10) * 1000, + }); + } + } + } + + // check for download flag + if (DOWNLOAD_FILE_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = DOWNLOAD_FILE_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { + Object.assign(httpNodeParameters.options.response.response, { + responseFormat: 'file', + outputPropertyName: 'data', + }); + } + } + + if (IGNORE_SSL_ISSUES_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = IGNORE_SSL_ISSUES_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { Object.assign(httpNodeParameters.options, { - timeout: parseInt(timeout, 10) * 1000, + allowUnauthorizedCerts: true, }); } } - } - // check for download flag - if (DOWNLOAD_FILE_FLAGS.some((flag) => curl.includes(` ${flag}`))) { - const foundFlag = DOWNLOAD_FILE_FLAGS.find((flag) => curl.includes(` ${flag}`)); - if (foundFlag) { - Object.assign(httpNodeParameters.options.response.response, { - responseFormat: 'file', - outputPropertyName: 'data', + const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY] as ContentTypes; + + if (isBinaryRequest(curlJson)) { + return Object.assign(httpNodeParameters, { + contentType: 'binaryData', + sendBody: true, }); } - } - if (IGNORE_SSL_ISSUES_FLAGS.some((flag) => curl.includes(` ${flag}`))) { - const foundFlag = IGNORE_SSL_ISSUES_FLAGS.find((flag) => curl.includes(` ${flag}`)); - if (foundFlag) { - Object.assign(httpNodeParameters.options, { - allowUnauthorizedCerts: true, + if (contentType && !SUPPORTED_CONTENT_TYPES.includes(contentType)) { + return Object.assign(httpNodeParameters, { + sendBody: true, + contentType: 'raw', + rawContentType: contentType, + body: Object.keys(curlJson?.data ?? {})[0], }); } - } - const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY] as ContentTypes; + if (isJsonRequest(curlJson)) { + Object.assign(httpNodeParameters, { + contentType: 'json', + sendBody: true, + }); - if (isBinaryRequest(curlJson)) { - return Object.assign(httpNodeParameters, { - contentType: 'binaryData', - sendBody: true, - }); - } + if (curlJson.data) { + const json = extractJson(curlJson.data); - if (contentType && !SUPPORTED_CONTENT_TYPES.includes(contentType)) { - return Object.assign(httpNodeParameters, { - sendBody: true, - contentType: 'raw', - rawContentType: contentType, - body: Object.keys(curlJson?.data ?? {})[0], - }); - } - - if (isJsonRequest(curlJson)) { - Object.assign(httpNodeParameters, { - contentType: 'json', - sendBody: true, - }); - - if (curlJson.data) { - const json = extractJson(curlJson.data); - - if (jsonHasNestedObjects(json)) { - // json body - Object.assign(httpNodeParameters, { - specifyBody: 'json', - jsonBody: JSON.stringify(json, null, 2), - }); - } else { - // key-value body - Object.assign(httpNodeParameters, { - specifyBody: 'keypair', - bodyParameters: { - parameters: jsonBodyToNodeParameters(curlJson.data), - }, - }); + if (jsonHasNestedObjects(json)) { + // json body + Object.assign(httpNodeParameters, { + specifyBody: 'json', + jsonBody: JSON.stringify(json, null, 2), + }); + } else { + // key-value body + Object.assign(httpNodeParameters, { + specifyBody: 'keypair', + bodyParameters: { + parameters: jsonBodyToNodeParameters(curlJson.data), + }, + }); + } } + } else if (isFormUrlEncodedRequest(curlJson)) { + Object.assign(httpNodeParameters, { + contentType: 'form-urlencoded', + sendBody: true, + specifyBody: 'keypair', + bodyParameters: { + parameters: keyValueBodyToNodeParameters(curlJson.data), + }, + }); + } else if (isMultipartRequest(curlJson)) { + Object.assign(httpNodeParameters, { + contentType: 'multipart-form-data', + sendBody: true, + bodyParameters: { + parameters: multipartToNodeParameters(curlJson.data, curlJson.files), + }, + }); + } else { + // could not figure the content type so do not set the body + Object.assign(httpNodeParameters, { + sendBody: false, + }); } - } else if (isFormUrlEncodedRequest(curlJson)) { - Object.assign(httpNodeParameters, { - contentType: 'form-urlencoded', - sendBody: true, - specifyBody: 'keypair', - bodyParameters: { - parameters: keyValueBodyToNodeParameters(curlJson.data), - }, - }); - } else if (isMultipartRequest(curlJson)) { - Object.assign(httpNodeParameters, { - contentType: 'multipart-form-data', - sendBody: true, - bodyParameters: { - parameters: multipartToNodeParameters(curlJson.data, curlJson.files), - }, - }); - } else { - // could not figure the content type so do not set the body - Object.assign(httpNodeParameters, { - sendBody: false, - }); - } - if (!Object.keys(httpNodeParameters.options?.redirect.redirect).length) { - // @ts-ignore - delete httpNodeParameters.options.redirect; - } + if (!Object.keys(httpNodeParameters.options?.redirect.redirect).length) { + // @ts-ignore + delete httpNodeParameters.options.redirect; + } - if (!Object.keys(httpNodeParameters.options.response.response).length) { - // @ts-ignore - delete httpNodeParameters.options.response; - } + if (!Object.keys(httpNodeParameters.options.response.response).length) { + // @ts-ignore + delete httpNodeParameters.options.response; + } - return httpNodeParameters; -}; + return httpNodeParameters; + } +} diff --git a/packages/cli/test/unit/controllers/curl.controller.test.ts b/packages/cli/test/unit/controllers/curl.controller.test.ts new file mode 100644 index 00000000000..35c263349e3 --- /dev/null +++ b/packages/cli/test/unit/controllers/curl.controller.test.ts @@ -0,0 +1,51 @@ +import type { Request } from 'express'; +import { mock } from 'jest-mock-extended'; +import { BadRequestError } from '@/errors/response-errors/bad-request.error'; +import { CurlController } from '@/controllers/curl.controller'; +import type { CurlService } from '@/services/curl.service'; + +describe('CurlController', () => { + const service = mock(); + const controller = new CurlController(service); + + beforeEach(() => jest.clearAllMocks()); + + describe('toJson', () => { + it('should throw BadRequestError when invalid cURL command is provided', () => { + const req = mock(); + service.toHttpNodeParameters.mockImplementation(() => { + throw new Error(); + }); + + expect(() => controller.toJson(req)).toThrow(BadRequestError); + }); + + it('should return flattened parameters when valid cURL command is provided', () => { + const curlCommand = 'curl -v -X GET https://test.n8n.berlin/users'; + const req = mock(); + req.body = { curlCommand }; + service.toHttpNodeParameters.mockReturnValue({ + url: 'https://test.n8n.berlin/users', + authentication: 'none', + method: 'GET', + sendHeaders: false, + sendQuery: false, + options: { + redirect: { redirect: {} }, + response: { response: {} }, + }, + sendBody: false, + }); + + const result = controller.toJson(req); + expect(result).toEqual({ + 'parameters.method': 'GET', + 'parameters.url': 'https://test.n8n.berlin/users', + 'parameters.authentication': 'none', + 'parameters.sendBody': false, + 'parameters.sendHeaders': false, + 'parameters.sendQuery': false, + }); + }); + }); +}); diff --git a/packages/cli/test/unit/CurlConverterHelper.test.ts b/packages/cli/test/unit/services/curl.service.test.ts similarity index 90% rename from packages/cli/test/unit/CurlConverterHelper.test.ts rename to packages/cli/test/unit/services/curl.service.test.ts index 0f4ec607956..21f87b45b77 100644 --- a/packages/cli/test/unit/CurlConverterHelper.test.ts +++ b/packages/cli/test/unit/services/curl.service.test.ts @@ -1,10 +1,12 @@ -import { toHttpNodeParameters } from '@/CurlConverterHelper'; +import { CurlService } from '@/services/curl.service'; + +describe('CurlService', () => { + const service = new CurlService(); -describe('CurlConverterHelper', () => { test('Should parse form-urlencoded content type correctly', () => { const curl = 'curl -X POST https://reqbin.com/echo/post/form -H "Content-Type: application/x-www-form-urlencoded" -d "param1=value1¶m2=value2"'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo/post/form'); expect(parameters.sendBody).toBe(true); expect(parameters.bodyParameters?.parameters[0].name).toBe('param1'); @@ -19,7 +21,7 @@ describe('CurlConverterHelper', () => { test('Should parse JSON content type correctly', () => { const curl = 'curl -X POST https://reqbin.com/echo/post/json -H \'Content-Type: application/json\' -d \'{"login":"my_login","password":"my_password"}\''; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo/post/json'); expect(parameters.sendBody).toBe(true); expect(parameters.bodyParameters?.parameters[0].name).toBe('login'); @@ -34,7 +36,7 @@ describe('CurlConverterHelper', () => { test('Should parse multipart-form-data content type correctly', () => { const curl = 'curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo/post/json'); expect(parameters.sendBody).toBe(true); expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData'); @@ -50,7 +52,7 @@ describe('CurlConverterHelper', () => { test('Should parse binary request correctly', () => { const curl = "curl --location --request POST 'https://www.website.com' --header 'Content-Type: image/png' --data-binary '@/Users/image.png"; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://www.website.com'); expect(parameters.method).toBe('POST'); expect(parameters.sendBody).toBe(true); @@ -64,7 +66,7 @@ describe('CurlConverterHelper', () => { -H "Content-Type: application/xml" -H "Accept: application/xml" -d "my_loginmy_password"`; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo/post/xml'); expect(parameters.method).toBe('POST'); expect(parameters.sendBody).toBe(true); @@ -79,7 +81,7 @@ describe('CurlConverterHelper', () => { test('Should parse header properties and keep the original case', () => { const curl = 'curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename -H "ACCEPT: text/javascript" -H "content-type: multipart/form-data"'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo/post/json'); expect(parameters.sendBody).toBe(true); expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData'); @@ -96,7 +98,7 @@ describe('CurlConverterHelper', () => { test('Should parse querystring properties', () => { const curl = "curl -G -d 'q=kitties' -d 'count=20' https://google.com/search"; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://google.com/search'); expect(parameters.sendBody).toBe(false); expect(parameters.contentType).toBeUndefined(); @@ -110,7 +112,7 @@ describe('CurlConverterHelper', () => { test('Should parse basic authentication property and keep the original case', () => { const curl = 'curl https://reqbin.com/echo -u "login:password"'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.sendBody).toBe(false); expect(parameters.contentType).toBeUndefined(); @@ -124,7 +126,7 @@ describe('CurlConverterHelper', () => { test('Should parse location flag with --location', () => { const curl = 'curl https://reqbin.com/echo -u "login:password" --location'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.sendBody).toBe(false); expect(parameters.contentType).toBeUndefined(); @@ -139,7 +141,7 @@ describe('CurlConverterHelper', () => { test('Should parse location flag with --L', () => { const curl = 'curl https://reqbin.com/echo -u "login:password" -L'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.sendBody).toBe(false); expect(parameters.contentType).toBeUndefined(); @@ -154,7 +156,7 @@ describe('CurlConverterHelper', () => { test('Should parse location and max redirects flags with --location and --max-redirs 10', () => { const curl = 'curl https://reqbin.com/echo -u "login:password" --location --max-redirs 10'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.sendBody).toBe(false); expect(parameters.contentType).toBeUndefined(); @@ -170,7 +172,7 @@ describe('CurlConverterHelper', () => { test('Should parse proxy flag -x', () => { const curl = 'curl https://reqbin.com/echo -u "login:password" -x https://google.com'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.sendBody).toBe(false); expect(parameters.contentType).toBeUndefined(); @@ -185,7 +187,7 @@ describe('CurlConverterHelper', () => { test('Should parse proxy flag --proxy', () => { const curl = 'curl https://reqbin.com/echo -u "login:password" -x https://google.com'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.sendBody).toBe(false); expect(parameters.contentType).toBeUndefined(); @@ -200,7 +202,7 @@ describe('CurlConverterHelper', () => { test('Should parse include headers on output flag --include', () => { const curl = 'curl https://reqbin.com/echo -u "login:password" --include -x https://google.com'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.sendBody).toBe(false); expect(parameters.contentType).toBeUndefined(); @@ -215,7 +217,7 @@ describe('CurlConverterHelper', () => { test('Should parse include headers on output flag -i', () => { const curl = 'curl https://reqbin.com/echo -u "login:password" -x https://google.com -i'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.sendBody).toBe(false); expect(parameters.contentType).toBeUndefined(); @@ -230,7 +232,7 @@ describe('CurlConverterHelper', () => { test('Should parse include request flag -X', () => { const curl = 'curl -X POST https://reqbin.com/echo -u "login:password" -x https://google.com'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.method).toBe('POST'); expect(parameters.sendBody).toBe(false); @@ -239,7 +241,7 @@ describe('CurlConverterHelper', () => { test('Should parse include request flag --request', () => { const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" -x https://google.com'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.method).toBe('POST'); expect(parameters.sendBody).toBe(false); @@ -248,7 +250,7 @@ describe('CurlConverterHelper', () => { test('Should parse include timeout flag --connect-timeout', () => { const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" --connect-timeout 20'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.method).toBe('POST'); expect(parameters.sendBody).toBe(false); @@ -257,7 +259,7 @@ describe('CurlConverterHelper', () => { test('Should parse download file flag -O', () => { const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" -O'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.method).toBe('POST'); expect(parameters.sendBody).toBe(false); @@ -267,7 +269,7 @@ describe('CurlConverterHelper', () => { test('Should parse download file flag -o', () => { const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" -o'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.method).toBe('POST'); expect(parameters.sendBody).toBe(false); @@ -277,7 +279,7 @@ describe('CurlConverterHelper', () => { test('Should parse ignore SSL flag -k', () => { const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" -k'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.method).toBe('POST'); expect(parameters.sendBody).toBe(false); @@ -286,7 +288,7 @@ describe('CurlConverterHelper', () => { test('Should parse ignore SSL flag --insecure', () => { const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" --insecure'; - const parameters = toHttpNodeParameters(curl); + const parameters = service.toHttpNodeParameters(curl); expect(parameters.url).toBe('https://reqbin.com/echo'); expect(parameters.method).toBe('POST'); expect(parameters.sendBody).toBe(false); diff --git a/packages/editor-ui/src/api/curlHelper.ts b/packages/editor-ui/src/api/curlHelper.ts index 4727ff17040..5c09ab0b9a7 100644 --- a/packages/editor-ui/src/api/curlHelper.ts +++ b/packages/editor-ui/src/api/curlHelper.ts @@ -5,5 +5,5 @@ export async function getCurlToJson( context: IRestApiContext, curlCommand: string, ): Promise { - return await makeRestApiRequest(context, 'POST', '/curl-to-json', { curlCommand }); + return await makeRestApiRequest(context, 'POST', '/curl/to-json', { curlCommand }); }